Skip to content

Commit 347d410

Browse files
committed
Use the project name to look up constraints
User-specified constraints only contain the project name without extras, but a constraint a project A would also apply to A's extra variants. This introduces a new project_name property on Requirement and Candidate classes for this lookup.
1 parent ab7ff0a commit 347d410

File tree

5 files changed

+77
-7
lines changed

5 files changed

+77
-7
lines changed

news/9232.bugfix.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
New resolver: Make constraints also apply to package variants with extras, so
2+
the resolver correctly avoids baktracking on them.

src/pip/_internal/resolution/resolvelib/base.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,25 @@ def is_satisfied_by(self, candidate):
6767

6868

6969
class Requirement(object):
70+
@property
71+
def project_name(self):
72+
# type: () -> str
73+
"""The "project name" of a requirement.
74+
75+
This is different from ``name`` if this requirement contains extras,
76+
in which case ``name`` would contain the ``[...]`` part, while this
77+
refers to the name of the project.
78+
"""
79+
raise NotImplementedError("Subclass should override")
80+
7081
@property
7182
def name(self):
7283
# type: () -> str
84+
"""The name identifying this requirement in the resolver.
85+
86+
This is different from ``project_name`` if this requirement contains
87+
extras, where ``project_name`` would not contain the ``[...]`` part.
88+
"""
7389
raise NotImplementedError("Subclass should override")
7490

7591
def is_satisfied_by(self, candidate):
@@ -86,9 +102,25 @@ def format_for_error(self):
86102

87103

88104
class Candidate(object):
105+
@property
106+
def project_name(self):
107+
# type: () -> str
108+
"""The "project name" of the candidate.
109+
110+
This is different from ``name`` if this candidate contains extras,
111+
in which case ``name`` would contain the ``[...]`` part, while this
112+
refers to the name of the project.
113+
"""
114+
raise NotImplementedError("Override in subclass")
115+
89116
@property
90117
def name(self):
91118
# type: () -> str
119+
"""The name identifying this candidate in the resolver.
120+
121+
This is different from ``project_name`` if this candidate contains
122+
extras, where ``project_name`` would not contain the ``[...]`` part.
123+
"""
92124
raise NotImplementedError("Override in subclass")
93125

94126
@property

src/pip/_internal/resolution/resolvelib/candidates.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,13 +175,15 @@ def source_link(self):
175175
return self._source_link
176176

177177
@property
178-
def name(self):
178+
def project_name(self):
179179
# type: () -> str
180180
"""The normalised name of the project the candidate refers to"""
181181
if self._name is None:
182182
self._name = canonicalize_name(self.dist.project_name)
183183
return self._name
184184

185+
name = project_name
186+
185187
@property
186188
def version(self):
187189
# type: () -> _BaseVersion
@@ -390,10 +392,12 @@ def __ne__(self, other):
390392
return not self.__eq__(other)
391393

392394
@property
393-
def name(self):
395+
def project_name(self):
394396
# type: () -> str
395397
return canonicalize_name(self.dist.project_name)
396398

399+
name = project_name
400+
397401
@property
398402
def version(self):
399403
# type: () -> _BaseVersion
@@ -481,11 +485,16 @@ def __ne__(self, other):
481485
# type: (Any) -> bool
482486
return not self.__eq__(other)
483487

488+
@property
489+
def project_name(self):
490+
# type: () -> str
491+
return self.base.project_name
492+
484493
@property
485494
def name(self):
486495
# type: () -> str
487496
"""The normalised name of the project the candidate refers to"""
488-
return format_name(self.base.name, self.extras)
497+
return format_name(self.base.project_name, self.extras)
489498

490499
@property
491500
def version(self):
@@ -572,11 +581,13 @@ def __str__(self):
572581
return "Python {}".format(self._version)
573582

574583
@property
575-
def name(self):
584+
def project_name(self):
576585
# type: () -> str
577586
# Avoid conflicting with the PyPI package "Python".
578587
return "<Python from Requires-Python>"
579588

589+
name = project_name
590+
580591
@property
581592
def version(self):
582593
# type: () -> _BaseVersion

src/pip/_internal/resolution/resolvelib/provider.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030

3131

3232
class PipProvider(AbstractProvider):
33+
"""Pip's provider implementation for resolvelib.
34+
35+
:params constraints: A mapping of constraints specified by the user. Keys
36+
are canonicalized project names.
37+
:params ignore_dependencies: Whether the user specified ``--no-deps``.
38+
:params upgrade_strategy: The user-specified upgrade strategy.
39+
:params user_requested: A set of canonicalized package names that the user
40+
supplied for pip to install/upgrade.
41+
"""
42+
3343
def __init__(
3444
self,
3545
factory, # type: Factory
@@ -113,7 +123,7 @@ def find_matches(self, requirements):
113123
# type: (Sequence[Requirement]) -> Iterable[Candidate]
114124
if not requirements:
115125
return []
116-
name = requirements[0].name
126+
name = requirements[0].project_name
117127

118128
def _eligible_for_upgrade(name):
119129
# type: (str) -> bool

src/pip/_internal/resolution/resolvelib/requirements.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ def __repr__(self):
2828
candidate=self.candidate,
2929
)
3030

31+
@property
32+
def project_name(self):
33+
# type: () -> str
34+
# No need to canonicalise - the candidate did this
35+
return self.candidate.project_name
36+
3137
@property
3238
def name(self):
3339
# type: () -> str
@@ -65,11 +71,15 @@ def __repr__(self):
6571
requirement=str(self._ireq.req),
6672
)
6773

74+
@property
75+
def project_name(self):
76+
# type: () -> str
77+
return canonicalize_name(self._ireq.req.name)
78+
6879
@property
6980
def name(self):
7081
# type: () -> str
71-
canonical_name = canonicalize_name(self._ireq.req.name)
72-
return format_name(canonical_name, self._extras)
82+
return format_name(self.project_name, self._extras)
7383

7484
def format_for_error(self):
7585
# type: () -> str
@@ -121,6 +131,11 @@ def __repr__(self):
121131
specifier=str(self.specifier),
122132
)
123133

134+
@property
135+
def project_name(self):
136+
# type: () -> str
137+
return self._candidate.project_name
138+
124139
@property
125140
def name(self):
126141
# type: () -> str

0 commit comments

Comments
 (0)