Skip to content

Commit

Permalink
Merge pull request #11783 from pradyunsg/resolvelib-update
Browse files Browse the repository at this point in the history
Upgrade resolvelib to 0.9.0
  • Loading branch information
pradyunsg authored Feb 7, 2023
2 parents 8844795 + 4f455ae commit 5a9efde
Show file tree
Hide file tree
Showing 14 changed files with 114 additions and 37 deletions.
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

0 comments on commit 5a9efde

Please sign in to comment.