Skip to content
This repository has been archived by the owner on Apr 27, 2022. It is now read-only.

Commit

Permalink
Attempt to implement logic required for pypa#8253
Browse files Browse the repository at this point in the history
  • Loading branch information
mwchase committed Feb 26, 2021
1 parent cc948a6 commit d9aef7a
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 9 deletions.
4 changes: 2 additions & 2 deletions src/pip/_internal/req/req_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -840,8 +840,8 @@ def check_invalid_constraint_type(req):
problem = ""
if not req.name:
problem = "Unnamed requirements are not allowed as constraints"
elif req.link:
problem = "Links are not allowed as constraints"
elif req.editable:
problem = "Editable requirements are not allowed as constraints"
elif req.extras:
problem = "Constraints cannot have extras"

Expand Down
40 changes: 33 additions & 7 deletions src/pip/_internal/resolution/resolvelib/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@

from pip._internal.models.link import Link
from pip._internal.req.req_install import InstallRequirement
from pip._internal.utils.direct_url_helpers import (
direct_url_from_link,
dist_get_direct_url,
)
from pip._internal.utils.hashes import Hashes

CandidateLookup = Tuple[Optional["Candidate"], Optional[InstallRequirement]]
Expand All @@ -20,24 +24,26 @@ def format_name(project, extras):


class Constraint:
def __init__(self, specifier, hashes):
# type: (SpecifierSet, Hashes) -> None
def __init__(self, specifier, hashes, links):
# type: (SpecifierSet, Hashes, Tuple[Link, ...]) -> None
self.specifier = specifier
self.hashes = hashes
self.links = links

@classmethod
def empty(cls):
# type: () -> Constraint
return Constraint(SpecifierSet(), Hashes())
return Constraint(SpecifierSet(), Hashes(), ())

@classmethod
def from_ireq(cls, ireq):
# type: (InstallRequirement) -> Constraint
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False))
links = (ireq.link,) if ireq.link else ()
return Constraint(ireq.specifier, ireq.hashes(trust_internet=False), links)

def __nonzero__(self):
# type: () -> bool
return bool(self.specifier) or bool(self.hashes)
return bool(self.specifier) or bool(self.hashes) or bool(self.links)

def __bool__(self):
# type: () -> bool
Expand All @@ -49,14 +55,19 @@ def __and__(self, other):
return NotImplemented
specifier = self.specifier & other.specifier
hashes = self.hashes & other.hashes(trust_internet=False)
return Constraint(specifier, hashes)
links = self.links
if other.link and other.link not in links:
links += (other.link,)
return Constraint(specifier, hashes, links)

def is_satisfied_by(self, candidate):
# type: (Candidate) -> bool
# We can safely always allow prereleases here since PackageFinder
# already implements the prerelease logic, and would have filtered out
# prerelease candidates if the user does not expect them.
return self.specifier.contains(candidate.version, prereleases=True)
return self.specifier.contains(candidate.version, prereleases=True) and all(
_match_link(link, candidate) for link in self.links
)


class Requirement:
Expand Down Expand Up @@ -94,6 +105,21 @@ def format_for_error(self):
raise NotImplementedError("Subclass should override")


def _match_link(link, candidate):
# type: (Link, Candidate) -> bool
if candidate.source_link:
return link == candidate.source_link
# The purpose of this is to support AlreadyInstalledCandidates.
# It doesn't handle ExtrasCandidates, which *shouldn't* be necessary.
dist = getattr(candidate, "dist", None)
if dist:
dist_url = dist_get_direct_url(dist)
if dist_url:
# This is missing some optional arguments that *hopefully* aren't needed.
return direct_url_from_link(link).to_dict() == dist_url.to_dict()
return False


class Candidate:
@property
def project_name(self):
Expand Down
16 changes: 16 additions & 0 deletions src/pip/_internal/resolution/resolvelib/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,22 @@ def find_candidates(
if ireq is not None:
ireqs.append(ireq)

for link in constraint.links:
# This part only has to convert an ireq from the requirements to a link.
# If there's only links, either it satisfies, or it doesn't.
if not ireqs:
break
ireq = 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 not None:
explicit_candidates.add(candidate)

# If none of the requirements want an explicit candidate, we can ask
# the finder for candidates.
if not explicit_candidates:
Expand Down

0 comments on commit d9aef7a

Please sign in to comment.