Skip to content

Commit bbd1015

Browse files
committed
Pseudo vendor resolvelib
1 parent 51de88c commit bbd1015

File tree

2 files changed

+89
-5
lines changed

2 files changed

+89
-5
lines changed

src/pip/_vendor/resolvelib/providers.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,55 @@ def resolve(self, requirements, **kwargs):
131131
:raises: ``self.base_exception`` or its subclass.
132132
"""
133133
raise NotImplementedError
134+
135+
def narrow_requirement_selection(
136+
self, identifiers, resolutions, candidates, information, backtrack_causes
137+
):
138+
"""
139+
Narrows the selection of requirements being considered during
140+
resolution.
141+
142+
The requirement selection is defined as "The possible requirements
143+
that will be resolved next." If a requirement isn't part of the returned
144+
iterable, it will not be considered during the next step of resolution.
145+
146+
:param identifiers: An iterable of `identifier` as returned by
147+
``identify()``. These identify all requirements currently being
148+
considered.
149+
:param resolutions: Mapping of candidates currently pinned by the
150+
resolver. Each key is an identifier, and the value is a candidate.
151+
The candidate may conflict with requirements from ``information``.
152+
:param candidates: Mapping of each dependency's possible candidates.
153+
Each value is an iterator of candidates.
154+
:param information: Mapping of requirement information of each package.
155+
Each value is an iterator of *requirement information*.
156+
:param backtrack_causes: Sequence of *requirement information* that are
157+
the requirements that caused the resolver to most recently
158+
backtrack.
159+
160+
A *requirement information* instance is a named tuple with two members:
161+
162+
* ``requirement`` specifies a requirement contributing to the current
163+
list of candidates.
164+
* ``parent`` specifies the candidate that provides (depended on) the
165+
requirement, or ``None`` to indicate a root requirement.
166+
167+
Must return a non-empty subset of `identifiers`, with the simplest
168+
implementation being to return `identifiers` unchanged.
169+
170+
Can be used by the provider to optimize the dependency resolution
171+
process. `get_preference` will only be called on the identifiers
172+
returned. If there is only one identifier returned, then `get_preference`
173+
won't be called at all.
174+
175+
Serving a similar purpose as `get_preference`, this method allows the
176+
provider to guide resolvelib through the resolution process. It should
177+
be used instead of `get_preference` when the provider needs to consider
178+
multiple identifiers simultaneously, or when the provider wants to skip
179+
checking all identifiers, e.g., because the checks are prohibitively
180+
expensive.
181+
182+
Returns:
183+
Iterable[KT]: A non-empty subset of `identifiers`.
184+
"""
185+
return identifiers

src/pip/_vendor/resolvelib/resolvers.py

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,12 @@ def _patch_criteria():
378378
# No way to backtrack anymore.
379379
return False
380380

381+
def _extract_causes(self, criteron):
382+
"""Extract causes from list of criterion and deduplicate"""
383+
return list(
384+
{id(i): i for c in criteron for i in c.information}.values()
385+
)
386+
381387
def resolve(self, requirements, max_rounds):
382388
if self._states:
383389
raise RuntimeError("already resolved")
@@ -422,12 +428,38 @@ def resolve(self, requirements, max_rounds):
422428
unsatisfied_names
423429
)
424430

425-
# Choose the most preferred unpinned criterion to try.
426-
name = min(unsatisfied_names, key=self._get_preference)
427-
failure_causes = self._attempt_to_pin_criterion(name)
431+
if len(unsatisfied_names) > 1:
432+
narrowed_unstatisfied_names = list(
433+
self._p.narrow_requirement_selection(
434+
identifiers=unsatisfied_names,
435+
resolutions=self.state.mapping,
436+
candidates=IteratorMapping(
437+
self.state.criteria,
438+
operator.attrgetter("candidates"),
439+
),
440+
information=IteratorMapping(
441+
self.state.criteria,
442+
operator.attrgetter("information"),
443+
),
444+
backtrack_causes=self.state.backtrack_causes,
445+
)
446+
)
447+
else:
448+
narrowed_unstatisfied_names = unsatisfied_names
449+
450+
# If there is only 1 unsatisfied name skip calling self._get_preference
451+
if len(narrowed_unstatisfied_names) > 1:
452+
# Choose the most preferred unpinned criterion to try.
453+
name = min(
454+
narrowed_unstatisfied_names, key=self._get_preference
455+
)
456+
else:
457+
name = narrowed_unstatisfied_names[0]
458+
459+
failure_criterion = self._attempt_to_pin_criterion(name)
428460

429-
if failure_causes:
430-
causes = [i for c in failure_causes for i in c.information]
461+
if failure_criterion:
462+
causes = self._extract_causes(failure_criterion)
431463
# Backjump if pinning fails. The backjump process puts us in
432464
# an unpinned state, so we can work on it in the next round.
433465
self._r.resolving_conflicts(causes=causes)

0 commit comments

Comments
 (0)