@@ -35,18 +35,30 @@ async def install(
35
35
36
36
@dataclass
37
37
class RequirementsContext :
38
- """Track state while parsing requirements files."""
38
+ """Track state while parsing requirements files.
39
39
40
- index_url : Optional [str ] = None
40
+ This class maintains state about requirements and their associated index URLs.
41
+ Multiple index URLs can be tracked to support searching in multiple indices
42
+ in order of specification.
43
+ """
44
+
45
+ index_urls : List [str ] = None
41
46
requirements : List [str ] = None
42
47
43
48
def __post_init__ (self ):
44
49
if self .requirements is None :
45
50
self .requirements = []
51
+ if self .index_urls is None :
52
+ self .index_urls = []
53
+
54
+ def add_index_url (self , url : str ) -> None :
55
+ """Add an index URL to the list of URLs to search from."""
56
+ if url not in self .index_urls :
57
+ self .index_urls .append (url )
46
58
47
59
def add_requirement (self , req : str ):
48
- """Add a requirement with the currently active index URL ."""
49
- self .requirements .append ((req , self .index_url ))
60
+ """Add a requirement that will use the current index URLs ."""
61
+ self .requirements .append ((req , self .index_urls [:] if self . index_urls else None ))
50
62
51
63
52
64
REQ_FILE_PREFIX = r"^(-r|--requirements)\s*=?\s*(.*)\s*"
@@ -141,49 +153,45 @@ async def get_action_kwargs(argv: list[str]) -> tuple[typing.Optional[str], dict
141
153
action = args .action
142
154
143
155
if action == "install" :
156
+ all_index_urls = []
157
+ if args .index_url :
158
+ all_index_urls .append (args .index_url )
159
+
144
160
all_requirements = []
145
161
146
162
if args .packages :
147
- all_requirements .extend ((pkg , args . index_url ) for pkg in args .packages )
163
+ all_requirements .extend ((pkg , all_index_urls [:] ) for pkg in args .packages )
148
164
149
165
# Process requirements files
150
166
for req_file in args .requirements or []:
151
- context = RequirementsContext ()
152
-
153
- if not Path (req_file ).exists ():
154
- warn (f"piplite could not find requirements file { req_file } " )
155
- continue
156
-
157
- # First let the file be processed normally to capture any index URL
158
- for line_no , line in enumerate (
159
- Path (req_file ).read_text (encoding = "utf-8" ).splitlines ()
160
- ):
161
- await _packages_from_requirements_line (
162
- Path (req_file ), line_no + 1 , line , context
167
+ try :
168
+ requirements , file_index_urls = await _packages_from_requirements_file (
169
+ Path (req_file )
163
170
)
164
171
165
- # If CLI provided an index URL, it should override the file's index URL
166
- # We update all requirements to use the CLI index URL instead. Or, we use
167
- # whatever index URL was found in the file (if any).
168
- if args .index_url :
169
- all_requirements .extend (
170
- (req , args .index_url ) for req , _ in context .requirements
171
- )
172
- else :
173
- all_requirements .extend (context .requirements )
172
+ # If CLI provided an index URL, it should override the file's index URL
173
+ # We update all requirements to use the CLI index URL instead. Or, we use
174
+ # whatever index URL was found in the file (if any).
175
+ if args .index_url :
176
+ all_requirements .extend (
177
+ (req , all_index_urls ) for req , _ in requirements
178
+ )
179
+ else :
180
+ for url in file_index_urls :
181
+ if url not in all_index_urls :
182
+ all_index_urls .append (url )
183
+ all_requirements .extend (requirements )
184
+ except Exception as e :
185
+ warn (f"Error processing requirements file { req_file } : { e } " )
186
+ continue
174
187
175
188
if all_requirements :
176
189
kwargs ["requirements" ] = []
177
- active_index_url = None
190
+ kwargs [ "requirements" ]. extend ( req for req , _ in all_requirements )
178
191
179
- for req , idx in all_requirements :
180
- if idx is not None :
181
- active_index_url = idx
182
- kwargs ["requirements" ].append (req )
183
-
184
- # Set the final index URL, if we found one
185
- if active_index_url is not None :
186
- kwargs ["index_urls" ] = active_index_url
192
+ # Set the final index URLs, if we found any
193
+ if all_index_urls :
194
+ kwargs ["index_urls" ] = all_index_urls
187
195
188
196
if args .pre :
189
197
kwargs ["pre" ] = True
@@ -199,22 +207,29 @@ async def get_action_kwargs(argv: list[str]) -> tuple[typing.Optional[str], dict
199
207
200
208
async def _packages_from_requirements_file (
201
209
req_path : Path ,
202
- ) -> Tuple [List [str ], Optional [str ]]:
203
- """Extract (potentially nested) package requirements from a requirements file.
210
+ ) -> Tuple [List [Tuple [str , Optional [List [str ]]]], List [str ]]:
211
+ """Extract package requirements and index URLs from a requirements file.
212
+
213
+ This function processes a requirements file to collect both package requirements
214
+ and any index URLs specified in it (with support for nested requirements).
204
215
205
216
Returns:
206
- Tuple of (list of package requirements, optional index URL)
217
+ A tuple of:
218
+ - List of (requirement, index_urls) pairs, where index_urls is a list of URLs
219
+ that should be used for this requirement
220
+ - List of index URLs found in the file
207
221
"""
222
+
208
223
if not req_path .exists ():
209
224
warn (f"piplite could not find requirements file { req_path } " )
210
- return []
225
+ return [], []
211
226
212
227
context = RequirementsContext ()
213
228
214
229
for line_no , line in enumerate (req_path .read_text (encoding = "utf-8" ).splitlines ()):
215
230
await _packages_from_requirements_line (req_path , line_no + 1 , line , context )
216
231
217
- return context .requirements , context .index_url
232
+ return context .requirements , context .index_urls
218
233
219
234
220
235
async def _packages_from_requirements_line (
@@ -237,15 +252,20 @@ async def _packages_from_requirements_line(
237
252
sub_req = Path (sub_path )
238
253
else :
239
254
sub_req = req_path .parent / sub_path
240
- sub_reqs , _ = await _packages_from_requirements_file (sub_req )
241
- # Use current context's index_url for nested requirements
242
- context .requirements .extend (sub_reqs )
255
+ # Create a new context for the nested file to maintain its own index URLs.
256
+ nested_context = RequirementsContext ()
257
+ nested_context .index_urls = context .index_urls [
258
+ :
259
+ ] # i nherit parent's index URLs
260
+ await _packages_from_requirements_file (sub_req , nested_context )
261
+ # Extend our requirements with the nested ones
262
+ context .requirements .extend (nested_context .requirements )
243
263
return
244
264
245
265
# Check for index URL specification
246
266
index_match = re .match (INDEX_URL_PREFIX , req )
247
267
if index_match :
248
- context .index_url = index_match [2 ].strip ()
268
+ context .add_index_url ( index_match [2 ].strip () )
249
269
return
250
270
251
271
if req .startswith ("-" ):
0 commit comments