Skip to content

Commit 5a9efde

Browse files
authored
Merge pull request #11783 from pradyunsg/resolvelib-update
Upgrade resolvelib to 0.9.0
2 parents 8844795 + 4f455ae commit 5a9efde

14 files changed

+114
-37
lines changed

news/resolvelib.vendor.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade resolvelib to 0.9.0

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

+27-9
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ def __init__(
104104
def identify(self, requirement_or_candidate: Union[Requirement, Candidate]) -> str:
105105
return requirement_or_candidate.name
106106

107-
def get_preference( # type: ignore
107+
def get_preference(
108108
self,
109109
identifier: str,
110110
resolutions: Mapping[str, Candidate],
@@ -124,14 +124,29 @@ def get_preference( # type: ignore
124124
* If equal, prefer if any requirement is "pinned", i.e. contains
125125
operator ``===`` or ``==``.
126126
* If equal, calculate an approximate "depth" and resolve requirements
127-
closer to the user-specified requirements first.
127+
closer to the user-specified requirements first. If the depth cannot
128+
by determined (eg: due to no matching parents), it is considered
129+
infinite.
128130
* Order user-specified requirements by the order they are specified.
129131
* If equal, prefers "non-free" requirements, i.e. contains at least one
130132
operator, such as ``>=`` or ``<``.
131133
* If equal, order alphabetically for consistency (helps debuggability).
132134
"""
133-
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
134-
candidate, ireqs = zip(*lookups)
135+
try:
136+
next(iter(information[identifier]))
137+
except StopIteration:
138+
# There is no information for this identifier, so there's no known
139+
# candidates.
140+
has_information = False
141+
else:
142+
has_information = True
143+
144+
if has_information:
145+
lookups = (r.get_candidate_lookup() for r, _ in information[identifier])
146+
candidate, ireqs = zip(*lookups)
147+
else:
148+
candidate, ireqs = None, ()
149+
135150
operators = [
136151
specifier.operator
137152
for specifier_set in (ireq.specifier for ireq in ireqs if ireq)
@@ -146,11 +161,14 @@ def get_preference( # type: ignore
146161
requested_order: Union[int, float] = self._user_requested[identifier]
147162
except KeyError:
148163
requested_order = math.inf
149-
parent_depths = (
150-
self._known_depths[parent.name] if parent is not None else 0.0
151-
for _, parent in information[identifier]
152-
)
153-
inferred_depth = min(d for d in parent_depths) + 1.0
164+
if has_information:
165+
parent_depths = (
166+
self._known_depths[parent.name] if parent is not None else 0.0
167+
for _, parent in information[identifier]
168+
)
169+
inferred_depth = min(d for d in parent_depths) + 1.0
170+
else:
171+
inferred_depth = math.inf
154172
else:
155173
inferred_depth = 1.0
156174
self._known_depths[identifier] = inferred_depth

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

+9-9
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111

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

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

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

38-
count = self.backtracks_by_package[candidate.name]
39-
if count not in self._messages_at_backtrack:
38+
count = self.reject_count_by_package[candidate.name]
39+
if count not in self._messages_at_reject_count:
4040
return
4141

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

4545

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

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

6767
def pinning(self, candidate: Candidate) -> None:
6868
logger.info("Reporter.pinning(%r)", candidate)

src/pip/_vendor/resolvelib/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"ResolutionTooDeep",
1212
]
1313

14-
__version__ = "0.8.1"
14+
__version__ = "0.9.0"
1515

1616

1717
from .providers import AbstractProvider, AbstractResolver
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from collections.abc import Mapping, Sequence

src/pip/_vendor/resolvelib/providers.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
class AbstractProvider(object):
2-
"""Delegate class to provide requirement interface for the resolver."""
2+
"""Delegate class to provide the required interface for the resolver."""
33

44
def identify(self, requirement_or_candidate):
55
"""Given a requirement, return an identifier for it.
@@ -24,9 +24,9 @@ def get_preference(
2424
this group of arguments is.
2525
2626
:param identifier: An identifier as returned by ``identify()``. This
27-
identifies the dependency matches of which should be returned.
27+
identifies the dependency matches which should be returned.
2828
:param resolutions: Mapping of candidates currently pinned by the
29-
resolver. Each key is an identifier, and the value a candidate.
29+
resolver. Each key is an identifier, and the value is a candidate.
3030
The candidate may conflict with requirements from ``information``.
3131
:param candidates: Mapping of each dependency's possible candidates.
3232
Each value is an iterator of candidates.
@@ -39,10 +39,10 @@ def get_preference(
3939
4040
* ``requirement`` specifies a requirement contributing to the current
4141
list of candidates.
42-
* ``parent`` specifies the candidate that provides (dependend on) the
42+
* ``parent`` specifies the candidate that provides (depended on) the
4343
requirement, or ``None`` to indicate a root requirement.
4444
45-
The preference could depend on a various of issues, including (not
45+
The preference could depend on various issues, including (not
4646
necessarily in this order):
4747
4848
* Is this package pinned in the current resolution result?
@@ -61,7 +61,7 @@ def get_preference(
6161
raise NotImplementedError
6262

6363
def find_matches(self, identifier, requirements, incompatibilities):
64-
"""Find all possible candidates that satisfy given constraints.
64+
"""Find all possible candidates that satisfy the given constraints.
6565
6666
:param identifier: An identifier as returned by ``identify()``. This
6767
identifies the dependency matches of which should be returned.
@@ -92,7 +92,7 @@ def find_matches(self, identifier, requirements, incompatibilities):
9292
def is_satisfied_by(self, requirement, candidate):
9393
"""Whether the given requirement can be satisfied by a candidate.
9494
95-
The candidate is guarenteed to have been generated from the
95+
The candidate is guaranteed to have been generated from the
9696
requirement.
9797
9898
A boolean should be returned to indicate whether ``candidate`` is a

src/pip/_vendor/resolvelib/providers.pyi

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
from typing import (
22
Any,
3-
Collection,
43
Generic,
54
Iterable,
65
Iterator,
76
Mapping,
8-
Optional,
97
Protocol,
8+
Sequence,
109
Union,
1110
)
1211

@@ -25,6 +24,7 @@ class AbstractProvider(Generic[RT, CT, KT]):
2524
resolutions: Mapping[KT, CT],
2625
candidates: Mapping[KT, Iterator[CT]],
2726
information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]],
27+
backtrack_causes: Sequence[RequirementInformation[RT, CT]],
2828
) -> Preference: ...
2929
def find_matches(
3030
self,

src/pip/_vendor/resolvelib/reporters.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def resolving_conflicts(self, causes):
3636
:param causes: The information on the collision that caused the backtracking.
3737
"""
3838

39-
def backtracking(self, candidate):
39+
def rejecting_candidate(self, criterion, candidate):
4040
"""Called when rejecting a candidate during backtracking."""
4141

4242
def pinning(self, candidate):

src/pip/_vendor/resolvelib/reporters.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ class BaseReporter:
66
def ending_round(self, index: int, state: Any) -> Any: ...
77
def ending(self, state: Any) -> Any: ...
88
def adding_requirement(self, requirement: Any, parent: Any) -> Any: ...
9-
def backtracking(self, candidate: Any) -> Any: ...
9+
def rejecting_candidate(self, criterion: Any, candidate: Any) -> Any: ...
1010
def resolving_conflicts(self, causes: Any) -> Any: ...
1111
def pinning(self, candidate: Any) -> Any: ...

src/pip/_vendor/resolvelib/resolvers.py

+42-2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,31 @@ def _add_to_criteria(self, criteria, requirement, parent):
173173
raise RequirementsConflicted(criterion)
174174
criteria[identifier] = criterion
175175

176+
def _remove_information_from_criteria(self, criteria, parents):
177+
"""Remove information from parents of criteria.
178+
179+
Concretely, removes all values from each criterion's ``information``
180+
field that have one of ``parents`` as provider of the requirement.
181+
182+
:param criteria: The criteria to update.
183+
:param parents: Identifiers for which to remove information from all criteria.
184+
"""
185+
if not parents:
186+
return
187+
for key, criterion in criteria.items():
188+
criteria[key] = Criterion(
189+
criterion.candidates,
190+
[
191+
information
192+
for information in criterion.information
193+
if (
194+
information[1] is None
195+
or self._p.identify(information[1]) not in parents
196+
)
197+
],
198+
criterion.incompatibilities,
199+
)
200+
176201
def _get_preference(self, name):
177202
return self._p.get_preference(
178203
identifier=name,
@@ -212,6 +237,7 @@ def _attempt_to_pin_criterion(self, name):
212237
try:
213238
criteria = self._get_updated_criteria(candidate)
214239
except RequirementsConflicted as e:
240+
self._r.rejecting_candidate(e.criterion, candidate)
215241
causes.append(e.criterion)
216242
continue
217243

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

284-
self._r.backtracking(candidate=candidate)
285-
286310
# Create a new state from the last known-to-work one, and apply
287311
# the previously gathered incompatibility information.
288312
def _patch_criteria():
@@ -368,6 +392,11 @@ def resolve(self, requirements, max_rounds):
368392
self._r.ending(state=self.state)
369393
return self.state
370394

395+
# keep track of satisfied names to calculate diff after pinning
396+
satisfied_names = set(self.state.criteria.keys()) - set(
397+
unsatisfied_names
398+
)
399+
371400
# Choose the most preferred unpinned criterion to try.
372401
name = min(unsatisfied_names, key=self._get_preference)
373402
failure_causes = self._attempt_to_pin_criterion(name)
@@ -384,6 +413,17 @@ def resolve(self, requirements, max_rounds):
384413
if not success:
385414
raise ResolutionImpossible(self.state.backtrack_causes)
386415
else:
416+
# discard as information sources any invalidated names
417+
# (unsatisfied names that were previously satisfied)
418+
newly_unsatisfied_names = {
419+
key
420+
for key, criterion in self.state.criteria.items()
421+
if key in satisfied_names
422+
and not self._is_current_pin_satisfying(key, criterion)
423+
}
424+
self._remove_information_from_criteria(
425+
self.state.criteria, newly_unsatisfied_names
426+
)
387427
# Pinning was successful. Push a new state to do another pin.
388428
self._push_new_state()
389429

src/pip/_vendor/resolvelib/resolvers.pyi

+12
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,18 @@ class ResolutionImpossible(ResolutionError, Generic[RT, CT]):
5555
class ResolutionTooDeep(ResolutionError):
5656
round_count: int
5757

58+
# This should be a NamedTuple, but Python 3.6 has a bug that prevents it.
59+
# https://stackoverflow.com/a/50531189/1376863
60+
class State(tuple, Generic[RT, CT, KT]):
61+
mapping: Mapping[KT, CT]
62+
criteria: Mapping[KT, Criterion[RT, CT, KT]]
63+
backtrack_causes: Collection[RequirementInformation[RT, CT]]
64+
65+
class Resolution(Generic[RT, CT, KT]):
66+
def resolve(
67+
self, requirements: Iterable[RT], max_rounds: int
68+
) -> State[RT, CT, KT]: ...
69+
5870
class Result(Generic[RT, CT, KT]):
5971
mapping: Mapping[KT, CT]
6072
graph: DirectedGraph[Optional[KT]]

src/pip/_vendor/resolvelib/structs.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -117,21 +117,26 @@ class _FactoryIterableView(object):
117117

118118
def __init__(self, factory):
119119
self._factory = factory
120+
self._iterable = None
120121

121122
def __repr__(self):
122-
return "{}({})".format(type(self).__name__, list(self._factory()))
123+
return "{}({})".format(type(self).__name__, list(self))
123124

124125
def __bool__(self):
125126
try:
126-
next(self._factory())
127+
next(iter(self))
127128
except StopIteration:
128129
return False
129130
return True
130131

131132
__nonzero__ = __bool__ # XXX: Python 2.
132133

133134
def __iter__(self):
134-
return self._factory()
135+
iterable = (
136+
self._factory() if self._iterable is None else self._iterable
137+
)
138+
self._iterable, current = itertools.tee(iterable)
139+
return current
135140

136141

137142
class _SequenceIterableView(object):

src/pip/_vendor/resolvelib/structs.pyi

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RT = TypeVar("RT") # Requirement.
1616
CT = TypeVar("CT") # Candidate.
1717
_T = TypeVar("_T")
1818

19-
Matches = Union[Iterable[CT], Callable[[], Iterator[CT]]]
19+
Matches = Union[Iterable[CT], Callable[[], Iterable[CT]]]
2020

2121
class IteratorMapping(Mapping[KT, _T], metaclass=ABCMeta):
2222
pass

src/pip/_vendor/vendor.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ requests==2.28.2
1515
rich==12.6.0
1616
pygments==2.13.0
1717
typing_extensions==4.4.0
18-
resolvelib==0.8.1
18+
resolvelib==0.9.0
1919
setuptools==44.0.0
2020
six==1.16.0
2121
tenacity==8.1.0

0 commit comments

Comments
 (0)