Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Upgrade resolvelib to 0.9.0 #11783

Merged
merged 4 commits into from
Feb 7, 2023
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
1 change: 1 addition & 0 deletions news/resolvelib.vendor.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Upgrade resolvelib to 0.9.0
36 changes: 27 additions & 9 deletions src/pip/_internal/resolution/resolvelib/provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ def __init__(
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
return requirement_or_candidate.name

def get_preference( # type: ignore
def get_preference(
self,
identifier: str,
resolutions: Mapping[str, Candidate],
Expand All @@ -124,14 +124,29 @@ def get_preference( # type: ignore
* If equal, prefer if any requirement is "pinned", i.e. contains
operator ``===`` or ``==``.
* If equal, calculate an approximate "depth" and resolve requirements
closer to the user-specified requirements first.
closer to the user-specified requirements first. If the depth cannot
by determined (eg: due to no matching parents), it is considered
infinite.
* Order user-specified requirements by the order they are specified.
* If equal, prefers "non-free" requirements, i.e. contains at least one
operator, such as ``>=`` or ``<``.
* If equal, order alphabetically for consistency (helps debuggability).
"""
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
candidate, ireqs = zip(*lookups)
try:
next(iter(information[identifier]))
except StopIteration:
# There is no information for this identifier, so there's no known
# candidates.
has_information = False
else:
has_information = True

if has_information:
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
candidate, ireqs = zip(*lookups)
else:
candidate, ireqs = None, ()

operators = [
specifier.operator
for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
Expand All @@ -146,11 +161,14 @@ def get_preference( # type: ignore
requested_order: Union[int, float] = self._user_requested[identifier]
except KeyError:
requested_order = math.inf
parent_depths = (
self._known_depths[parent.name] if parent is not None else 0.0
for _, parent in information[identifier]
)
inferred_depth = min(d for d in parent_depths) + 1.0
if has_information:
parent_depths = (
self._known_depths[parent.name] if parent is not None else 0.0
for _, parent in information[identifier]
)
inferred_depth = min(d for d in parent_depths) + 1.0
else:
inferred_depth = math.inf
else:
inferred_depth = 1.0
self._known_depths[identifier] = inferred_depth
Expand Down
18 changes: 9 additions & 9 deletions src/pip/_internal/resolution/resolvelib/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@

class PipReporter(BaseReporter):
def __init__(self) -> None:
self.backtracks_by_package: DefaultDict[str, int] = defaultdict(int)
self.reject_count_by_package: DefaultDict[str, int] = defaultdict(int)

self._messages_at_backtrack = {
self._messages_at_reject_count = {
1: (
"pip is looking at multiple versions of {package_name} to "
"determine which version is compatible with other "
Expand All @@ -32,14 +32,14 @@ def __init__(self) -> None:
),
}

def backtracking(self, candidate: Candidate) -> None:
self.backtracks_by_package[candidate.name] += 1
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
self.reject_count_by_package[candidate.name] += 1

count = self.backtracks_by_package[candidate.name]
if count not in self._messages_at_backtrack:
count = self.reject_count_by_package[candidate.name]
if count not in self._messages_at_reject_count:
return

message = self._messages_at_backtrack[count]
message = self._messages_at_reject_count[count]
logger.info("INFO: %s", message.format(package_name=candidate.name))


Expand All @@ -61,8 +61,8 @@ def ending(self, state: Any) -> None:
def adding_requirement(self, requirement: Requirement, parent: Candidate) -> None:
logger.info("Reporter.adding_requirement(%r, %r)", requirement, parent)

def backtracking(self, candidate: Candidate) -> None:
logger.info("Reporter.backtracking(%r)", candidate)
def rejecting_candidate(self, criterion: Any, candidate: Candidate) -> None:
logger.info("Reporter.rejecting_candidate(%r, %r)", criterion, candidate)

def pinning(self, candidate: Candidate) -> None:
logger.info("Reporter.pinning(%r)", candidate)
2 changes: 1 addition & 1 deletion src/pip/_vendor/resolvelib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"ResolutionTooDeep",
]

__version__ = "0.8.1"
__version__ = "0.9.0"


from .providers import AbstractProvider, AbstractResolver
Expand Down
1 change: 1 addition & 0 deletions src/pip/_vendor/resolvelib/compat/collections_abc.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from collections.abc import Mapping, Sequence
14 changes: 7 additions & 7 deletions src/pip/_vendor/resolvelib/providers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class AbstractProvider(object):
"""Delegate class to provide requirement interface for the resolver."""
"""Delegate class to provide the required interface for the resolver."""

def identify(self, requirement_or_candidate):
"""Given a requirement, return an identifier for it.
Expand All @@ -24,9 +24,9 @@ def get_preference(
this group of arguments is.

:param identifier: An identifier as returned by ``identify()``. This
identifies the dependency matches of which should be returned.
identifies the dependency matches which should be returned.
:param resolutions: Mapping of candidates currently pinned by the
resolver. Each key is an identifier, and the value a candidate.
resolver. Each key is an identifier, and the value is a candidate.
The candidate may conflict with requirements from ``information``.
:param candidates: Mapping of each dependency's possible candidates.
Each value is an iterator of candidates.
Expand All @@ -39,10 +39,10 @@ def get_preference(

* ``requirement`` specifies a requirement contributing to the current
list of candidates.
* ``parent`` specifies the candidate that provides (dependend on) the
* ``parent`` specifies the candidate that provides (depended on) the
requirement, or ``None`` to indicate a root requirement.

The preference could depend on a various of issues, including (not
The preference could depend on various issues, including (not
necessarily in this order):

* Is this package pinned in the current resolution result?
Expand All @@ -61,7 +61,7 @@ def get_preference(
raise NotImplementedError

def find_matches(self, identifier, requirements, incompatibilities):
"""Find all possible candidates that satisfy given constraints.
"""Find all possible candidates that satisfy the given constraints.

:param identifier: An identifier as returned by ``identify()``. This
identifies the dependency matches of which should be returned.
Expand Down Expand Up @@ -92,7 +92,7 @@ def find_matches(self, identifier, requirements, incompatibilities):
def is_satisfied_by(self, requirement, candidate):
"""Whether the given requirement can be satisfied by a candidate.

The candidate is guarenteed to have been generated from the
The candidate is guaranteed to have been generated from the
requirement.

A boolean should be returned to indicate whether ``candidate`` is a
Expand Down
4 changes: 2 additions & 2 deletions src/pip/_vendor/resolvelib/providers.pyi
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
from typing import (
Any,
Collection,
Generic,
Iterable,
Iterator,
Mapping,
Optional,
Protocol,
Sequence,
Union,
)

Expand All @@ -25,6 +24,7 @@ class AbstractProvider(Generic[RT, CT, KT]):
resolutions: Mapping[KT, CT],
candidates: Mapping[KT, Iterator[CT]],
information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]],
backtrack_causes: Sequence[RequirementInformation[RT, CT]],
) -> Preference: ...
def find_matches(
self,
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/resolvelib/reporters.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def resolving_conflicts(self, causes):
:param causes: The information on the collision that caused the backtracking.
"""

def backtracking(self, candidate):
def rejecting_candidate(self, criterion, candidate):
"""Called when rejecting a candidate during backtracking."""

def pinning(self, candidate):
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/resolvelib/reporters.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ class BaseReporter:
def ending_round(self, index: int, state: Any) -> Any: ...
def ending(self, state: Any) -> Any: ...
def adding_requirement(self, requirement: Any, parent: Any) -> Any: ...
def backtracking(self, candidate: Any) -> Any: ...
def rejecting_candidate(self, criterion: Any, candidate: Any) -> Any: ...
def resolving_conflicts(self, causes: Any) -> Any: ...
def pinning(self, candidate: Any) -> Any: ...
44 changes: 42 additions & 2 deletions src/pip/_vendor/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,31 @@ def _add_to_criteria(self, criteria, requirement, parent):
raise RequirementsConflicted(criterion)
criteria[identifier] = criterion

def _remove_information_from_criteria(self, criteria, parents):
"""Remove information from parents of criteria.

Concretely, removes all values from each criterion's ``information``
field that have one of ``parents`` as provider of the requirement.

:param criteria: The criteria to update.
:param parents: Identifiers for which to remove information from all criteria.
"""
if not parents:
return
for key, criterion in criteria.items():
criteria[key] = Criterion(
criterion.candidates,
[
information
for information in criterion.information
if (
information[1] is None
or self._p.identify(information[1]) not in parents
)
],
criterion.incompatibilities,
)

def _get_preference(self, name):
return self._p.get_preference(
identifier=name,
Expand Down Expand Up @@ -212,6 +237,7 @@ def _attempt_to_pin_criterion(self, name):
try:
criteria = self._get_updated_criteria(candidate)
except RequirementsConflicted as e:
self._r.rejecting_candidate(e.criterion, candidate)
causes.append(e.criterion)
continue

Expand Down Expand Up @@ -281,8 +307,6 @@ def _backtrack(self):
# Also mark the newly known incompatibility.
incompatibilities_from_broken.append((name, [candidate]))

self._r.backtracking(candidate=candidate)

# Create a new state from the last known-to-work one, and apply
# the previously gathered incompatibility information.
def _patch_criteria():
Expand Down Expand Up @@ -368,6 +392,11 @@ def resolve(self, requirements, max_rounds):
self._r.ending(state=self.state)
return self.state

# keep track of satisfied names to calculate diff after pinning
satisfied_names = set(self.state.criteria.keys()) - set(
unsatisfied_names
)

# Choose the most preferred unpinned criterion to try.
name = min(unsatisfied_names, key=self._get_preference)
failure_causes = self._attempt_to_pin_criterion(name)
Expand All @@ -384,6 +413,17 @@ def resolve(self, requirements, max_rounds):
if not success:
raise ResolutionImpossible(self.state.backtrack_causes)
else:
# discard as information sources any invalidated names
# (unsatisfied names that were previously satisfied)
newly_unsatisfied_names = {
key
for key, criterion in self.state.criteria.items()
if key in satisfied_names
and not self._is_current_pin_satisfying(key, criterion)
}
self._remove_information_from_criteria(
self.state.criteria, newly_unsatisfied_names
)
# Pinning was successful. Push a new state to do another pin.
self._push_new_state()

Expand Down
12 changes: 12 additions & 0 deletions src/pip/_vendor/resolvelib/resolvers.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,18 @@ class ResolutionImpossible(ResolutionError, Generic[RT, CT]):
class ResolutionTooDeep(ResolutionError):
round_count: int

# This should be a NamedTuple, but Python 3.6 has a bug that prevents it.
# https://stackoverflow.com/a/50531189/1376863
class State(tuple, Generic[RT, CT, KT]):
mapping: Mapping[KT, CT]
criteria: Mapping[KT, Criterion[RT, CT, KT]]
backtrack_causes: Collection[RequirementInformation[RT, CT]]

class Resolution(Generic[RT, CT, KT]):
def resolve(
self, requirements: Iterable[RT], max_rounds: int
) -> State[RT, CT, KT]: ...

class Result(Generic[RT, CT, KT]):
mapping: Mapping[KT, CT]
graph: DirectedGraph[Optional[KT]]
Expand Down
11 changes: 8 additions & 3 deletions src/pip/_vendor/resolvelib/structs.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,26 @@ class _FactoryIterableView(object):

def __init__(self, factory):
self._factory = factory
self._iterable = None

def __repr__(self):
return "{}({})".format(type(self).__name__, list(self._factory()))
return "{}({})".format(type(self).__name__, list(self))

def __bool__(self):
try:
next(self._factory())
next(iter(self))
except StopIteration:
return False
return True

__nonzero__ = __bool__ # XXX: Python 2.

def __iter__(self):
return self._factory()
iterable = (
self._factory() if self._iterable is None else self._iterable
)
self._iterable, current = itertools.tee(iterable)
return current


class _SequenceIterableView(object):
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/resolvelib/structs.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ RT = TypeVar("RT") # Requirement.
CT = TypeVar("CT") # Candidate.
_T = TypeVar("_T")

Matches = Union[Iterable[CT], Callable[[], Iterator[CT]]]
Matches = Union[Iterable[CT], Callable[[], Iterable[CT]]]

class IteratorMapping(Mapping[KT, _T], metaclass=ABCMeta):
pass
Expand Down
2 changes: 1 addition & 1 deletion src/pip/_vendor/vendor.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ requests==2.28.2
rich==12.6.0
pygments==2.13.0
typing_extensions==4.4.0
resolvelib==0.8.1
resolvelib==0.9.0
setuptools==44.0.0
six==1.16.0
tenacity==8.1.0
Expand Down