84
84
SUPPORTED_OPTIONS_REQ_DEST = [str (o ().dest ) for o in SUPPORTED_OPTIONS_REQ ]
85
85
86
86
87
+ class ParsedLine (object ):
88
+ def __init__ (
89
+ self ,
90
+ filename , # type: str
91
+ lineno , # type: int
92
+ comes_from , # type: str
93
+ args , # type: str
94
+ opts , # type: Values
95
+ constraint , # type: bool
96
+ ):
97
+ # type: (...) -> None
98
+ self .filename = filename
99
+ self .lineno = lineno
100
+ self .comes_from = comes_from
101
+ self .args = args
102
+ self .opts = opts
103
+ self .constraint = constraint
104
+
105
+
87
106
def parse_requirements (
88
107
filename , # type: str
89
108
finder = None , # type: Optional[PackageFinder]
@@ -113,20 +132,19 @@ def parse_requirements(
113
132
"'session'"
114
133
)
115
134
116
- _ , content = get_file_content (
117
- filename , comes_from = comes_from , session = session
118
- )
119
-
120
135
skip_requirements_regex = (
121
136
options .skip_requirements_regex if options else None
122
137
)
123
- lines_enum = preprocess (content , skip_requirements_regex )
138
+ line_parser = get_line_parser (finder )
139
+ parser = RequirementsFileParser (
140
+ session , line_parser , comes_from , skip_requirements_regex
141
+ )
124
142
125
- for line_number , line in lines_enum :
126
- req_iter = process_line ( line , filename , line_number , finder ,
127
- comes_from , options , session , wheel_cache ,
128
- use_pep517 = use_pep517 , constraint = constraint )
129
- for req in req_iter :
143
+ for parsed_line in parser . parse ( filename , constraint ) :
144
+ req = handle_line (
145
+ parsed_line , finder , options , session , wheel_cache , use_pep517
146
+ )
147
+ if req is not None :
130
148
yield req
131
149
132
150
@@ -146,21 +164,17 @@ def preprocess(content, skip_requirements_regex):
146
164
return lines_enum
147
165
148
166
149
- def process_line (
150
- line , # type: Text
151
- filename , # type: str
152
- line_number , # type: int
167
+ def handle_line (
168
+ line , # type: ParsedLine
153
169
finder = None , # type: Optional[PackageFinder]
154
- comes_from = None , # type: Optional[str]
155
170
options = None , # type: Optional[optparse.Values]
156
171
session = None , # type: Optional[PipSession]
157
172
wheel_cache = None , # type: Optional[WheelCache]
158
173
use_pep517 = None , # type: Optional[bool]
159
- constraint = False , # type: bool
160
174
):
161
- # type: (...) -> Iterator [InstallRequirement]
162
- """Process a single requirements line; This can result in creating/yielding
163
- requirements, or updating the finder.
175
+ # type: (...) -> Optional [InstallRequirement]
176
+ """Handle a single parsed requirements line; This can result in
177
+ creating/yielding requirements, or updating the finder.
164
178
165
179
For lines that contain requirements, the only options that have an effect
166
180
are from SUPPORTED_OPTIONS_REQ, and they are scoped to the
@@ -172,102 +186,65 @@ def process_line(
172
186
be present, but are ignored. These lines may contain multiple options
173
187
(although our docs imply only one is supported), and all our parsed and
174
188
affect the finder.
175
-
176
- :param constraint: If True, parsing a constraints file.
177
- :param options: OptionParser options that we may update
178
189
"""
179
- line_parser = get_line_parser (finder )
180
- try :
181
- args_str , opts = line_parser (line )
182
- except OptionParsingError as e :
183
- # add offending line
184
- msg = 'Invalid requirement: %s\n %s' % (line , e .msg )
185
- raise RequirementsFileParseError (msg )
186
-
187
- # parse a nested requirements file
188
- if (
189
- not args_str and
190
- not opts .editables and
191
- (opts .requirements or opts .constraints )
192
- ):
193
- if opts .requirements :
194
- req_path = opts .requirements [0 ]
195
- nested_constraint = False
196
- else :
197
- req_path = opts .constraints [0 ]
198
- nested_constraint = True
199
- # original file is over http
200
- if SCHEME_RE .search (filename ):
201
- # do a url join so relative paths work
202
- req_path = urllib_parse .urljoin (filename , req_path )
203
- # original file and nested file are paths
204
- elif not SCHEME_RE .search (req_path ):
205
- # do a join so relative paths work
206
- req_path = os .path .join (os .path .dirname (filename ), req_path )
207
- parsed_reqs = parse_requirements (
208
- req_path , finder , comes_from , options , session ,
209
- constraint = nested_constraint , wheel_cache = wheel_cache
210
- )
211
- for req in parsed_reqs :
212
- yield req
213
- return
214
190
215
191
# preserve for the nested code path
216
192
line_comes_from = '%s %s (line %s)' % (
217
- '-c' if constraint else '-r' , filename , line_number ,
193
+ '-c' if line . constraint else '-r' , line . filename , line . lineno ,
218
194
)
219
195
220
- # yield a line requirement
221
- if args_str :
196
+ # return a line requirement
197
+ if line . args :
222
198
isolated = options .isolated_mode if options else False
223
199
if options :
224
- cmdoptions .check_install_build_global (options , opts )
200
+ cmdoptions .check_install_build_global (options , line . opts )
225
201
# get the options that apply to requirements
226
202
req_options = {}
227
203
for dest in SUPPORTED_OPTIONS_REQ_DEST :
228
- if dest in opts .__dict__ and opts .__dict__ [dest ]:
229
- req_options [dest ] = opts .__dict__ [dest ]
230
- line_source = 'line {} of {}' .format (line_number , filename )
231
- yield install_req_from_line (
232
- args_str ,
204
+ if dest in line . opts .__dict__ and line . opts .__dict__ [dest ]:
205
+ req_options [dest ] = line . opts .__dict__ [dest ]
206
+ line_source = 'line {} of {}' .format (line . lineno , line . filename )
207
+ return install_req_from_line (
208
+ line . args ,
233
209
comes_from = line_comes_from ,
234
210
use_pep517 = use_pep517 ,
235
211
isolated = isolated ,
236
212
options = req_options ,
237
213
wheel_cache = wheel_cache ,
238
- constraint = constraint ,
214
+ constraint = line . constraint ,
239
215
line_source = line_source ,
240
216
)
241
217
242
- # yield an editable requirement
243
- elif opts .editables :
218
+ # return an editable requirement
219
+ elif line . opts .editables :
244
220
isolated = options .isolated_mode if options else False
245
- yield install_req_from_editable (
246
- opts .editables [0 ], comes_from = line_comes_from ,
221
+ return install_req_from_editable (
222
+ line . opts .editables [0 ], comes_from = line_comes_from ,
247
223
use_pep517 = use_pep517 ,
248
- constraint = constraint , isolated = isolated , wheel_cache = wheel_cache
224
+ constraint = line .constraint , isolated = isolated ,
225
+ wheel_cache = wheel_cache
249
226
)
250
227
251
228
# percolate hash-checking option upward
252
- elif opts .require_hashes :
253
- options .require_hashes = opts .require_hashes
229
+ elif line . opts .require_hashes :
230
+ options .require_hashes = line . opts .require_hashes
254
231
255
232
# set finder options
256
233
elif finder :
257
234
find_links = finder .find_links
258
235
index_urls = finder .index_urls
259
- if opts .index_url :
260
- index_urls = [opts .index_url ]
261
- if opts .no_index is True :
236
+ if line . opts .index_url :
237
+ index_urls = [line . opts .index_url ]
238
+ if line . opts .no_index is True :
262
239
index_urls = []
263
- if opts .extra_index_urls :
264
- index_urls .extend (opts .extra_index_urls )
265
- if opts .find_links :
240
+ if line . opts .extra_index_urls :
241
+ index_urls .extend (line . opts .extra_index_urls )
242
+ if line . opts .find_links :
266
243
# FIXME: it would be nice to keep track of the source
267
244
# of the find_links: support a find-links local path
268
245
# relative to a requirements file.
269
- value = opts .find_links [0 ]
270
- req_dir = os .path .dirname (os .path .abspath (filename ))
246
+ value = line . opts .find_links [0 ]
247
+ req_dir = os .path .dirname (os .path .abspath (line . filename ))
271
248
relative_to_reqs_file = os .path .join (req_dir , value )
272
249
if os .path .exists (relative_to_reqs_file ):
273
250
value = relative_to_reqs_file
@@ -279,23 +256,110 @@ def process_line(
279
256
)
280
257
finder .search_scope = search_scope
281
258
282
- if opts .pre :
259
+ if line . opts .pre :
283
260
finder .set_allow_all_prereleases ()
284
- for host in opts .trusted_hosts or []:
285
- source = 'line {} of {}' .format (line_number , filename )
286
- session .add_trusted_host (host , source = source )
261
+
262
+ if session :
263
+ for host in line .opts .trusted_hosts or []:
264
+ source = 'line {} of {}' .format (line .lineno , line .filename )
265
+ session .add_trusted_host (host , source = source )
266
+
267
+ return None
268
+
269
+
270
+ class RequirementsFileParser (object ):
271
+ def __init__ (
272
+ self ,
273
+ session , # type: PipSession
274
+ line_parser , # type: LineParser
275
+ comes_from , # type: str
276
+ skip_requirements_regex , # type: Optional[str]
277
+ ):
278
+ # type: (...) -> None
279
+ self ._session = session
280
+ self ._line_parser = line_parser
281
+ self ._comes_from = comes_from
282
+ self ._skip_requirements_regex = skip_requirements_regex
283
+
284
+ def parse (self , filename , constraint ):
285
+ # type: (str, bool) -> Iterator[ParsedLine]
286
+ """Parse a given file, yielding parsed lines.
287
+ """
288
+ for line in self ._parse_and_recurse (filename , constraint ):
289
+ yield line
290
+
291
+ def _parse_and_recurse (self , filename , constraint ):
292
+ # type: (str, bool) -> Iterator[ParsedLine]
293
+ for line in self ._parse_file (filename , constraint ):
294
+ if (
295
+ not line .args and
296
+ not line .opts .editables and
297
+ (line .opts .requirements or line .opts .constraints )
298
+ ):
299
+ # parse a nested requirements file
300
+ if line .opts .requirements :
301
+ req_path = line .opts .requirements [0 ]
302
+ nested_constraint = False
303
+ else :
304
+ req_path = line .opts .constraints [0 ]
305
+ nested_constraint = True
306
+
307
+ # original file is over http
308
+ if SCHEME_RE .search (filename ):
309
+ # do a url join so relative paths work
310
+ req_path = urllib_parse .urljoin (filename , req_path )
311
+ # original file and nested file are paths
312
+ elif not SCHEME_RE .search (req_path ):
313
+ # do a join so relative paths work
314
+ req_path = os .path .join (
315
+ os .path .dirname (filename ), req_path ,
316
+ )
317
+
318
+ for inner_line in self ._parse_and_recurse (
319
+ req_path , nested_constraint ,
320
+ ):
321
+ yield inner_line
322
+ else :
323
+ yield line
324
+
325
+ def _parse_file (self , filename , constraint ):
326
+ # type: (str, bool) -> Iterator[ParsedLine]
327
+ _ , content = get_file_content (
328
+ filename , comes_from = self ._comes_from , session = self ._session
329
+ )
330
+
331
+ lines_enum = preprocess (content , self ._skip_requirements_regex )
332
+
333
+ for line_number , line in lines_enum :
334
+ try :
335
+ args_str , opts = self ._line_parser (line )
336
+ except OptionParsingError as e :
337
+ # add offending line
338
+ msg = 'Invalid requirement: %s\n %s' % (line , e .msg )
339
+ raise RequirementsFileParseError (msg )
340
+
341
+ yield ParsedLine (
342
+ filename ,
343
+ line_number ,
344
+ self ._comes_from ,
345
+ args_str ,
346
+ opts ,
347
+ constraint ,
348
+ )
287
349
288
350
289
351
def get_line_parser (finder ):
290
352
# type: (Optional[PackageFinder]) -> LineParser
291
- parser = build_parser ()
292
- defaults = parser .get_default_values ()
293
- defaults .index_url = None
294
- if finder :
295
- defaults .format_control = finder .format_control
296
-
297
353
def parse_line (line ):
298
354
# type: (Text) -> Tuple[str, Values]
355
+ # Build new parser for each line since it accumulates appendable
356
+ # options.
357
+ parser = build_parser ()
358
+ defaults = parser .get_default_values ()
359
+ defaults .index_url = None
360
+ if finder :
361
+ defaults .format_control = finder .format_control
362
+
299
363
args_str , options_str = break_args_options (line )
300
364
# Prior to 2.7.3, shlex cannot deal with unicode entries
301
365
if sys .version_info < (2 , 7 , 3 ):
0 commit comments