1
1
import collections
2
2
import math
3
- from typing import TYPE_CHECKING , Dict , Iterable , Iterator , Mapping , Sequence , Union
3
+ from typing import (
4
+ TYPE_CHECKING ,
5
+ Dict ,
6
+ Iterable ,
7
+ Iterator ,
8
+ Mapping ,
9
+ Sequence ,
10
+ TypeVar ,
11
+ Union ,
12
+ )
4
13
5
14
from pip ._vendor .resolvelib .providers import AbstractProvider
6
15
37
46
# services to those objects (access to pip's finder and preparer).
38
47
39
48
49
+ D = TypeVar ("D" )
50
+ V = TypeVar ("V" )
51
+
52
+
53
+ def _get_with_identifier (
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
+
40
78
class PipProvider (_ProviderBase ):
41
79
"""Pip's provider implementation for resolvelib.
42
80
@@ -150,28 +188,13 @@ def get_preference( # type: ignore
150
188
identifier ,
151
189
)
152
190
153
- def _get_constraint (self , identifier : str ) -> Constraint :
154
- if identifier in self ._constraints :
155
- return self ._constraints [identifier ]
156
-
157
- # HACK: Theoretically we should check whether this identifier is a valid
158
- # "NAME[EXTRAS]" format, and parse out the name part with packaging or
159
- # some regular expression. But since pip's resolver only spits out
160
- # three kinds of identifiers: normalized PEP 503 names, normalized names
161
- # plus extras, and Requires-Python, we can cheat a bit here.
162
- name , open_bracket , _ = identifier .partition ("[" )
163
- if open_bracket and name in self ._constraints :
164
- return self ._constraints [name ]
165
-
166
- return Constraint .empty ()
167
-
168
191
def find_matches (
169
192
self ,
170
193
identifier : str ,
171
194
requirements : Mapping [str , Iterator [Requirement ]],
172
195
incompatibilities : Mapping [str , Iterator [Candidate ]],
173
196
) -> Iterable [Candidate ]:
174
- def _eligible_for_upgrade (name : str ) -> bool :
197
+ def _eligible_for_upgrade (identifier : str ) -> bool :
175
198
"""Are upgrades allowed for this project?
176
199
177
200
This checks the upgrade strategy, and whether the project was one
@@ -185,13 +208,23 @@ def _eligible_for_upgrade(name: str) -> bool:
185
208
if self ._upgrade_strategy == "eager" :
186
209
return True
187
210
elif self ._upgrade_strategy == "only-if-needed" :
188
- return name in self ._user_requested
211
+ user_order = _get_with_identifier (
212
+ self ._user_requested ,
213
+ identifier ,
214
+ default = None ,
215
+ )
216
+ return user_order is not None
189
217
return False
190
218
219
+ constraint = _get_with_identifier (
220
+ self ._constraints ,
221
+ identifier ,
222
+ default = Constraint .empty (),
223
+ )
191
224
return self ._factory .find_candidates (
192
225
identifier = identifier ,
193
226
requirements = requirements ,
194
- constraint = self . _get_constraint ( identifier ) ,
227
+ constraint = constraint ,
195
228
prefers_installed = (not _eligible_for_upgrade (identifier )),
196
229
incompatibilities = incompatibilities ,
197
230
)
0 commit comments