1from pip._vendor.packaging.utils import canonicalize_name
2
3from pip._internal.utils.typing import MYPY_CHECK_RUNNING
4
5from .base import Requirement, format_name
6
7if MYPY_CHECK_RUNNING:
8    from pip._vendor.packaging.specifiers import SpecifierSet
9
10    from pip._internal.req.req_install import InstallRequirement
11
12    from .base import Candidate, CandidateLookup
13
14
15class ExplicitRequirement(Requirement):
16    def __init__(self, candidate):
17        # type: (Candidate) -> None
18        self.candidate = candidate
19
20    def __str__(self):
21        # type: () -> str
22        return str(self.candidate)
23
24    def __repr__(self):
25        # type: () -> str
26        return "{class_name}({candidate!r})".format(
27            class_name=self.__class__.__name__,
28            candidate=self.candidate,
29        )
30
31    @property
32    def project_name(self):
33        # type: () -> str
34        # No need to canonicalise - the candidate did this
35        return self.candidate.project_name
36
37    @property
38    def name(self):
39        # type: () -> str
40        # No need to canonicalise - the candidate did this
41        return self.candidate.name
42
43    def format_for_error(self):
44        # type: () -> str
45        return self.candidate.format_for_error()
46
47    def get_candidate_lookup(self):
48        # type: () -> CandidateLookup
49        return self.candidate, None
50
51    def is_satisfied_by(self, candidate):
52        # type: (Candidate) -> bool
53        return candidate == self.candidate
54
55
56class SpecifierRequirement(Requirement):
57    def __init__(self, ireq):
58        # type: (InstallRequirement) -> None
59        assert ireq.link is None, "This is a link, not a specifier"
60        self._ireq = ireq
61        self._extras = frozenset(ireq.extras)
62
63    def __str__(self):
64        # type: () -> str
65        return str(self._ireq.req)
66
67    def __repr__(self):
68        # type: () -> str
69        return "{class_name}({requirement!r})".format(
70            class_name=self.__class__.__name__,
71            requirement=str(self._ireq.req),
72        )
73
74    @property
75    def project_name(self):
76        # type: () -> str
77        return canonicalize_name(self._ireq.req.name)
78
79    @property
80    def name(self):
81        # type: () -> str
82        return format_name(self.project_name, self._extras)
83
84    def format_for_error(self):
85        # type: () -> str
86
87        # Convert comma-separated specifiers into "A, B, ..., F and G"
88        # This makes the specifier a bit more "human readable", without
89        # risking a change in meaning. (Hopefully! Not all edge cases have
90        # been checked)
91        parts = [s.strip() for s in str(self).split(",")]
92        if len(parts) == 0:
93            return ""
94        elif len(parts) == 1:
95            return parts[0]
96
97        return ", ".join(parts[:-1]) + " and " + parts[-1]
98
99    def get_candidate_lookup(self):
100        # type: () -> CandidateLookup
101        return None, self._ireq
102
103    def is_satisfied_by(self, candidate):
104        # type: (Candidate) -> bool
105        assert candidate.name == self.name, \
106            "Internal issue: Candidate is not for this requirement " \
107            " {} vs {}".format(candidate.name, self.name)
108        # We can safely always allow prereleases here since PackageFinder
109        # already implements the prerelease logic, and would have filtered out
110        # prerelease candidates if the user does not expect them.
111        spec = self._ireq.req.specifier
112        return spec.contains(candidate.version, prereleases=True)
113
114
115class RequiresPythonRequirement(Requirement):
116    """A requirement representing Requires-Python metadata.
117    """
118    def __init__(self, specifier, match):
119        # type: (SpecifierSet, Candidate) -> None
120        self.specifier = specifier
121        self._candidate = match
122
123    def __str__(self):
124        # type: () -> str
125        return f"Python {self.specifier}"
126
127    def __repr__(self):
128        # type: () -> str
129        return "{class_name}({specifier!r})".format(
130            class_name=self.__class__.__name__,
131            specifier=str(self.specifier),
132        )
133
134    @property
135    def project_name(self):
136        # type: () -> str
137        return self._candidate.project_name
138
139    @property
140    def name(self):
141        # type: () -> str
142        return self._candidate.name
143
144    def format_for_error(self):
145        # type: () -> str
146        return str(self)
147
148    def get_candidate_lookup(self):
149        # type: () -> CandidateLookup
150        if self.specifier.contains(self._candidate.version, prereleases=True):
151            return self._candidate, None
152        return None, None
153
154    def is_satisfied_by(self, candidate):
155        # type: (Candidate) -> bool
156        assert candidate.name == self._candidate.name, "Not Python candidate"
157        # We can safely always allow prereleases here since PackageFinder
158        # already implements the prerelease logic, and would have filtered out
159        # prerelease candidates if the user does not expect them.
160        return self.specifier.contains(candidate.version, prereleases=True)
161
162
163class UnsatisfiableRequirement(Requirement):
164    """A requirement that cannot be satisfied.
165    """
166    def __init__(self, name):
167        # type: (str) -> None
168        self._name = name
169
170    def __str__(self):
171        # type: () -> str
172        return "{} (unavailable)".format(self._name)
173
174    def __repr__(self):
175        # type: () -> str
176        return "{class_name}({name!r})".format(
177            class_name=self.__class__.__name__,
178            name=str(self._name),
179        )
180
181    @property
182    def project_name(self):
183        # type: () -> str
184        return self._name
185
186    @property
187    def name(self):
188        # type: () -> str
189        return self._name
190
191    def format_for_error(self):
192        # type: () -> str
193        return str(self)
194
195    def get_candidate_lookup(self):
196        # type: () -> CandidateLookup
197        return None, None
198
199    def is_satisfied_by(self, candidate):
200        # type: (Candidate) -> bool
201        return False
202