Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions news/9232.bugfix.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
New resolver: Make constraints also apply to package variants with extras, so
the resolver correctly avoids backtracking on them.
32 changes: 32 additions & 0 deletions src/pip/_internal/resolution/resolvelib/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,25 @@ def is_satisfied_by(self, candidate):


class Requirement(object):
@property
def project_name(self):
# type: () -> str
"""The "project name" of a requirement.

This is different from ``name`` if this requirement contains extras,
in which case ``name`` would contain the ``[...]`` part, while this
refers to the name of the project.
"""
raise NotImplementedError("Subclass should override")

@property
def name(self):
# type: () -> str
"""The name identifying this requirement in the resolver.

This is different from ``project_name`` if this requirement contains
extras, where ``project_name`` would not contain the ``[...]`` part.
"""
raise NotImplementedError("Subclass should override")

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


class Candidate(object):
@property
def project_name(self):
# type: () -> str
"""The "project name" of the candidate.

This is different from ``name`` if this candidate contains extras,
in which case ``name`` would contain the ``[...]`` part, while this
refers to the name of the project.
"""
raise NotImplementedError("Override in subclass")

@property
def name(self):
# type: () -> str
"""The name identifying this candidate in the resolver.

This is different from ``project_name`` if this candidate contains
extras, where ``project_name`` would not contain the ``[...]`` part.
"""
raise NotImplementedError("Override in subclass")

@property
Expand Down
28 changes: 24 additions & 4 deletions src/pip/_internal/resolution/resolvelib/candidates.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,13 +175,18 @@ def source_link(self):
return self._source_link

@property
def name(self):
def project_name(self):
# type: () -> str
"""The normalised name of the project the candidate refers to"""
if self._name is None:
self._name = canonicalize_name(self.dist.project_name)
return self._name

@property
def name(self):
# type: () -> str
return self.project_name

@property
def version(self):
# type: () -> _BaseVersion
Expand Down Expand Up @@ -390,10 +395,15 @@ def __ne__(self, other):
return not self.__eq__(other)

@property
def name(self):
def project_name(self):
# type: () -> str
return canonicalize_name(self.dist.project_name)

@property
def name(self):
# type: () -> str
return self.project_name

@property
def version(self):
# type: () -> _BaseVersion
Expand Down Expand Up @@ -481,11 +491,16 @@ def __ne__(self, other):
# type: (Any) -> bool
return not self.__eq__(other)

@property
def project_name(self):
# type: () -> str
return self.base.project_name

@property
def name(self):
# type: () -> str
"""The normalised name of the project the candidate refers to"""
return format_name(self.base.name, self.extras)
return format_name(self.base.project_name, self.extras)

@property
def version(self):
Expand Down Expand Up @@ -572,11 +587,16 @@ def __str__(self):
return "Python {}".format(self._version)

@property
def name(self):
def project_name(self):
# type: () -> str
# Avoid conflicting with the PyPI package "Python".
return "<Python from Requires-Python>"

@property
def name(self):
# type: () -> str
return self.project_name

@property
def version(self):
# type: () -> _BaseVersion
Expand Down
12 changes: 11 additions & 1 deletion src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@


class PipProvider(AbstractProvider):
"""Pip's provider implementation for resolvelib.

:params constraints: A mapping of constraints specified by the user. Keys
are canonicalized project names.
:params ignore_dependencies: Whether the user specified ``--no-deps``.
:params upgrade_strategy: The user-specified upgrade strategy.
:params user_requested: A set of canonicalized package names that the user
supplied for pip to install/upgrade.
"""

def __init__(
self,
factory, # type: Factory
Expand Down Expand Up @@ -113,7 +123,7 @@ def find_matches(self, requirements):
# type: (Sequence[Requirement]) -> Iterable[Candidate]
if not requirements:
return []
name = requirements[0].name
name = requirements[0].project_name

def _eligible_for_upgrade(name):
# type: (str) -> bool
Expand Down
19 changes: 17 additions & 2 deletions src/pip/_internal/resolution/resolvelib/requirements.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ def __repr__(self):
candidate=self.candidate,
)

@property
def project_name(self):
# type: () -> str
# No need to canonicalise - the candidate did this
return self.candidate.project_name

@property
def name(self):
# type: () -> str
Expand Down Expand Up @@ -65,11 +71,15 @@ def __repr__(self):
requirement=str(self._ireq.req),
)

@property
def project_name(self):
# type: () -> str
return canonicalize_name(self._ireq.req.name)

@property
def name(self):
# type: () -> str
canonical_name = canonicalize_name(self._ireq.req.name)
return format_name(canonical_name, self._extras)
return format_name(self.project_name, self._extras)

def format_for_error(self):
# type: () -> str
Expand Down Expand Up @@ -121,6 +131,11 @@ def __repr__(self):
specifier=str(self.specifier),
)

@property
def project_name(self):
# type: () -> str
return self._candidate.project_name

@property
def name(self):
# type: () -> str
Expand Down