Let us walk on the 3-isogeny graph
Loading...
Searching...
No Matches
provider.py
Go to the documentation of this file.
1import collections
2import math
3from typing import (
4 TYPE_CHECKING,
5 Dict,
6 Iterable,
7 Iterator,
8 Mapping,
9 Sequence,
10 TypeVar,
11 Union,
12)
13
14from pip._vendor.resolvelib.providers import AbstractProvider
15
16from .base import Candidate, Constraint, Requirement
17from .candidates import REQUIRES_PYTHON_IDENTIFIER
18from .factory import Factory
19
20if TYPE_CHECKING:
21 from pip._vendor.resolvelib.providers import Preference
22 from pip._vendor.resolvelib.resolvers import RequirementInformation
23
24 PreferenceInformation = RequirementInformation[Requirement, Candidate]
25
26 _ProviderBase = AbstractProvider[Requirement, Candidate, str]
27else:
28 _ProviderBase = AbstractProvider
29
30# Notes on the relationship between the provider, the factory, and the
31# candidate and requirement classes.
32#
33# The provider is a direct implementation of the resolvelib class. Its role
34# is to deliver the API that resolvelib expects.
35#
36# Rather than work with completely abstract "requirement" and "candidate"
37# concepts as resolvelib does, pip has concrete classes implementing these two
38# ideas. The API of Requirement and Candidate objects are defined in the base
39# classes, but essentially map fairly directly to the equivalent provider
40# methods. In particular, `find_matches` and `is_satisfied_by` are
41# requirement methods, and `get_dependencies` is a candidate method.
42#
43# The factory is the interface to pip's internal mechanisms. It is stateless,
44# and is created by the resolver and held as a property of the provider. It is
45# responsible for creating Requirement and Candidate objects, and provides
46# services to those objects (access to pip's finder and preparer).
47
48
49D = TypeVar("D")
50V = TypeVar("V")
51
52
54 mapping: Mapping[str, V],
55 identifier: str,
56 default: D,
57) -> Union[D, V]:
58 """Get item from a package name lookup mapping with a resolver identifier.
59
60 This extra logic is needed when the target mapping is keyed by package
61 name, which cannot be directly looked up with an identifier (which may
62 contain requested extras). Additional logic is added to also look up a value
63 by "cleaning up" the extras from the identifier.
64 """
65 if identifier in mapping:
66 return mapping[identifier]
67 # HACK: Theoretically we should check whether this identifier is a valid
68 # "NAME[EXTRAS]" format, and parse out the name part with packaging or
69 # some regular expression. But since pip's resolver only spits out three
70 # kinds of identifiers: normalized PEP 503 names, normalized names plus
71 # extras, and Requires-Python, we can cheat a bit here.
72 name, open_bracket, _ = identifier.partition("[")
73 if open_bracket and name in mapping:
74 return mapping[name]
75 return default
76
77
79 """Pip's provider implementation for resolvelib.
80
81 :params constraints: A mapping of constraints specified by the user. Keys
82 are canonicalized project names.
83 :params ignore_dependencies: Whether the user specified ``--no-deps``.
84 :params upgrade_strategy: The user-specified upgrade strategy.
85 :params user_requested: A set of canonicalized package names that the user
86 supplied for pip to install/upgrade.
87 """
88
90 self,
91 factory: Factory,
92 constraints: Dict[str, Constraint],
93 ignore_dependencies: bool,
94 upgrade_strategy: str,
95 user_requested: Dict[str, int],
96 ) -> None:
97 self._factory = factory
98 self._constraints = constraints
99 self._ignore_dependencies = ignore_dependencies
100 self._upgrade_strategy = upgrade_strategy
101 self._user_requested = user_requested
102 self._known_depths: Dict[str, float] = collections.defaultdict(lambda: math.inf)
103
104 def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
106
108 self,
109 identifier: str,
110 resolutions: Mapping[str, Candidate],
111 candidates: Mapping[str, Iterator[Candidate]],
112 information: Mapping[str, Iterable["PreferenceInformation"]],
113 backtrack_causes: Sequence["PreferenceInformation"],
114 ) -> "Preference":
115 """Produce a sort key for given requirement based on preference.
116
117 The lower the return value is, the more preferred this group of
118 arguments is.
119
120 Currently pip considers the following in order:
121
122 * Prefer if any of the known requirements is "direct", e.g. points to an
123 explicit URL.
124 * If equal, prefer if any requirement is "pinned", i.e. contains
125 operator ``===`` or ``==``.
126 * If equal, calculate an approximate "depth" and resolve requirements
127 closer to the user-specified requirements first. If the depth cannot
128 by determined (eg: due to no matching parents), it is considered
129 infinite.
130 * Order user-specified requirements by the order they are specified.
131 * If equal, prefers "non-free" requirements, i.e. contains at least one
132 operator, such as ``>=`` or ``<``.
133 * If equal, order alphabetically for consistency (helps debuggability).
134 """
135 try:
136 next(iter(information[identifier]))
137 except StopIteration:
138 # There is no information for this identifier, so there's no known
139 # candidates.
140 has_information = False
141 else:
142 has_information = True
143
144 if has_information:
145 lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
146 candidate, ireqs = zip(*lookups)
147 else:
148 candidate, ireqs = None, ()
149
150 operators = [
152 for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
153 for specifier in specifier_set
154 ]
155
156 direct = candidate is not None
157 pinned = any(op[:2] == "==" for op in operators)
158 unfree = bool(operators)
159
160 try:
161 requested_order: Union[int, float] = self._user_requested[identifier]
162 except KeyError:
163 requested_order = math.inf
164 if has_information:
165 parent_depths = (
166 self._known_depths[parent.name] if parent is not None else 0.0
167 for _, parent in information[identifier]
168 )
169 inferred_depth = min(d for d in parent_depths) + 1.0
170 else:
171 inferred_depth = math.inf
172 else:
173 inferred_depth = 1.0
174 self._known_depths[identifier] = inferred_depth
175
176 requested_order = self._user_requested.get(identifier, math.inf)
177
178 # Requires-Python has only one candidate and the check is basically
179 # free, so we always do it first to avoid needless work if it fails.
180 requires_python = identifier == REQUIRES_PYTHON_IDENTIFIER
181
182 # Prefer the causes of backtracking on the assumption that the problem
183 # resolving the dependency tree is related to the failures that caused
184 # the backtracking
185 backtrack_cause = self.is_backtrack_cause(identifier, backtrack_causes)
186
187 return (
188 not requires_python,
189 not direct,
190 not pinned,
191 not backtrack_cause,
192 inferred_depth,
193 requested_order,
194 not unfree,
195 identifier,
196 )
197
199 self,
200 identifier: str,
201 requirements: Mapping[str, Iterator[Requirement]],
202 incompatibilities: Mapping[str, Iterator[Candidate]],
203 ) -> Iterable[Candidate]:
204 def _eligible_for_upgrade(identifier: str) -> bool:
205 """Are upgrades allowed for this project?
206
207 This checks the upgrade strategy, and whether the project was one
208 that the user specified in the command line, in order to decide
209 whether we should upgrade if there's a newer version available.
210
211 (Note that we don't need access to the `--upgrade` flag, because
212 an upgrade strategy of "to-satisfy-only" means that `--upgrade`
213 was not specified).
214 """
215 if self._upgrade_strategy == "eager":
216 return True
217 elif self._upgrade_strategy == "only-if-needed":
218 user_order = _get_with_identifier(
219 self._user_requested,
220 identifier,
221 default=None,
222 )
223 return user_order is not None
224 return False
225
226 constraint = _get_with_identifier(
227 self._constraints,
228 identifier,
229 default=Constraint.empty(),
230 )
231 return self._factory.find_candidates(
232 identifier=identifier,
233 requirements=requirements,
234 constraint=constraint,
235 prefers_installed=(not _eligible_for_upgrade(identifier)),
236 incompatibilities=incompatibilities,
237 )
238
239 def is_satisfied_by(self, requirement: Requirement, candidate: Candidate) -> bool:
240 return requirement.is_satisfied_by(candidate)
241
242 def get_dependencies(self, candidate: Candidate) -> Sequence[Requirement]:
243 with_requires = not self._ignore_dependencies
244 return [r for r in candidate.iter_dependencies(with_requires) if r is not None]
245
246 @staticmethod
248 identifier: str, backtrack_causes: Sequence["PreferenceInformation"]
249 ) -> bool:
250 for backtrack_cause in backtrack_causes:
251 if identifier == backtrack_cause.requirement.name:
252 return True
254 return True
255 return False
"Preference" get_preference(self, str identifier, Mapping[str, Candidate] resolutions, Mapping[str, Iterator[Candidate]] candidates, Mapping[str, Iterable["PreferenceInformation"]] information, Sequence["PreferenceInformation"] backtrack_causes)
Definition provider.py:114
bool is_backtrack_cause(str identifier, Sequence["PreferenceInformation"] backtrack_causes)
Definition provider.py:249
Iterable[Candidate] find_matches(self, str identifier, Mapping[str, Iterator[Requirement]] requirements, Mapping[str, Iterator[Candidate]] incompatibilities)
Definition provider.py:203
str identify(self, Union[Requirement, Candidate] requirement_or_candidate)
Definition provider.py:104
bool is_satisfied_by(self, Requirement requirement, Candidate candidate)
Definition provider.py:239
None __init__(self, Factory factory, Dict[str, Constraint] constraints, bool ignore_dependencies, str upgrade_strategy, Dict[str, int] user_requested)
Definition provider.py:96
Sequence[Requirement] get_dependencies(self, Candidate candidate)
Definition provider.py:242
Union[D, V] _get_with_identifier(Mapping[str, V] mapping, str identifier, D default)
Definition provider.py:57
for i