Skip to content

Commit

Permalink
Refactor Factory.find_candidates() for readability
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Apr 19, 2021
1 parent 0305e0d commit 8c2d448
Showing 1 changed file with 99 additions and 67 deletions.
166 changes: 99 additions & 67 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ def force_reinstall(self):
# type: () -> bool
return self._force_reinstall

def _fail_if_link_is_unsupported_wheel(self, link: Link) -> None:
if not link.is_wheel:
return
wheel = Wheel(link.filename)
if wheel.supported(self._finder.target_python.get_tags()):
return
msg = f"{link.filename} is not a supported wheel on this platform."
raise UnsupportedWheel(msg)

def _make_extras_candidate(self, base, extras):
# type: (BaseCandidate, FrozenSet[str]) -> ExtrasCandidate
cache_key = (id(base), extras)
Expand Down Expand Up @@ -278,6 +287,62 @@ def iter_index_candidate_infos():
incompatible_ids,
)

def _iter_explicit_candidates_from_base(
self,
identifier: str,
requirements: Mapping[str, Iterator[Requirement]],
) -> Iterator[Candidate]:
"""Produce explicit candidates from the base given an extra-ed package.
:param identifier: A dependency identifier in the resolver. If this
points to an extra-ed package (e.g. ``package[foo]``), explcit
candidates known for the base package (identified by ``package``)
are returned. No candidates are generated if this does not point to
an extra-ed Python package.
:param requirements: A mapping of requirements known to the resolver,
as passed in by ``find_matches()``.
"""
try:
pkg_req = PackagingRequirement(identifier)
except InvalidRequirement:
return
base_identifier = pkg_req.name
if base_identifier not in requirements:
return
extras = frozenset(pkg_req.extras)
for req in requirements[base_identifier]:
lookup_cand, _ = req.get_candidate_lookup()
if lookup_cand is None: # Not explicit.
continue
# We've stripped extras from the identifier, and should always
# get a BaseCandidate here, unless there's a bug elsewhere.
base_cand = as_base_candidate(lookup_cand)
assert base_cand is not None
yield self._make_extras_candidate(base_cand, extras)

def _iter_explicit_candidates_from_constraints(
self,
identifier: str,
constraint: Constraint,
template: InstallRequirement,
) -> Iterator[Candidate]:
"""Produce explicit candidates from constraints.
This creates "fake" InstallRequirement objects that are basically clones
of what "should" be the template, but with original_link set to link.
"""
for link in constraint.links:
# If we're constrained to install a wheel incompatible with the
# target architecture, no candidates will ever be valid.
self._fail_if_link_is_unsupported_wheel(link)
yield self._make_candidate_from_link(
link,
extras=frozenset(),
template=install_req_from_link_and_ireq(link, template),
name=identifier,
version=None,
)

def find_candidates(
self,
identifier: str,
Expand All @@ -291,76 +356,49 @@ def find_candidates(
# can be made quicker by comparing only the id() values.
incompat_ids = {id(c) for c in incompatibilities.get(identifier, ())}

# Collect basic lookup information from the requirements.
explicit_candidates = set() # type: Set[Candidate]
ireqs = [] # type: List[InstallRequirement]
for req in requirements[identifier]:
cand, ireq = req.get_candidate_lookup()
if cand is not None and id(cand) not in incompat_ids:
if cand is not None and id(cand):
explicit_candidates.add(cand)
if ireq is not None:
ireqs.append(ireq)

for link in constraint.links:
if not ireqs:
# If we hit this condition, then we cannot construct a candidate.
# However, if we hit this condition, then none of the requirements
# provided an ireq, so they must have provided an explicit candidate.
# In that case, either the candidate matches, in which case this loop
# doesn't need to do anything, or it doesn't, in which case there's
# nothing this loop can do to recover.
break
if link.is_wheel:
wheel = Wheel(link.filename)
# Check whether the provided wheel is compatible with the target
# platform.
if not wheel.supported(self._finder.target_python.get_tags()):
# We are constrained to install a wheel that is incompatible with
# the target architecture, so there are no valid candidates.
# Return early, with no candidates.
return ()
# Create a "fake" InstallRequirement that's basically a clone of
# what "should" be the template, but with original_link set to link.
# Using the given requirement is necessary for preserving hash
# requirements, but without the original_link, direct_url.json
# won't be created.
ireq = install_req_from_link_and_ireq(link, ireqs[0])
candidate = self._make_candidate_from_link(
link,
extras=frozenset(),
template=ireq,
name=canonicalize_name(ireq.name) if ireq.name else None,
version=None,
)
if candidate is None:
# _make_candidate_from_link returns None if the wheel fails to build.
# We are constrained to install this wheel, so there are no valid
# candidates.
# Return early, with no candidates.
return ()

explicit_candidates.add(candidate)
# If the current identifier contains extras, add explicit candidates
# from entries from extra-less identifier.
explicit_candidates = explicit_candidates.union(
self._iter_explicit_candidates_from_base(identifier, requirements),
)

# If the current identifier contains extras, also add explicit
# candidates from entries from extra-less identifier.
try:
identifier_req = PackagingRequirement(identifier)
except InvalidRequirement:
base_identifier = None
extras: FrozenSet[str] = frozenset()
# Add explicit candidates from constraints. We only do this if there are
# kown ireqs, which represent requirements not already explicit. If
# there are no ireqs, we're constraining already-explicit requirements,
# which is covered by the else block.
if ireqs:
try:
explicit_candidates.update(
self._iter_explicit_candidates_from_constraints(
identifier,
constraint,
template=ireqs[0],
),
)
except UnsupportedWheel:
return ()
else:
base_identifier = identifier_req.name
extras = frozenset(identifier_req.extras)
if base_identifier and base_identifier in requirements:
for req in requirements[base_identifier]:
lookup_cand, _ = req.get_candidate_lookup()
if lookup_cand is None: # Not explicit.
continue
# We've stripped extras from the identifier, and should always
# get a BaseCandidate here, unless there's a bug elsewhere.
base_cand = as_base_candidate(lookup_cand)
assert base_cand is not None
candidate = self._make_extras_candidate(base_cand, extras)
explicit_candidates.add(candidate)
explicit_candidates = {
candidate
for candidate in explicit_candidates
if constraint.is_satisfied_by(candidate)
}

explicit_candidates = {
candidate
for candidate in explicit_candidates
if id(candidate) not in incompat_ids
}

# If none of the requirements want an explicit candidate, we can ask
# the finder for candidates.
Expand Down Expand Up @@ -391,13 +429,7 @@ def make_requirement_from_install_req(self, ireq, requested_extras):
return None
if not ireq.link:
return SpecifierRequirement(ireq)
if ireq.link.is_wheel:
wheel = Wheel(ireq.link.filename)
if not wheel.supported(self._finder.target_python.get_tags()):
msg = "{} is not a supported wheel on this platform.".format(
wheel.filename,
)
raise UnsupportedWheel(msg)
self._fail_if_link_is_unsupported_wheel(ireq.link)
cand = self._make_candidate_from_link(
ireq.link,
extras=frozenset(ireq.extras),
Expand Down

0 comments on commit 8c2d448

Please sign in to comment.