Skip to content

Commit 831f32e

Browse files
authored
Merge pull request #9699 from uranusjr/resolvelib-types
2 parents fe27218 + 9ceabb5 commit 831f32e

15 files changed

+245
-45
lines changed

news/resolvelib.vendor.rst

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Upgrade vendored resolvelib to 0.5.5.

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

+8-3
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
Set,
1313
Tuple,
1414
TypeVar,
15+
cast,
1516
)
1617

1718
from pip._vendor.packaging.specifiers import SpecifierSet
@@ -419,7 +420,7 @@ def _report_requires_python_error(self, causes):
419420
return UnsupportedPythonVersion(message)
420421

421422
def _report_single_requirement_conflict(self, req, parent):
422-
# type: (Requirement, Candidate) -> DistributionNotFound
423+
# type: (Requirement, Optional[Candidate]) -> DistributionNotFound
423424
if parent is None:
424425
req_disp = str(req)
425426
else:
@@ -439,7 +440,7 @@ def _report_single_requirement_conflict(self, req, parent):
439440

440441
def get_installation_error(
441442
self,
442-
e, # type: ResolutionImpossible
443+
e, # type: ResolutionImpossible[Requirement, Candidate]
443444
constraints, # type: Dict[str, Constraint]
444445
):
445446
# type: (...) -> InstallationError
@@ -455,7 +456,11 @@ def get_installation_error(
455456
and not cause.requirement.is_satisfied_by(self._python_candidate)
456457
]
457458
if requires_python_causes:
458-
return self._report_requires_python_error(requires_python_causes)
459+
# The comprehension above makes sure all Requirement instances are
460+
# RequiresPythonRequirement, so let's cast for convinience.
461+
return self._report_requires_python_error(
462+
cast("Sequence[ConflictCause]", requires_python_causes),
463+
)
459464

460465
# Otherwise, we have a set of causes which can't all be satisfied
461466
# at once.

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

+17-7
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
1-
from typing import Any, Dict, Iterable, Optional, Sequence, Tuple, Union
1+
from typing import TYPE_CHECKING, Dict, Iterable, Optional, Sequence, Union
22

33
from pip._vendor.resolvelib.providers import AbstractProvider
44

55
from .base import Candidate, Constraint, Requirement
66
from .factory import Factory
77

8+
if TYPE_CHECKING:
9+
from pip._vendor.resolvelib.providers import Preference
10+
from pip._vendor.resolvelib.resolvers import RequirementInformation
11+
12+
PreferenceInformation = RequirementInformation[Requirement, Candidate]
13+
14+
_ProviderBase = AbstractProvider[Requirement, Candidate, str]
15+
else:
16+
_ProviderBase = AbstractProvider
17+
818
# Notes on the relationship between the provider, the factory, and the
919
# candidate and requirement classes.
1020
#
@@ -24,7 +34,7 @@
2434
# services to those objects (access to pip's finder and preparer).
2535

2636

27-
class PipProvider(AbstractProvider):
37+
class PipProvider(_ProviderBase):
2838
"""Pip's provider implementation for resolvelib.
2939
3040
:params constraints: A mapping of constraints specified by the user. Keys
@@ -50,17 +60,17 @@ def __init__(
5060
self._upgrade_strategy = upgrade_strategy
5161
self._user_requested = user_requested
5262

53-
def identify(self, dependency):
63+
def identify(self, requirement_or_candidate):
5464
# type: (Union[Requirement, Candidate]) -> str
55-
return dependency.name
65+
return requirement_or_candidate.name
5666

5767
def get_preference(
5868
self,
5969
resolution, # type: Optional[Candidate]
60-
candidates, # type: Sequence[Candidate]
61-
information, # type: Sequence[Tuple[Requirement, Candidate]]
70+
candidates, # type: Iterable[Candidate]
71+
information, # type: Iterable[PreferenceInformation]
6272
):
63-
# type: (...) -> Any
73+
# type: (...) -> Preference
6474
"""Produce a sort key for given requirement based on preference.
6575
6676
The lower the return value is, the more preferred this group of

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

