summaryrefslogtreecommitdiff
path: root/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py
diff options
context:
space:
mode:
Diffstat (limited to 'venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py')
-rw-r--r--venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py338
1 files changed, 338 insertions, 0 deletions
diff --git a/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py
new file mode 100644
index 0000000..9e6ef41
--- /dev/null
+++ b/venv/lib/python3.7/site-packages/pip-10.0.1-py3.7.egg/pip/_internal/req/req_file.py
@@ -0,0 +1,338 @@
1"""
2Requirements file parsing
3"""
4
5from __future__ import absolute_import
6
7import optparse
8import os
9import re
10import shlex
11import sys
12
13from pip._vendor.six.moves import filterfalse
14from pip._vendor.six.moves.urllib import parse as urllib_parse
15
16from pip._internal import cmdoptions
17from pip._internal.download import get_file_content
18from pip._internal.exceptions import RequirementsFileParseError
19from pip._internal.req.req_install import InstallRequirement
20
21__all__ = ['parse_requirements']
22
23SCHEME_RE = re.compile(r'^(http|https|file):', re.I)
24COMMENT_RE = re.compile(r'(^|\s)+#.*$')
25
26# Matches environment variable-style values in '${MY_VARIABLE_1}' with the
27# variable name consisting of only uppercase letters, digits or the '_'
28# (underscore). This follows the POSIX standard defined in IEEE Std 1003.1,
29# 2013 Edition.
30ENV_VAR_RE = re.compile(r'(?P<var>\$\{(?P<name>[A-Z0-9_]+)\})')
31
32SUPPORTED_OPTIONS = [
33 cmdoptions.constraints,
34 cmdoptions.editable,
35 cmdoptions.requirements,
36 cmdoptions.no_index,
37 cmdoptions.index_url,
38 cmdoptions.find_links,
39 cmdoptions.extra_index_url,
40 cmdoptions.always_unzip,
41 cmdoptions.no_binary,
42 cmdoptions.only_binary,
43 cmdoptions.pre,
44 cmdoptions.process_dependency_links,
45 cmdoptions.trusted_host,
46 cmdoptions.require_hashes,
47]
48
49# options to be passed to requirements
50SUPPORTED_OPTIONS_REQ = [
51 cmdoptions.install_options,
52 cmdoptions.global_options,
53 cmdoptions.hash,
54]
55
56# the 'dest' string values
57SUPPORTED_OPTIONS_REQ_DEST = [o().dest for o in SUPPORTED_OPTIONS_REQ]
58
59
60def parse_requirements(filename, finder=None, comes_from=None, options=None,
61 session=None, constraint=False, wheel_cache=None):
62 """Parse a requirements file and yield InstallRequirement instances.
63
64 :param filename: Path or url of requirements file.
65 :param finder: Instance of pip.index.PackageFinder.
66 :param comes_from: Origin description of requirements.
67 :param options: cli options.
68 :param session: Instance of pip.download.PipSession.
69 :param constraint: If true, parsing a constraint file rather than
70 requirements file.
71 :param wheel_cache: Instance of pip.wheel.WheelCache
72 """
73 if session is None:
74 raise TypeError(
75 "parse_requirements() missing 1 required keyword argument: "
76 "'session'"
77 )
78
79 _, content = get_file_content(
80 filename, comes_from=comes_from, session=session
81 )
82
83 lines_enum = preprocess(content, options)
84
85 for line_number, line in lines_enum:
86 req_iter = process_line(line, filename, line_number, finder,
87 comes_from, options, session, wheel_cache,
88 constraint=constraint)
89 for req in req_iter:
90 yield req
91
92
93def preprocess(content, options):
94 """Split, filter, and join lines, and return a line iterator
95
96 :param content: the content of the requirements file
97 :param options: cli options
98 """
99 lines_enum = enumerate(content.splitlines(), start=1)
100 lines_enum = join_lines(lines_enum)
101 lines_enum = ignore_comments(lines_enum)
102 lines_enum = skip_regex(lines_enum, options)
103 lines_enum = expand_env_variables(lines_enum)
104 return lines_enum
105
106
107def process_line(line, filename, line_number, finder=None, comes_from=None,
108 options=None, session=None, wheel_cache=None,
109 constraint=False):
110 """Process a single requirements line; This can result in creating/yielding
111 requirements, or updating the finder.
112
113 For lines that contain requirements, the only options that have an effect
114 are from SUPPORTED_OPTIONS_REQ, and they are scoped to the
115 requirement. Other options from SUPPORTED_OPTIONS may be present, but are
116 ignored.
117
118 For lines that do not contain requirements, the only options that have an
119 effect are from SUPPORTED_OPTIONS. Options from SUPPORTED_OPTIONS_REQ may
120 be present, but are ignored. These lines may contain multiple options
121 (although our docs imply only one is supported), and all our parsed and
122 affect the finder.
123
124 :param constraint: If True, parsing a constraints file.
125 :param options: OptionParser options that we may update
126 """
127 parser = build_parser(line)
128 defaults = parser.get_default_values()
129 defaults.index_url = None
130 if finder:
131 # `finder.format_control` will be updated during parsing
132 defaults.format_control = finder.format_control
133 args_str, options_str = break_args_options(line)
134 if sys.version_info < (2, 7, 3):
135 # Prior to 2.7.3, shlex cannot deal with unicode entries
136 options_str = options_str.encode('utf8')
137 opts, _ = parser.parse_args(shlex.split(options_str), defaults)
138
139 # preserve for the nested code path
140 line_comes_from = '%s %s (line %s)' % (
141 '-c' if constraint else '-r', filename, line_number,
142 )
143
144 # yield a line requirement
145 if args_str:
146 isolated = options.isolated_mode if options else False
147 if options:
148 cmdoptions.check_install_build_global(options, opts)
149 # get the options that apply to requirements
150 req_options = {}
151 for dest in SUPPORTED_OPTIONS_REQ_DEST:
152 if dest in opts.__dict__ and opts.__dict__[dest]:
153 req_options[dest] = opts.__dict__[dest]
154 yield InstallRequirement.from_line(
155 args_str, line_comes_from, constraint=constraint,
156 isolated=isolated, options=req_options, wheel_cache=wheel_cache
157 )
158
159 # yield an editable requirement
160 elif opts.editables:
161 isolated = options.isolated_mode if options else False
162 yield InstallRequirement.from_editable(
163 opts.editables[0], comes_from=line_comes_from,
164 constraint=constraint, isolated=isolated, wheel_cache=wheel_cache
165 )
166
167 # parse a nested requirements file
168 elif opts.requirements or opts.constraints:
169 if opts.requirements:
170 req_path = opts.requirements[0]
171 nested_constraint = False
172 else:
173 req_path = opts.constraints[0]
174 nested_constraint = True
175 # original file is over http
176 if SCHEME_RE.search(filename):
177 # do a url join so relative paths work
178 req_path = urllib_parse.urljoin(filename, req_path)
179 # original file and nested file are paths
180 elif not SCHEME_RE.search(req_path):
181 # do a join so relative paths work
182 req_path = os.path.join(os.path.dirname(filename), req_path)
183 # TODO: Why not use `comes_from='-r {} (line {})'` here as well?
184 parser = parse_requirements(
185 req_path, finder, comes_from, options, session,
186 constraint=nested_constraint, wheel_cache=wheel_cache
187 )
188 for req in parser:
189 yield req
190
191 # percolate hash-checking option upward
192 elif opts.require_hashes:
193 options.require_hashes = opts.require_hashes
194
195 # set finder options
196 elif finder:
197 if opts.index_url:
198 finder.index_urls = [opts.index_url]
199 if opts.no_index is True:
200 finder.index_urls = []
201 if opts.extra_index_urls:
202 finder.index_urls.extend(opts.extra_index_urls)
203 if opts.find_links:
204 # FIXME: it would be nice to keep track of the source
205 # of the find_links: support a find-links local path
206 # relative to a requirements file.
207 value = opts.find_links[0]
208 req_dir = os.path.dirname(os.path.abspath(filename))
209 relative_to_reqs_file = os.path.join(req_dir, value)
210 if os.path.exists(relative_to_reqs_file):
211 value = relative_to_reqs_file
212 finder.find_links.append(value)
213 if opts.pre:
214 finder.allow_all_prereleases = True
215 if opts.process_dependency_links:
216 finder.process_dependency_links = True
217 if opts.trusted_hosts:
218 finder.secure_origins.extend(
219 ("*", host, "*") for host in opts.trusted_hosts)
220
221
222def break_args_options(line):
223 """Break up the line into an args and options string. We only want to shlex
224 (and then optparse) the options, not the args. args can contain markers
225 which are corrupted by shlex.
226 """
227 tokens = line.split(' ')
228 args = []
229 options = tokens[:]
230 for token in tokens:
231 if token.startswith('-') or token.startswith('--'):
232 break
233 else:
234 args.append(token)
235 options.pop(0)
236 return ' '.join(args), ' '.join(options)
237
238
239def build_parser(line):
240 """
241 Return a parser for parsing requirement lines
242 """
243 parser = optparse.OptionParser(add_help_option=False)
244
245 option_factories = SUPPORTED_OPTIONS + SUPPORTED_OPTIONS_REQ
246 for option_factory in option_factories:
247 option = option_factory()
248 parser.add_option(option)
249
250 # By default optparse sys.exits on parsing errors. We want to wrap
251 # that in our own exception.
252 def parser_exit(self, msg):
253 # add offending line
254 msg = 'Invalid requirement: %s\n%s' % (line, msg)
255 raise RequirementsFileParseError(msg)
256 parser.exit = parser_exit
257
258 return parser
259
260
261def join_lines(lines_enum):
262 """Joins a line ending in '\' with the previous line (except when following
263 comments). The joined line takes on the index of the first line.
264 """
265 primary_line_number = None
266 new_line = []
267 for line_number, line in lines_enum:
268 if not line.endswith('\\') or COMMENT_RE.match(line):
269 if COMMENT_RE.match(line):
270 # this ensures comments are always matched later
271 line = ' ' + line
272 if new_line:
273 new_line.append(line)
274 yield primary_line_number, ''.join(new_line)
275 new_line = []
276 else:
277 yield line_number, line
278 else:
279 if not new_line:
280 primary_line_number = line_number
281 new_line.append(line.strip('\\'))
282
283 # last line contains \
284 if new_line:
285 yield primary_line_number, ''.join(new_line)
286
287 # TODO: handle space after '\'.
288
289
290def ignore_comments(lines_enum):
291 """
292 Strips comments and filter empty lines.
293 """
294 for line_number, line in lines_enum:
295 line = COMMENT_RE.sub('', line)
296 line = line.strip()
297 if line:
298 yield line_number, line
299
300
301def skip_regex(lines_enum, options):
302 """
303 Skip lines that match '--skip-requirements-regex' pattern
304
305 Note: the regex pattern is only built once
306 """
307 skip_regex = options.skip_requirements_regex if options else None
308 if skip_regex:
309 pattern = re.compile(skip_regex)
310 lines_enum = filterfalse(lambda e: pattern.search(e[1]), lines_enum)
311 return lines_enum
312
313
314def expand_env_variables(lines_enum):
315 """Replace all environment variables that can be retrieved via `os.getenv`.
316
317 The only allowed format for environment variables defined in the
318 requirement file is `${MY_VARIABLE_1}` to ensure two things:
319
320 1. Strings that contain a `$` aren't accidentally (partially) expanded.
321 2. Ensure consistency across platforms for requirement files.
322
323 These points are the result of a discusssion on the `github pull
324 request #3514 <https://github.com/pypa/pip/pull/3514>`_.
325
326 Valid characters in variable names follow the `POSIX standard
327 <http://pubs.opengroup.org/onlinepubs/9699919799/>`_ and are limited
328 to uppercase letter, digits and the `_` (underscore).
329 """
330 for line_number, line in lines_enum:
331 for env_var, var_name in ENV_VAR_RE.findall(line):
332 value = os.getenv(var_name)
333 if not value:
334 continue
335
336 line = line.replace(env_var, value)
337
338 yield line_number, line