36
36
from pip ._internal .models .link import Link
37
37
from pip ._internal .models .wheel import Wheel
38
38
from pip ._internal .operations .prepare import RequirementPreparer
39
- from pip ._internal .req .constructors import install_req_from_link_and_ireq
39
+ from pip ._internal .req .constructors import (
40
+ install_req_drop_extras ,
41
+ install_req_from_link_and_ireq ,
42
+ )
40
43
from pip ._internal .req .req_install import (
41
44
InstallRequirement ,
42
45
check_invalid_constraint_type ,
@@ -176,6 +179,20 @@ def _make_candidate_from_link(
176
179
name : Optional [NormalizedName ],
177
180
version : Optional [CandidateVersion ],
178
181
) -> Optional [Candidate ]:
182
+ base : Optional [BaseCandidate ] = self ._make_base_candidate_from_link (
183
+ link , template , name , version
184
+ )
185
+ if not extras or base is None :
186
+ return base
187
+ return self ._make_extras_candidate (base , extras , comes_from = template )
188
+
189
+ def _make_base_candidate_from_link (
190
+ self ,
191
+ link : Link ,
192
+ template : InstallRequirement ,
193
+ name : Optional [NormalizedName ],
194
+ version : Optional [CandidateVersion ],
195
+ ) -> Optional [BaseCandidate ]:
179
196
# TODO: Check already installed candidate, and use it if the link and
180
197
# editable flag match.
181
198
@@ -204,7 +221,7 @@ def _make_candidate_from_link(
204
221
self ._build_failures [link ] = e
205
222
return None
206
223
207
- base : BaseCandidate = self ._editable_candidate_cache [link ]
224
+ return self ._editable_candidate_cache [link ]
208
225
else :
209
226
if link not in self ._link_candidate_cache :
210
227
try :
@@ -224,11 +241,7 @@ def _make_candidate_from_link(
224
241
)
225
242
self ._build_failures [link ] = e
226
243
return None
227
- base = self ._link_candidate_cache [link ]
228
-
229
- if not extras :
230
- return base
231
- return self ._make_extras_candidate (base , extras , comes_from = template )
244
+ return self ._link_candidate_cache [link ]
232
245
233
246
def _iter_found_candidates (
234
247
self ,
@@ -362,9 +375,8 @@ def _iter_candidates_from_constraints(
362
375
"""
363
376
for link in constraint .links :
364
377
self ._fail_if_link_is_unsupported_wheel (link )
365
- candidate = self ._make_candidate_from_link (
378
+ candidate = self ._make_base_candidate_from_link (
366
379
link ,
367
- extras = frozenset (),
368
380
template = install_req_from_link_and_ireq (link , template ),
369
381
name = canonicalize_name (identifier ),
370
382
version = None ,
@@ -454,10 +466,10 @@ def _make_requirements_from_install_req(
454
466
Returns requirement objects associated with the given InstallRequirement. In
455
467
most cases this will be a single object but the following special cases exist:
456
468
- the InstallRequirement has markers that do not apply -> result is empty
457
- - the InstallRequirement has both a constraint and extras -> result is split
458
- in two requirement objects: one with the constraint and one with the
459
- extra. This allows centralized constraint handling for the base,
460
- resulting in fewer candidate rejections.
469
+ - the InstallRequirement has both a constraint (or link) and extras
470
+ -> result is split in two requirement objects: one with the constraint
471
+ (or link) and one with the extra. This allows centralized constraint
472
+ handling for the base, resulting in fewer candidate rejections.
461
473
"""
462
474
if not ireq .match_markers (requested_extras ):
463
475
logger .info (
@@ -471,10 +483,13 @@ def _make_requirements_from_install_req(
471
483
yield SpecifierRequirement (ireq )
472
484
else :
473
485
self ._fail_if_link_is_unsupported_wheel (ireq .link )
474
- cand = self ._make_candidate_from_link (
486
+ # Always make the link candidate for the base requirement to make it
487
+ # available to `find_candidates` for explicit candidate lookup for any
488
+ # set of extras.
489
+ # The extras are required separately via a second requirement.
490
+ cand = self ._make_base_candidate_from_link (
475
491
ireq .link ,
476
- extras = frozenset (ireq .extras ),
477
- template = ireq ,
492
+ template = install_req_drop_extras (ireq ) if ireq .extras else ireq ,
478
493
name = canonicalize_name (ireq .name ) if ireq .name else None ,
479
494
version = None ,
480
495
)
@@ -489,7 +504,13 @@ def _make_requirements_from_install_req(
489
504
raise self ._build_failures [ireq .link ]
490
505
yield UnsatisfiableRequirement (canonicalize_name (ireq .name ))
491
506
else :
507
+ # require the base from the link
492
508
yield self .make_requirement_from_candidate (cand )
509
+ if ireq .extras :
510
+ # require the extras on top of the base candidate
511
+ yield self .make_requirement_from_candidate (
512
+ self ._make_extras_candidate (cand , frozenset (ireq .extras ))
513
+ )
493
514
494
515
def collect_root_requirements (
495
516
self , root_ireqs : List [InstallRequirement ]
0 commit comments