+17-8
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import functools
22
import logging
33
import os
4-
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple
4+
from typing import TYPE_CHECKING, Dict, List, Optional, Set, Tuple, cast
55

66
from pip._vendor.packaging.utils import canonicalize_name
77
from pip._vendor.packaging.version import parse as parse_version
88
from pip._vendor.resolvelib import BaseReporter, ResolutionImpossible
99
from pip._vendor.resolvelib import Resolver as RLResolver
10-
from pip._vendor.resolvelib.resolvers import Result
10+
from pip._vendor.resolvelib.structs import DirectedGraph
1111

1212
from pip._internal.cache import WheelCache
1313
from pip._internal.exceptions import InstallationError
@@ -28,11 +28,14 @@
2828
from pip._internal.utils.filetypes import is_archive_file
2929
from pip._internal.utils.misc import dist_is_editable
3030

31-
from .base import Constraint
31+
from .base import Candidate, Constraint, Requirement
3232
from .factory import Factory
3333

3434
if TYPE_CHECKING:
35-
from pip._vendor.resolvelib.structs import DirectedGraph
35+
from pip._vendor.resolvelib.resolvers import Result as RLResult
36+
37+
Result = RLResult[Requirement, Candidate, str]
38+
3639

3740
logger = logging.getLogger(__name__)
3841

@@ -114,7 +117,10 @@ def resolve(self, root_reqs, check_supported_wheels):
114117
reporter = PipDebuggingReporter() # type: BaseReporter
115118
else:
116119
reporter = PipReporter()
117-
resolver = RLResolver(provider, reporter)
120+
resolver = RLResolver(
121+
provider,
122+
reporter,
123+
) # type: RLResolver[Requirement, Candidate, str]
118124

119125
try:
120126
try_to_avoid_resolution_too_deep = 2000000
@@ -123,7 +129,10 @@ def resolve(self, root_reqs, check_supported_wheels):
123129
)
124130

125131
except ResolutionImpossible as e:
126-
error = self.factory.get_installation_error(e, constraints)
132+
error = self.factory.get_installation_error(
133+
cast("ResolutionImpossible[Requirement, Candidate]", e),
134+
constraints,
135+
)
127136
raise error from e
128137

129138
req_set = RequirementSet(check_supported_wheels=check_supported_wheels)
@@ -148,7 +157,7 @@ def resolve(self, root_reqs, check_supported_wheels):
148157
# The incoming distribution is editable, or different in
149158
# editable-ness to installation -- reinstall.
150159
ireq.should_reinstall = True
151-
elif candidate.source_link.is_file:
160+
elif candidate.source_link and candidate.source_link.is_file:
152161
# The incoming distribution is under file://
153162
if candidate.source_link.is_wheel:
154163
# is a local wheel -- do nothing.
@@ -236,7 +245,7 @@ def get_installation_order(self, req_set):
236245

237246

