1+ import contextlib
12import functools
23import logging
34from typing import (
@@ -126,6 +127,15 @@ def force_reinstall(self):
126127 # type: () -> bool
127128 return self ._force_reinstall
128129
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+
129139 def _make_extras_candidate (self , base , extras ):
130140 # type: (BaseCandidate, FrozenSet[str]) -> ExtrasCandidate
131141 cache_key = (id (base ), extras )
@@ -278,6 +288,51 @@ def iter_index_candidate_infos():
278288 incompatible_ids ,
279289 )
280290
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+
281336 def find_candidates (
282337 self ,
283338 identifier : str ,
@@ -291,76 +346,44 @@ def find_candidates(
291346 # can be made quicker by comparing only the id() values.
292347 incompat_ids = {id (c ) for c in incompatibilities .get (identifier , ())}
293348
349+ # Collect basic lookup information from the requirements.
294350 explicit_candidates = set () # type: Set[Candidate]
295351 ireqs = [] # type: List[InstallRequirement]
296352 for req in requirements [identifier ]:
297353 cand , ireq = req .get_candidate_lookup ()
298- if cand is not None and id (cand ) not in incompat_ids :
354+ if cand is not None and id (cand ):
299355 explicit_candidates .add (cand )
300356 if ireq is not None :
301357 ireqs .append (ireq )
302358
303- for link in constraint .links :
304- if not ireqs :
305- # If we hit this condition, then we cannot construct a candidate.
306- # However, if we hit this condition, then none of the requirements
307- # provided an ireq, so they must have provided an explicit candidate.
308- # In that case, either the candidate matches, in which case this loop
309- # doesn't need to do anything, or it doesn't, in which case there's
310- # nothing this loop can do to recover.
311- break
312- if link .is_wheel :
313- wheel = Wheel (link .filename )
314- # Check whether the provided wheel is compatible with the target
315- # platform.
316- if not wheel .supported (self ._finder .target_python .get_tags ()):
317- # We are constrained to install a wheel that is incompatible with
318- # the target architecture, so there are no valid candidates.
319- # Return early, with no candidates.
320- return ()
321- # Create a "fake" InstallRequirement that's basically a clone of
322- # what "should" be the template, but with original_link set to link.
323- # Using the given requirement is necessary for preserving hash
324- # requirements, but without the original_link, direct_url.json
325- # won't be created.
326- ireq = install_req_from_link_and_ireq (link , ireqs [0 ])
327- candidate = self ._make_candidate_from_link (
328- link ,
329- extras = frozenset (),
330- template = ireq ,
331- name = canonicalize_name (ireq .name ) if ireq .name else None ,
332- version = None ,
359+ # If the current identifier contains extras, add explicit candidates
360+ # from entries from extra-less identifier.
361+ with contextlib .suppress (InvalidRequirement ):
362+ parsed_requirement = PackagingRequirement (identifier )
363+ explicit_candidates .update (
364+ self ._iter_explicit_candidates_from_base (
365+ requirements .get (parsed_requirement .name , ()),
366+ frozenset (parsed_requirement .extras ),
367+ ),
333368 )
334- if candidate is None :
335- # _make_candidate_from_link returns None if the wheel fails to build.
336- # We are constrained to install this wheel, so there are no valid
337- # candidates.
338- # Return early, with no candidates.
339- return ()
340-
341- explicit_candidates .add (candidate )
342369
343- # If the current identifier contains extras, also add explicit
344- # candidates from entries from extra-less identifier.
345- try :
346- identifier_req = PackagingRequirement (identifier )
347- except InvalidRequirement :
348- base_identifier = None
349- extras : FrozenSet [str ] = frozenset ()
350- else :
351- base_identifier = identifier_req .name
352- extras = frozenset (identifier_req .extras )
353- if base_identifier and base_identifier in requirements :
354- for req in requirements [base_identifier ]:
355- lookup_cand , _ = req .get_candidate_lookup ()
356- if lookup_cand is None : # Not explicit.
357- continue
358- # We've stripped extras from the identifier, and should always
359- # get a BaseCandidate here, unless there's a bug elsewhere.
360- base_cand = as_base_candidate (lookup_cand )
361- assert base_cand is not None
362- candidate = self ._make_extras_candidate (base_cand , extras )
363- explicit_candidates .add (candidate )
370+ # Add explicit candidates from constraints. We only do this if there are
371+ # kown ireqs, which represent requirements not already explicit. If
372+ # there are no ireqs, we're constraining already-explicit requirements,
373+ # which is handled later when we return the explicit candidates.
374+ if ireqs :
375+ try :
376+ constraint_cands = self ._iter_candidates_from_constraints (
377+ identifier ,
378+ constraint ,
379+ template = ireqs [0 ],
380+ )
381+ except UnsupportedWheel :
382+ # If we're constrained to install a wheel incompatible with the
383+ # target architecture, no candidates will ever be valid.
384+ return ()
385+ else :
386+ explicit_candidates .update (constraint_cands )
364387
365388 # If none of the requirements want an explicit candidate, we can ask
366389 # the finder for candidates.
@@ -376,7 +399,8 @@ def find_candidates(
376399 return (
377400 c
378401 for c in explicit_candidates
379- if constraint .is_satisfied_by (c )
402+ if id (c ) not in incompat_ids
403+ and constraint .is_satisfied_by (c )
380404 and all (req .is_satisfied_by (c ) for req in requirements [identifier ])
381405 )
382406
@@ -391,13 +415,7 @@ def make_requirement_from_install_req(self, ireq, requested_extras):
391415 return None
392416 if not ireq .link :
393417 return SpecifierRequirement (ireq )
394- if ireq .link .is_wheel :
395- wheel = Wheel (ireq .link .filename )
396- if not wheel .supported (self ._finder .target_python .get_tags ()):
397- msg = "{} is not a supported wheel on this platform." .format (
398- wheel .filename ,
399- )
400- raise UnsupportedWheel (msg )
418+ self ._fail_if_link_is_unsupported_wheel (ireq .link )
401419 cand = self ._make_candidate_from_link (
402420 ireq .link ,
403421 extras = frozenset (ireq .extras ),
0 commit comments