Skip to content

Commit 923cb5a

Browse files
authored
Merge pull request #11119 from pradyunsg/move-add_requirement-to-legacy-resolver
2 parents 0a982f6 + d673aa1 commit 923cb5a

File tree

4 files changed

+238
-204
lines changed

4 files changed

+238
-204
lines changed

src/pip/_internal/req/req_set.py

Lines changed: 1 addition & 121 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import logging
22
from collections import OrderedDict
3-
from typing import Dict, Iterable, List, Optional, Tuple
3+
from typing import Dict, List
44

55
from pip._vendor.packaging.utils import canonicalize_name
66

7-
from pip._internal.exceptions import InstallationError
8-
from pip._internal.models.wheel import Wheel
97
from pip._internal.req.req_install import InstallRequirement
10-
from pip._internal.utils import compatibility_tags
118

129
logger = logging.getLogger(__name__)
1310

@@ -51,123 +48,6 @@ def add_named_requirement(self, install_req: InstallRequirement) -> None:
5148
project_name = canonicalize_name(install_req.name)
5249
self.requirements[project_name] = install_req
5350

54-
def add_requirement(
55-
self,
56-
install_req: InstallRequirement,
57-
parent_req_name: Optional[str] = None,
58-
extras_requested: Optional[Iterable[str]] = None,
59-
) -> Tuple[List[InstallRequirement], Optional[InstallRequirement]]:
60-
"""Add install_req as a requirement to install.
61-
62-
:param parent_req_name: The name of the requirement that needed this
63-
added. The name is used because when multiple unnamed requirements
64-
resolve to the same name, we could otherwise end up with dependency
65-
links that point outside the Requirements set. parent_req must
66-
already be added. Note that None implies that this is a user
67-
supplied requirement, vs an inferred one.
68-
:param extras_requested: an iterable of extras used to evaluate the
69-
environment markers.
70-
:return: Additional requirements to scan. That is either [] if
71-
the requirement is not applicable, or [install_req] if the
72-
requirement is applicable and has just been added.
73-
"""
74-
# If the markers do not match, ignore this requirement.
75-
if not install_req.match_markers(extras_requested):
76-
logger.info(
77-
"Ignoring %s: markers '%s' don't match your environment",
78-
install_req.name,
79-
install_req.markers,
80-
)
81-
return [], None
82-
83-
# If the wheel is not supported, raise an error.
84-
# Should check this after filtering out based on environment markers to
85-
# allow specifying different wheels based on the environment/OS, in a
86-
# single requirements file.
87-
if install_req.link and install_req.link.is_wheel:
88-
wheel = Wheel(install_req.link.filename)
89-
tags = compatibility_tags.get_supported()
90-
if self.check_supported_wheels and not wheel.supported(tags):
91-
raise InstallationError(
92-
"{} is not a supported wheel on this platform.".format(
93-
wheel.filename
94-
)
95-
)
96-
97-
# This next bit is really a sanity check.
98-
assert (
99-
not install_req.user_supplied or parent_req_name is None
100-
), "a user supplied req shouldn't have a parent"
101-
102-
# Unnamed requirements are scanned again and the requirement won't be
103-
# added as a dependency until after scanning.
104-
if not install_req.name:
105-
self.add_unnamed_requirement(install_req)
106-
return [install_req], None
107-
108-
try:
109-
existing_req: Optional[InstallRequirement] = self.get_requirement(
110-
install_req.name
111-
)
112-
except KeyError:
113-
existing_req = None
114-
115-
has_conflicting_requirement = (
116-
parent_req_name is None
117-
and existing_req
118-
and not existing_req.constraint
119-
and existing_req.extras == install_req.extras
120-
and existing_req.req
121-
and install_req.req
122-
and existing_req.req.specifier != install_req.req.specifier
123-
)
124-
if has_conflicting_requirement:
125-
raise InstallationError(
126-
"Double requirement given: {} (already in {}, name={!r})".format(
127-
install_req, existing_req, install_req.name
128-
)
129-
)
130-
131-
# When no existing requirement exists, add the requirement as a
132-
# dependency and it will be scanned again after.
133-
if not existing_req:
134-
self.add_named_requirement(install_req)
135-
# We'd want to rescan this requirement later
136-
return [install_req], install_req
137-
138-
# Assume there's no need to scan, and that we've already
139-
# encountered this for scanning.
140-
if install_req.constraint or not existing_req.constraint:
141-
return [], existing_req
142-
143-
does_not_satisfy_constraint = install_req.link and not (
144-
existing_req.link and install_req.link.path == existing_req.link.path
145-
)
146-
if does_not_satisfy_constraint:
147-
raise InstallationError(
148-
"Could not satisfy constraints for '{}': "
149-
"installation from path or url cannot be "
150-
"constrained to a version".format(install_req.name)
151-
)
152-
# If we're now installing a constraint, mark the existing
153-
# object for real installation.
154-
existing_req.constraint = False
155-
# If we're now installing a user supplied requirement,
156-
# mark the existing object as such.
157-
if install_req.user_supplied:
158-
existing_req.user_supplied = True
159-
existing_req.extras = tuple(
160-
sorted(set(existing_req.extras) | set(install_req.extras))
161-
)
162-
logger.debug(
163-
"Setting %s extras to: %s",
164-
existing_req,
165-
existing_req.extras,
166-
)
167-
# Return the existing requirement for addition to the parent and
168-
# scanning again.
169-
return [existing_req], existing_req
170-
17151
def has_requirement(self, name: str) -> bool:
17252
project_name = canonicalize_name(name)
17353