238247
def get_topological_weights(graph, expected_node_count):
239-
# type: (DirectedGraph, int) -> Dict[Optional[str], int]
248+
# type: (DirectedGraph[Optional[str]], int) -> Dict[Optional[str], int]
240249
"""Assign weights to each node based on how "deep" they are.
241250
242251
This implementation may change at any point in the future without prior

src/pip/_vendor/resolvelib.pyi

-1
This file was deleted.

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.5.4"
14+
__version__ = "0.5.5"
1515

1616

1717
from .providers import AbstractProvider, AbstractResolver
+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
__version__: str
2+
3+
from .providers import (
4+
AbstractResolver as AbstractResolver,
5+
AbstractProvider as AbstractProvider,
6+
)
7+
from .reporters import BaseReporter as BaseReporter
8+
from .resolvers import (
9+
InconsistentCandidate as InconsistentCandidate,
10+
RequirementsConflicted as RequirementsConflicted,
11+
Resolver as Resolver,
12+
ResolutionError as ResolutionError,
13+
ResolutionImpossible as ResolutionImpossible,
14+
ResolutionTooDeep as ResolutionTooDeep,
15+
)

src/pip/_vendor/resolvelib/providers.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ class AbstractProvider(object):
22
"""Delegate class to provide requirement interface for the resolver."""
33

44
def identify(self, requirement_or_candidate):
5-
"""Given a requirement or candidate, return an identifier for it.
5+
"""Given a requirement, return an identifier for it.
66
7-
This is used in many places to identify a requirement or candidate,
8-
e.g. whether two requirements should have their specifier parts merged,
9-
whether two candidates would conflict with each other (because they
10-
have same name but different versions).
7+
This is used to identify a requirement, e.g. whether two requirements
8+
should have their specifier parts merged.
119
"""
1210
raise NotImplementedError
1311

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
from typing import (
2+
Any,
3+
Collection,
4+
Generic,
5+
Iterable,
6+
Mapping,
7+
Optional,
8+
Protocol,
9+
Sequence,
10+
Union,
11+
)
12+
13+
from .reporters import BaseReporter
14+
from .resolvers import RequirementInformation
15+
from .structs import (
16+
KT,
17+
RT,
18+
CT,
19+
IterableView,
20+
Matches,
21+
)
22+
23+
class Preference(Protocol):
24+
def __lt__(self, __other: Any) -> bool: ...
25+
26+
class AbstractProvider(Generic[RT, CT, KT]):
27+
def identify(self, requirement_or_candidate: Union[RT, CT]) -> KT: ...
28+
def get_preference(
29+
self,
30+
resolution: Optional[CT],
31+
candidates: IterableView[CT],
32+
information: Collection[RequirementInformation[RT, CT]],
33+
) -> Preference: ...
34+
def find_matches(self, requirements: Sequence[RT]) -> Matches: ...
35+
def is_satisfied_by(self, requirement: RT, candidate: CT) -> bool: ...
36+
def get_dependencies(self, candidate: CT) -> Iterable[RT]: ...
37+
38+
class AbstractResolver(Generic[RT, CT, KT]):
39+
base_exception = Exception
40+
provider: AbstractProvider[RT, CT, KT]
41+
reporter: BaseReporter
42+
def __init__(
43+
self, provider: AbstractProvider[RT, CT, KT], reporter: BaseReporter
44+
): ...

src/pip/_vendor/resolvelib/py.typed

Whitespace-only changes.
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from typing import Any
2+
3+
class BaseReporter:
4+
def starting(self) -> Any: ...
5+
def starting_round(self, index: int) -> Any: ...
6+
def ending_round(self, index: int, state: Any) -> Any: ...
7+
def ending(self, state: Any) -> Any: ...
8+
def adding_requirement(self, requirement: Any, parent: Any) -> Any: ...
9+
def backtracking(self, candidate: Any) -> Any: ...
10+
def pinning(self, candidate: Any) -> Any: ...

src/pip/_vendor/resolvelib/resolvers.py

+20-19
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ def __repr__(self):
7676
@classmethod
7777
def from_requirement(cls, provider, requirement, parent):
7878
"""Build an instance from a requirement."""
79-
cands = build_iter_view(provider.find_matches([requirement]))
79+
matches = provider.find_matches(requirements=[requirement])
80+
cands = build_iter_view(matches)
8081
infos = [RequirementInformation(requirement, parent)]
8182
criterion = cls(cands, infos, incompatibilities=[])
8283
if not cands:
@@ -93,7 +94,8 @@ def merged_with(self, provider, requirement, parent):
9394
"""Build a new instance from this and a new requirement."""
9495
infos = list(self.information)
9596
infos.append(RequirementInformation(requirement, parent))
96-
cands = build_iter_view(provider.find_matches([r for r, _ in infos]))
97+
matches = provider.find_matches([r for r, _ in infos])
98+
cands = build_iter_view(matches)
9799
criterion = type(self)(cands, infos, list(self.incompatibilities))
98100
if not cands:
99101
raise RequirementsConflicted(criterion)
@@ -165,22 +167,21 @@ def _push_new_state(self):
165167
self._states.append(state)
166168

