1 from pip._vendor.packaging.specifiers import SpecifierSet
2 from pip._vendor.packaging.utils import NormalizedName, canonicalize_name
4 from pip._internal.req.req_install import InstallRequirement
6 from .base import Candidate, CandidateLookup, Requirement, format_name
9 class ExplicitRequirement(Requirement):
10 def __init__(self, candidate: Candidate) -> None:
11 self.candidate = candidate
13 def __str__(self) -> str:
14 return str(self.candidate)
16 def __repr__(self) -> str:
17 return "{class_name}({candidate!r})".format(
18 class_name=self.__class__.__name__,
19 candidate=self.candidate,
23 def project_name(self) -> NormalizedName:
24 # No need to canonicalize - the candidate did this
25 return self.candidate.project_name
28 def name(self) -> str:
29 # No need to canonicalize - the candidate did this
30 return self.candidate.name
32 def format_for_error(self) -> str:
33 return self.candidate.format_for_error()
35 def get_candidate_lookup(self) -> CandidateLookup:
36 return self.candidate, None
38 def is_satisfied_by(self, candidate: Candidate) -> bool:
39 return candidate == self.candidate
42 class SpecifierRequirement(Requirement):
43 def __init__(self, ireq: InstallRequirement) -> None:
44 assert ireq.link is None, "This is a link, not a specifier"
46 self._extras = frozenset(ireq.extras)
48 def __str__(self) -> str:
49 return str(self._ireq.req)
51 def __repr__(self) -> str:
52 return "{class_name}({requirement!r})".format(
53 class_name=self.__class__.__name__,
54 requirement=str(self._ireq.req),
58 def project_name(self) -> NormalizedName:
59 assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
60 return canonicalize_name(self._ireq.req.name)
63 def name(self) -> str:
64 return format_name(self.project_name, self._extras)
66 def format_for_error(self) -> str:
68 # Convert comma-separated specifiers into "A, B, ..., F and G"
69 # This makes the specifier a bit more "human readable", without
70 # risking a change in meaning. (Hopefully! Not all edge cases have
72 parts = [s.strip() for s in str(self).split(",")]
78 return ", ".join(parts[:-1]) + " and " + parts[-1]
80 def get_candidate_lookup(self) -> CandidateLookup:
81 return None, self._ireq
83 def is_satisfied_by(self, candidate: Candidate) -> bool:
84 assert candidate.name == self.name, (
85 f"Internal issue: Candidate is not for this requirement "
86 f"{candidate.name} vs {self.name}"
88 # We can safely always allow prereleases here since PackageFinder
89 # already implements the prerelease logic, and would have filtered out
90 # prerelease candidates if the user does not expect them.
91 assert self._ireq.req, "Specifier-backed ireq is always PEP 508"
92 spec = self._ireq.req.specifier
93 return spec.contains(candidate.version, prereleases=True)
96 class RequiresPythonRequirement(Requirement):
97 """A requirement representing Requires-Python metadata."""
99 def __init__(self, specifier: SpecifierSet, match: Candidate) -> None:
100 self.specifier = specifier
101 self._candidate = match
103 def __str__(self) -> str:
104 return f"Python {self.specifier}"
106 def __repr__(self) -> str:
107 return "{class_name}({specifier!r})".format(
108 class_name=self.__class__.__name__,
109 specifier=str(self.specifier),
113 def project_name(self) -> NormalizedName:
114 return self._candidate.project_name
117 def name(self) -> str:
118 return self._candidate.name
120 def format_for_error(self) -> str:
123 def get_candidate_lookup(self) -> CandidateLookup:
124 if self.specifier.contains(self._candidate.version, prereleases=True):
125 return self._candidate, None
128 def is_satisfied_by(self, candidate: Candidate) -> bool:
129 assert candidate.name == self._candidate.name, "Not Python candidate"
130 # We can safely always allow prereleases here since PackageFinder
131 # already implements the prerelease logic, and would have filtered out
132 # prerelease candidates if the user does not expect them.
133 return self.specifier.contains(candidate.version, prereleases=True)
136 class UnsatisfiableRequirement(Requirement):
137 """A requirement that cannot be satisfied."""
139 def __init__(self, name: NormalizedName) -> None:
142 def __str__(self) -> str:
143 return f"{self._name} (unavailable)"
145 def __repr__(self) -> str:
146 return "{class_name}({name!r})".format(
147 class_name=self.__class__.__name__,
148 name=str(self._name),
152 def project_name(self) -> NormalizedName:
156 def name(self) -> str:
159 def format_for_error(self) -> str:
162 def get_candidate_lookup(self) -> CandidateLookup:
165 def is_satisfied_by(self, candidate: Candidate) -> bool: