28
28
DistributionNotFound ,
29
29
HashError ,
30
30
HashErrors ,
31
+ InstallationError ,
31
32
NoneMetadataError ,
32
33
UnsupportedPythonVersion ,
33
34
)
34
35
from pip ._internal .index .package_finder import PackageFinder
35
36
from pip ._internal .metadata import BaseDistribution
36
37
from pip ._internal .models .link import Link
38
+ from pip ._internal .models .wheel import Wheel
37
39
from pip ._internal .operations .prepare import RequirementPreparer
38
40
from pip ._internal .req .req_install import (
39
41
InstallRequirement ,
40
42
check_invalid_constraint_type ,
41
43
)
42
44
from pip ._internal .req .req_set import RequirementSet
43
45
from pip ._internal .resolution .base import BaseResolver , InstallRequirementProvider
46
+ from pip ._internal .utils import compatibility_tags
44
47
from pip ._internal .utils .compatibility_tags import get_supported
45
48
from pip ._internal .utils .logging import indent_log
46
49
from pip ._internal .utils .misc import normalize_version_info
@@ -168,7 +171,7 @@ def resolve(
168
171
for req in root_reqs :
169
172
if req .constraint :
170
173
check_invalid_constraint_type (req )
171
- requirement_set . add_requirement ( req )
174
+ self . _add_requirement_to_set ( requirement_set , req )
172
175
173
176
# Actually prepare the files, and collect any exceptions. Most hash
174
177
# exceptions cannot be checked ahead of time, because
@@ -188,6 +191,124 @@ def resolve(
188
191
189
192
return requirement_set
190
193
194
+ def _add_requirement_to_set (
195
+ self ,
196
+ requirement_set : RequirementSet ,
197
+ install_req : InstallRequirement ,
198
+ parent_req_name : Optional [str ] = None ,
199
+ extras_requested : Optional [Iterable [str ]] = None ,
200
+ ) -> Tuple [List [InstallRequirement ], Optional [InstallRequirement ]]:
201
+ """Add install_req as a requirement to install.
202
+
203
+ :param parent_req_name: The name of the requirement that needed this
204
+ added. The name is used because when multiple unnamed requirements
205
+ resolve to the same name, we could otherwise end up with dependency
206
+ links that point outside the Requirements set. parent_req must
207
+ already be added. Note that None implies that this is a user
208
+ supplied requirement, vs an inferred one.
209
+ :param extras_requested: an iterable of extras used to evaluate the
210
+ environment markers.
211
+ :return: Additional requirements to scan. That is either [] if
212
+ the requirement is not applicable, or [install_req] if the
213
+ requirement is applicable and has just been added.
214
+ """
215
+ # If the markers do not match, ignore this requirement.
216
+ if not install_req .match_markers (extras_requested ):
217
+ logger .info (
218
+ "Ignoring %s: markers '%s' don't match your environment" ,
219
+ install_req .name ,
220
+ install_req .markers ,
221
+ )
222
+ return [], None
223
+
224
+ # If the wheel is not supported, raise an error.
225
+ # Should check this after filtering out based on environment markers to
226
+ # allow specifying different wheels based on the environment/OS, in a
227
+ # single requirements file.
228
+ if install_req .link and install_req .link .is_wheel :
229
+ wheel = Wheel (install_req .link .filename )
230
+ tags = compatibility_tags .get_supported ()
231
+ if requirement_set .check_supported_wheels and not wheel .supported (tags ):
232
+ raise InstallationError (
233
+ "{} is not a supported wheel on this platform." .format (
234
+ wheel .filename
235
+ )
236
+ )
237
+
238
+ # This next bit is really a sanity check.
239
+ assert (
240
+ not install_req .user_supplied or parent_req_name is None
241
+ ), "a user supplied req shouldn't have a parent"
242
+
243
+ # Unnamed requirements are scanned again and the requirement won't be
244
+ # added as a dependency until after scanning.
245
+ if not install_req .name :
246
+ requirement_set .add_unnamed_requirement (install_req )
247
+ return [install_req ], None
248
+
249
+ try :
250
+ existing_req : Optional [
251
+ InstallRequirement
252
+ ] = requirement_set .get_requirement (install_req .name )
253
+ except KeyError :
254
+ existing_req = None
255
+
256
+ has_conflicting_requirement = (
257
+ parent_req_name is None
258
+ and existing_req
259
+ and not existing_req .constraint
260
+ and existing_req .extras == install_req .extras
261
+ and existing_req .req
262
+ and install_req .req
263
+ and existing_req .req .specifier != install_req .req .specifier
264
+ )
265
+ if has_conflicting_requirement :
266
+ raise InstallationError (
267
+ "Double requirement given: {} (already in {}, name={!r})" .format (
268
+ install_req , existing_req , install_req .name
269
+ )
270
+ )
271
+
272
+ # When no existing requirement exists, add the requirement as a
273
+ # dependency and it will be scanned again after.
274
+ if not existing_req :
275
+ requirement_set .add_named_requirement (install_req )
276
+ # We'd want to rescan this requirement later
277
+ return [install_req ], install_req
278
+
279
+ # Assume there's no need to scan, and that we've already
280
+ # encountered this for scanning.
281
+ if install_req .constraint or not existing_req .constraint :
282
+ return [], existing_req
283
+
284
+ does_not_satisfy_constraint = install_req .link and not (
285
+ existing_req .link and install_req .link .path == existing_req .link .path
286
+ )
287
+ if does_not_satisfy_constraint :
288
+ raise InstallationError (
289
+ "Could not satisfy constraints for '{}': "
290
+ "installation from path or url cannot be "
291
+ "constrained to a version" .format (install_req .name )
292
+ )
293
+ # If we're now installing a constraint, mark the existing
294
+ # object for real installation.
295
+ existing_req .constraint = False
296
+ # If we're now installing a user supplied requirement,
297
+ # mark the existing object as such.
298
+ if install_req .user_supplied :
299
+ existing_req .user_supplied = True
300
+ existing_req .extras = tuple (
301
+ sorted (set (existing_req .extras ) | set (install_req .extras ))
302
+ )
303
+ logger .debug (
304
+ "Setting %s extras to: %s" ,
305
+ existing_req ,
306
+ existing_req .extras ,
307
+ )
308
+ # Return the existing requirement for addition to the parent and
309
+ # scanning again.
310
+ return [existing_req ], existing_req
311
+
191
312
def _is_upgrade_allowed (self , req : InstallRequirement ) -> bool :
192
313
if self .upgrade_strategy == "to-satisfy-only" :
193
314
return False
@@ -393,7 +514,8 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
393
514
# the legacy resolver so I'm just not going to bother refactoring.
394
515
sub_install_req = self ._make_install_req (str (subreq ), req_to_install )
395
516
parent_req_name = req_to_install .name
396
- to_scan_again , add_to_parent = requirement_set .add_requirement (
517
+ to_scan_again , add_to_parent = self ._add_requirement_to_set (
518
+ requirement_set ,
397
519
sub_install_req ,
398
520
parent_req_name = parent_req_name ,
399
521
extras_requested = extras_requested ,
@@ -410,7 +532,9 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
410
532
# 'unnamed' requirements can only come from being directly
411
533
# provided by the user.
412
534
assert req_to_install .user_supplied
413
- requirement_set .add_requirement (req_to_install , parent_req_name = None )
535
+ self ._add_requirement_to_set (
536
+ requirement_set , req_to_install , parent_req_name = None
537
+ )
414
538
415
539
if not self .ignore_dependencies :
416
540
if req_to_install .extras :
0 commit comments