167169
def _merge_into_criterion(self, requirement, parent):
168-
self._r.adding_requirement(requirement, parent)
169-
name = self._p.identify(requirement)
170-
try:
170+
self._r.adding_requirement(requirement=requirement, parent=parent)
171+
name = self._p.identify(requirement_or_candidate=requirement)
172+
if name in self.state.criteria:
171173
crit = self.state.criteria[name]
172-
except KeyError:
173-
crit = Criterion.from_requirement(self._p, requirement, parent)
174-
else:
175174
crit = crit.merged_with(self._p, requirement, parent)
175+
else:
176+
crit = Criterion.from_requirement(self._p, requirement, parent)
176177
return name, crit
177178

178179
def _get_criterion_item_preference(self, item):
179180
name, criterion = item
180181
return self._p.get_preference(
181-
self.state.mapping.get(name),
182-
criterion.candidates.for_preference(),
183-
criterion.information,
182+
resolution=self.state.mapping.get(name),
183+
candidates=criterion.candidates.for_preference(),
184+
information=criterion.information,
184185
)
185186

186187
def _is_current_pin_satisfying(self, name, criterion):
@@ -189,13 +190,13 @@ def _is_current_pin_satisfying(self, name, criterion):
189190
except KeyError:
190191
return False
191192
return all(
192-
self._p.is_satisfied_by(r, current_pin)
193+
self._p.is_satisfied_by(requirement=r, candidate=current_pin)
193194
for r in criterion.iter_requirement()
194195
)
195196

196197
def _get_criteria_to_update(self, candidate):
197198
criteria = {}
198-
for r in self._p.get_dependencies(candidate):
199+
for r in self._p.get_dependencies(candidate=candidate):
199200
name, crit = self._merge_into_criterion(r, parent=candidate)
200201
criteria[name] = crit
201202
return criteria
@@ -214,15 +215,15 @@ def _attempt_to_pin_criterion(self, name, criterion):
214215
# faulty provider, we will raise an error to notify the implementer
215216
# to fix find_matches() and/or is_satisfied_by().
216217
satisfied = all(
217-
self._p.is_satisfied_by(r, candidate)
218+
self._p.is_satisfied_by(requirement=r, candidate=candidate)
218219
for r in criterion.iter_requirement()
219220
)
220221
if not satisfied:
221222
raise InconsistentCandidate(candidate, criterion)
222223

223224
# Put newly-pinned candidate at the end. This is essential because
224225
# backtracking looks at this mapping to get the last pin.
225-
self._r.pinning(candidate)
226+
self._r.pinning(candidate=candidate)
226227
self.state.mapping.pop(name, None)
227228
self.state.mapping[name] = candidate
228229
self.state.criteria.update(criteria)
@@ -274,7 +275,7 @@ def _backtrack(self):
274275
# Also mark the newly known incompatibility.
275276
incompatibilities_from_broken.append((name, [candidate]))
276277

277-
self._r.backtracking(candidate)
278+
self._r.backtracking(candidate=candidate)
278279

279280
# Create a new state from the last known-to-work one, and apply
280281
# the previously gathered incompatibility information.
@@ -326,7 +327,7 @@ def resolve(self, requirements, max_rounds):
326327
self._push_new_state()
327328

328329
for round_index in range(max_rounds):
329-
self._r.starting_round(round_index)
330+
self._r.starting_round(index=round_index)
330331

331332
unsatisfied_criterion_items = [
332333
item
@@ -336,7 +337,7 @@ def resolve(self, requirements, max_rounds):
336337

337338
# All criteria are accounted for. Nothing more to pin, we are done!
338339
if not unsatisfied_criterion_items:
339-
self._r.ending(self.state)
340+
self._r.ending(state=self.state)
340341
return self.state
341342

342343
# Choose the most preferred unpinned criterion to try.
@@ -359,7 +360,7 @@ def resolve(self, requirements, max_rounds):
359360
# Pinning was successful. Push a new state to do another pin.
360361
self._push_new_state()
361362

362-
self._r.ending_round(round_index, self.state)
363+
self._r.ending_round(index=round_index, state=self.state)
363364

364365
raise ResolutionTooDeep(max_rounds)
365366

0 commit comments

Comments
 (0)