9
9
UnsupportedPythonVersion ,
10
10
)
11
11
from pip ._internal .utils .compatibility_tags import get_supported
12
+ from pip ._internal .utils .hashes import Hashes
12
13
from pip ._internal .utils .misc import (
13
14
dist_in_site_packages ,
14
15
dist_in_usersite ,
31
32
)
32
33
33
34
if MYPY_CHECK_RUNNING :
34
- from typing import Dict , Iterable , Iterator , Optional , Set , Tuple , TypeVar
35
+ from typing import (
36
+ FrozenSet ,
37
+ Dict ,
38
+ Iterable ,
39
+ List ,
40
+ Optional ,
41
+ Sequence ,
42
+ Set ,
43
+ Tuple ,
44
+ TypeVar ,
45
+ )
35
46
36
47
from pip ._vendor .packaging .specifiers import SpecifierSet
37
48
from pip ._vendor .packaging .version import _BaseVersion
@@ -71,7 +82,7 @@ def __init__(
71
82
):
72
83
# type: (...) -> None
73
84
74
- self .finder = finder
85
+ self ._finder = finder
75
86
self .preparer = preparer
76
87
self ._wheel_cache = wheel_cache
77
88
self ._python_candidate = RequiresPythonCandidate (py_version_info )
@@ -94,7 +105,7 @@ def __init__(
94
105
def _make_candidate_from_dist (
95
106
self ,
96
107
dist , # type: Distribution
97
- extras , # type: Set [str]
108
+ extras , # type: FrozenSet [str]
98
109
parent , # type: InstallRequirement
99
110
):
100
111
# type: (...) -> Candidate
@@ -106,7 +117,7 @@ def _make_candidate_from_dist(
106
117
def _make_candidate_from_link (
107
118
self ,
108
119
link , # type: Link
109
- extras , # type: Set [str]
120
+ extras , # type: FrozenSet [str]
110
121
parent , # type: InstallRequirement
111
122
name , # type: Optional[str]
112
123
version , # type: Optional[_BaseVersion]
@@ -130,9 +141,28 @@ def _make_candidate_from_link(
130
141
return ExtrasCandidate (base , extras )
131
142
return base
132
143
133
- def iter_found_candidates (self , ireq , extras ):
134
- # type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
135
- name = canonicalize_name (ireq .req .name )
144
+ def _iter_found_candidates (
145
+ self ,
146
+ ireqs , # type: Sequence[InstallRequirement]
147
+ specifier , # type: SpecifierSet
148
+ ):
149
+ # type: (...) -> Iterable[Candidate]
150
+ if not ireqs :
151
+ return ()
152
+
153
+ # The InstallRequirement implementation requires us to give it a
154
+ # "parent", which doesn't really fit with graph-based resolution.
155
+ # Here we just choose the first requirement to represent all of them.
156
+ # Hopefully the Project model can correct this mismatch in the future.
157
+ parent = ireqs [0 ]
158
+ name = canonicalize_name (parent .req .name )
159
+
160
+ hashes = Hashes ()
161
+ extras = frozenset () # type: FrozenSet[str]
162
+ for ireq in ireqs :
163
+ specifier &= ireq .req .specifier
164
+ hashes |= ireq .hashes (trust_internet = False )
165
+ extras |= frozenset (ireq .extras )
136
166
137
167
# We use this to ensure that we only yield a single candidate for
138
168
# each version (the finder's preferred one for that version). The
@@ -148,43 +178,68 @@ def iter_found_candidates(self, ireq, extras):
148
178
if not self ._force_reinstall and name in self ._installed_dists :
149
179
installed_dist = self ._installed_dists [name ]
150
180
installed_version = installed_dist .parsed_version
151
- if ireq .req .specifier .contains (
152
- installed_version ,
153
- prereleases = True
154
- ):
181
+ if specifier .contains (installed_version , prereleases = True ):
155
182
candidate = self ._make_candidate_from_dist (
156
183
dist = installed_dist ,
157
184
extras = extras ,
158
- parent = ireq ,
185
+ parent = parent ,
159
186
)
160
187
candidates [installed_version ] = candidate
161
188
162
- found = self .finder .find_best_candidate (
163
- project_name = ireq . req . name ,
164
- specifier = ireq . req . specifier ,
165
- hashes = ireq . hashes ( trust_internet = False ) ,
189
+ found = self ._finder .find_best_candidate (
190
+ project_name = name ,
191
+ specifier = specifier ,
192
+ hashes = hashes ,
166
193
)
167
194
for ican in found .iter_applicable ():
168
195
if ican .version == installed_version :
169
196
continue
170
197
candidate = self ._make_candidate_from_link (
171
198
link = ican .link ,
172
199
extras = extras ,
173
- parent = ireq ,
200
+ parent = parent ,
174
201
name = name ,
175
202
version = ican .version ,
176
203
)
177
204
candidates [ican .version ] = candidate
178
205
179
206
return six .itervalues (candidates )
180
207
208
+ def find_candidates (self , requirements , constraint ):
209
+ # type: (Sequence[Requirement], SpecifierSet) -> Iterable[Candidate]
210
+ explicit_candidates = set () # type: Set[Candidate]
211
+ ireqs = [] # type: List[InstallRequirement]
212
+ for req in requirements :
213
+ cand , ireq = req .get_candidate_lookup ()
214
+ if cand is not None :
215
+ explicit_candidates .add (cand )
216
+ if ireq is not None :
217
+ ireqs .append (ireq )
218
+
219
+ # If none of the requirements want an explicit candidate, we can ask
220
+ # the finder for candidates.
221
+ if not explicit_candidates :
222
+ return self ._iter_found_candidates (ireqs , constraint )
223
+
224
+ if constraint :
225
+ name = explicit_candidates .pop ().name
226
+ raise InstallationError (
227
+ "Could not satisfy constraints for {!r}: installation from "
228
+ "path or url cannot be constrained to a version" .format (name )
229
+ )
230
+
231
+ return (
232
+ c for c in explicit_candidates
233
+ if all (req .is_satisfied_by (c ) for req in requirements )
234
+ )
235
+
181
236
def make_requirement_from_install_req (self , ireq ):
182
237
# type: (InstallRequirement) -> Requirement
183
238
if not ireq .link :
184
- return SpecifierRequirement (ireq , factory = self )
239
+ return SpecifierRequirement (ireq )
185
240
cand = self ._make_candidate_from_link (
186
241
ireq .link ,
187
- extras = set (ireq .extras ),
242
+ extras = frozenset (ireq .extras ),
188
243
parent = ireq ,
189
244
name = canonicalize_name (ireq .name ) if ireq .name else None ,
190
245
version = None ,
0 commit comments