1
+ import contextlib
1
2
import functools
2
3
import logging
3
4
from typing import (
16
17
cast ,
17
18
)
18
19
20
+ from pip ._vendor .packaging .requirements import InvalidRequirement
21
+ from pip ._vendor .packaging .requirements import Requirement as PackagingRequirement
19
22
from pip ._vendor .packaging .specifiers import SpecifierSet
20
23
from pip ._vendor .packaging .utils import NormalizedName , canonicalize_name
21
24
from pip ._vendor .pkg_resources import Distribution
54
57
ExtrasCandidate ,
55
58
LinkCandidate ,
56
59
RequiresPythonCandidate ,
60
+ as_base_candidate ,
57
61
)
58
62
from .found_candidates import FoundCandidates , IndexCandidateInfo
59
63
from .requirements import (
@@ -123,6 +127,15 @@ def force_reinstall(self):
123
127
# type: () -> bool
124
128
return self ._force_reinstall
125
129
130
+ def _fail_if_link_is_unsupported_wheel (self , link : Link ) -> None :
131
+ if not link .is_wheel :
132
+ return
133
+ wheel = Wheel (link .filename )
134
+ if wheel .supported (self ._finder .target_python .get_tags ()):
135
+ return
136
+ msg = f"{ link .filename } is not a supported wheel on this platform."
137
+ raise UnsupportedWheel (msg )
138
+
126
139
def _make_extras_candidate (self , base , extras ):
127
140
# type: (BaseCandidate, FrozenSet[str]) -> ExtrasCandidate
128
141
cache_key = (id (base ), extras )
@@ -275,6 +288,51 @@ def iter_index_candidate_infos():
275
288
incompatible_ids ,
276
289
)
277
290
291
+ def _iter_explicit_candidates_from_base (
292
+ self ,
293
+ base_requirements : Iterable [Requirement ],
294
+ extras : FrozenSet [str ],
295
+ ) -> Iterator [Candidate ]:
296
+ """Produce explicit candidates from the base given an extra-ed package.
297
+
298
+ :param base_requirements: Requirements known to the resolver. The
299
+ requirements are guaranteed to not have extras.
300
+ :param extras: The extras to inject into the explicit requirements'
301
+ candidates.
302
+ """
303
+ for req in base_requirements :
304
+ lookup_cand , _ = req .get_candidate_lookup ()
305
+ if lookup_cand is None : # Not explicit.
306
+ continue
307
+ # We've stripped extras from the identifier, and should always
308
+ # get a BaseCandidate here, unless there's a bug elsewhere.
309
+ base_cand = as_base_candidate (lookup_cand )
310
+ assert base_cand is not None , "no extras here"
311
+ yield self ._make_extras_candidate (base_cand , extras )
312
+
313
+ def _iter_candidates_from_constraints (
314
+ self ,
315
+ identifier : str ,
316
+ constraint : Constraint ,
317
+ template : InstallRequirement ,
318
+ ) -> Iterator [Candidate ]:
319
+ """Produce explicit candidates from constraints.
320
+
321
+ This creates "fake" InstallRequirement objects that are basically clones
322
+ of what "should" be the template, but with original_link set to link.
323
+ """
324
+ for link in constraint .links :
325
+ self ._fail_if_link_is_unsupported_wheel (link )
326
+ candidate = self ._make_candidate_from_link (
327
+ link ,
328
+ extras = frozenset (),
329
+ template = install_req_from_link_and_ireq (link , template ),
330
+ name = canonicalize_name (identifier ),
331
+ version = None ,
332
+ )
333
+ if candidate :
334
+ yield candidate
335
+
278
336
def find_candidates (
279
337
self ,
280
338
identifier : str ,
@@ -283,59 +341,48 @@ def find_candidates(
283
341
constraint : Constraint ,
284
342
prefers_installed : bool ,
285
343
) -> Iterable [Candidate ]:
286
-
287
- # Since we cache all the candidates, incompatibility identification
288
- # can be made quicker by comparing only the id() values.
289
- incompat_ids = {id (c ) for c in incompatibilities .get (identifier , ())}
290
-
344
+ # Collect basic lookup information from the requirements.
291
345
explicit_candidates = set () # type: Set[Candidate]
292
346
ireqs = [] # type: List[InstallRequirement]
293
347
for req in requirements [identifier ]:
294
348
cand , ireq = req .get_candidate_lookup ()
295
- if cand is not None and id ( cand ) not in incompat_ids :
349
+ if cand is not None :
296
350
explicit_candidates .add (cand )
297
351
if ireq is not None :
298
352
ireqs .append (ireq )
299
353
300
- for link in constraint .links :
301
- if not ireqs :
302
- # If we hit this condition, then we cannot construct a candidate.
303
- # However, if we hit this condition, then none of the requirements
304
- # provided an ireq, so they must have provided an explicit candidate.
305
- # In that case, either the candidate matches, in which case this loop
306
- # doesn't need to do anything, or it doesn't, in which case there's
307
- # nothing this loop can do to recover.
308
- break
309
- if link .is_wheel :
310
- wheel = Wheel (link .filename )
311
- # Check whether the provided wheel is compatible with the target
312
- # platform.
313
- if not wheel .supported (self ._finder .target_python .get_tags ()):
314
- # We are constrained to install a wheel that is incompatible with
315
- # the target architecture, so there are no valid candidates.
316
- # Return early, with no candidates.
317
- return ()
318
- # Create a "fake" InstallRequirement that's basically a clone of
319
- # what "should" be the template, but with original_link set to link.
320
- # Using the given requirement is necessary for preserving hash
321
- # requirements, but without the original_link, direct_url.json
322
- # won't be created.
323
- ireq = install_req_from_link_and_ireq (link , ireqs [0 ])
324
- candidate = self ._make_candidate_from_link (
325
- link ,
326
- extras = frozenset (),
327
- template = ireq ,
328
- name = canonicalize_name (ireq .name ) if ireq .name else None ,
329
- version = None ,
354
+ # If the current identifier contains extras, add explicit candidates
355
+ # from entries from extra-less identifier.
356
+ with contextlib .suppress (InvalidRequirement ):
357
+ parsed_requirement = PackagingRequirement (identifier )
358
+ explicit_candidates .update (
359
+ self ._iter_explicit_candidates_from_base (
360
+ requirements .get (parsed_requirement .name , ()),
361
+ frozenset (parsed_requirement .extras ),
362
+ ),
330
363
)
331
- if candidate is None :
332
- # _make_candidate_from_link returns None if the wheel fails to build.
333
- # We are constrained to install this wheel, so there are no valid
334
- # candidates.
335
- # Return early, with no candidates.
364
+
365
+ # Add explicit candidates from constraints. We only do this if there are
366
+ # kown ireqs, which represent requirements not already explicit. If
367
+ # there are no ireqs, we're constraining already-explicit requirements,
368
+ # which is handled later when we return the explicit candidates.
369
+ if ireqs :
370
+ try :
371
+ explicit_candidates .update (
372
+ self ._iter_candidates_from_constraints (
373
+ identifier ,
374
+ constraint ,
375
+ template = ireqs [0 ],
376
+ ),
377
+ )
378
+ except UnsupportedWheel :
379
+ # If we're constrained to install a wheel incompatible with the
380
+ # target architecture, no candidates will ever be valid.
336
381
return ()
337
382
338
- explicit_candidates .add (candidate )
383
+ # Since we cache all the candidates, incompatibility identification
384
+ # can be made quicker by comparing only the id() values.
385
+ incompat_ids = {id (c ) for c in incompatibilities .get (identifier , ())}
339
386
340
387
# If none of the requirements want an explicit candidate, we can ask
341
388
# the finder for candidates.
@@ -351,7 +398,8 @@ def find_candidates(
351
398
return (
352
399
c
353
400
for c in explicit_candidates
354
- if constraint .is_satisfied_by (c )
401
+ if id (c ) not in incompat_ids
402
+ and constraint .is_satisfied_by (c )
355
403
and all (req .is_satisfied_by (c ) for req in requirements [identifier ])
356
404
)
357
405
@@ -366,13 +414,7 @@ def make_requirement_from_install_req(self, ireq, requested_extras):
366
414
return None
367
415
if not ireq .link :
368
416
return SpecifierRequirement (ireq )
369
- if ireq .link .is_wheel :
370
- wheel = Wheel (ireq .link .filename )
371
- if not wheel .supported (self ._finder .target_python .get_tags ()):
372
- msg = "{} is not a supported wheel on this platform." .format (
373
- wheel .filename ,
374
- )
375
- raise UnsupportedWheel (msg )
417
+ self ._fail_if_link_is_unsupported_wheel (ireq .link )
376
418
cand = self ._make_candidate_from_link (
377
419
ireq .link ,
378
420
extras = frozenset (ireq .extras ),
0 commit comments