src/pip/_internal/resolution/legacy/resolver.py

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,22 @@
2828
DistributionNotFound,
2929
HashError,
3030
HashErrors,
31+
InstallationError,
3132
NoneMetadataError,
3233
UnsupportedPythonVersion,
3334
)
3435
from pip._internal.index.package_finder import PackageFinder
3536
from pip._internal.metadata import BaseDistribution
3637
from pip._internal.models.link import Link
38+
from pip._internal.models.wheel import Wheel
3739
from pip._internal.operations.prepare import RequirementPreparer
3840
from pip._internal.req.req_install import (
3941
InstallRequirement,
4042
check_invalid_constraint_type,
4143
)
4244
from pip._internal.req.req_set import RequirementSet
4345
from pip._internal.resolution.base import BaseResolver, InstallRequirementProvider
46+
from pip._internal.utils import compatibility_tags
4447
from pip._internal.utils.compatibility_tags import get_supported
4548
from pip._internal.utils.logging import indent_log
4649
from pip._internal.utils.misc import normalize_version_info
@@ -168,7 +171,7 @@ def resolve(
168171
for req in root_reqs:
169172
if req.constraint:
170173
check_invalid_constraint_type(req)
171-
requirement_set.add_requirement(req)
174+
self._add_requirement_to_set(requirement_set, req)
172175

173176
# Actually prepare the files, and collect any exceptions. Most hash
174177
# exceptions cannot be checked ahead of time, because
@@ -188,6 +191,124 @@ def resolve(
188191

189192
return requirement_set
190193

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+
191312
def _is_upgrade_allowed(self, req: InstallRequirement) -> bool:
192313
if self.upgrade_strategy == "to-satisfy-only":
193314
return False
@@ -393,7 +514,8 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
393514
# the legacy resolver so I'm just not going to bother refactoring.
394515
sub_install_req = self._make_install_req(str(subreq), req_to_install)
395516
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,
397519
sub_install_req,
398520
parent_req_name=parent_req_name,
399521
extras_requested=extras_requested,
@@ -410,7 +532,9 @@ def add_req(subreq: Requirement, extras_requested: Iterable[str]) -> None:
410532
# 'unnamed' requirements can only come from being directly
411533
# provided by the user.
412534
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+
)
414538

415539
if not self.ignore_dependencies:
416540
if req_to_install.extras:

0 commit comments

Comments
 (0)