Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow provider to narrow backtrack selection
Browse files Browse the repository at this point in the history
notatallshaw committed Feb 3, 2024
1 parent 995ed67 commit 6b3016e
Showing 8 changed files with 117 additions and 1 deletion.
10 changes: 10 additions & 0 deletions examples/extras_provider.py
Original file line number Diff line number Diff line change
@@ -52,3 +52,13 @@ def get_dependencies(self, candidate):
req = self.get_base_requirement(candidate)
deps.append(req)
return deps

def narrow_backtrack_selection(
self,
unsatisfied_names,
resolutions,
candidates,
information,
backtrack_causes,
):
return unsatisfied_names
10 changes: 10 additions & 0 deletions examples/reporter_demo.py
Original file line number Diff line number Diff line change
@@ -101,6 +101,16 @@ def is_satisfied_by(self, requirement, candidate):
def get_dependencies(self, candidate):
return self.candidates[candidate]

def narrow_backtrack_selection(
self,
unsatisfied_names,
resolutions,
candidates,
information,
backtrack_causes,
):
return unsatisfied_names


class Reporter(resolvelib.BaseReporter):
def starting(self):
29 changes: 29 additions & 0 deletions src/resolvelib/providers.py
Original file line number Diff line number Diff line change
@@ -136,3 +136,32 @@ def get_dependencies(self, candidate: CT) -> Iterable[RT]:
specifies as its dependencies.
"""
raise NotImplementedError

def narrow_backtrack_selection(
self,
unsatisfied_names: Iterable[KT],
resolutions: Mapping[KT, CT],
candidates: Mapping[KT, Iterator[CT]],
information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]],
backtrack_causes: Sequence[RequirementInformation[RT, CT]],
) -> Iterable[KT]:
"""
Narrows the selection of unsatisfied dependency names during the
backtracking process.
It is required to return a non-empty subset of `unsatisfied_names`,
the simplest implementation is to return `unsatisfied_names` unchanged.
This method can be used by the provider to optimizes the dependency
resolution process by determining which unsatisfied dependencies may
be selected for the next phase of backtracking.
Serving a similar purpose as `get_preference`, this method allows
the provider to guide resolvelib through the backtracking process.
It should be used instead of `get_preference` when the provider needs
to consider multiple unsatisfied dependency names simultaneously.
Returns:
Iterable[KT]: A non-empty subset of `unsatisfied_names`.
"""
raise NotImplementedError
27 changes: 26 additions & 1 deletion src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
@@ -435,8 +435,33 @@ def resolve(
unsatisfied_names
)

if len(unsatisfied_names) > 1:
filtered_unstatisfied_names = list(
self._p.narrow_backtrack_selection(
unsatisfied_names=unsatisfied_names,
resolutions=self.state.mapping,
candidates=IteratorMapping(
self.state.criteria,
operator.attrgetter("candidates"),
),
information=IteratorMapping(
self.state.criteria,
operator.attrgetter("information"),
),
backtrack_causes=self.state.backtrack_causes,
)
)
else:
filtered_unstatisfied_names = unsatisfied_names

# Choose the most preferred unpinned criterion to try.
name = min(unsatisfied_names, key=self._get_preference)
if len(filtered_unstatisfied_names) > 1:
name = min(
filtered_unstatisfied_names, key=self._get_preference
)
else:
name = filtered_unstatisfied_names[0]

failure_criterion = self._attempt_to_pin_criterion(name)

if failure_criterion:
10 changes: 10 additions & 0 deletions tests/functional/cocoapods/test_resolvers_cocoapods.py
Original file line number Diff line number Diff line change
@@ -222,6 +222,16 @@ def is_satisfied_by(self, requirement, candidate):
def get_dependencies(self, candidate):
return candidate.deps

def narrow_backtrack_selection(
self,
unsatisfied_names,
resolutions,
candidates,
information,
backtrack_causes,
):
return unsatisfied_names


XFAIL_CASES = {
# ResolveLib does not complain about cycles, so these will be different.
10 changes: 10 additions & 0 deletions tests/functional/python/test_resolvers_python.py
Original file line number Diff line number Diff line change
@@ -119,6 +119,16 @@ def _iter_dependencies(self, candidate):
def get_dependencies(self, candidate):
return list(self._iter_dependencies(candidate))

def narrow_backtrack_selection(
self,
unsatisfied_names,
resolutions,
candidates,
information,
backtrack_causes,
):
return unsatisfied_names


INPUTS_DIR = os.path.abspath(os.path.join(__file__, "..", "inputs"))

10 changes: 10 additions & 0 deletions tests/functional/swift-package-manager/test_resolvers_swift.py
Original file line number Diff line number Diff line change
@@ -132,6 +132,16 @@ def _iter_dependencies(self, candidate):
def get_dependencies(self, candidate):
return list(self._iter_dependencies(candidate))

def narrow_backtrack_selection(
self,
unsatisfied_names,
resolutions,
candidates,
information,
backtrack_causes,
):
return unsatisfied_names


@pytest.fixture(
params=[os.path.join(INPUTS_DIR, n) for n in INPUT_NAMES],
12 changes: 12 additions & 0 deletions tests/test_resolvers.py
Original file line number Diff line number Diff line change
@@ -58,6 +58,9 @@ def is_satisfied_by(self, requirement, candidate):
assert candidate is self.candidate
return False

def narrow_backtrack_selection(self, unsatisfied_names, **_):
return unsatisfied_names

resolver = Resolver(Provider(requirement, candidate), BaseReporter())

with pytest.raises(InconsistentCandidate) as ctx:
@@ -104,6 +107,9 @@ def find_matches(self, identifier, requirements, incompatibilities):
def is_satisfied_by(self, requirement, candidate):
return candidate[1] in requirement[1]

def narrow_backtrack_selection(self, unsatisfied_names, **_):
return unsatisfied_names

# Now when resolved, both requirements to child specified by parent should
# be pulled, and the resolver should choose v1, not v2 (happens if the
# v1-only requirement is dropped).
@@ -164,6 +170,9 @@ def find_matches(self, identifier, requirements, incompatibilities):
def is_satisfied_by(self, requirement, candidate):
return candidate.version in requirement.versions

def narrow_backtrack_selection(self, unsatisfied_names, **_):
return unsatisfied_names

def run_resolver(*args):
reporter = Reporter()
resolver = Resolver(Provider(), reporter)
@@ -243,6 +252,9 @@ def is_satisfied_by(
) -> bool:
return candidate[1] in Requirement(requirement).specifier

def narrow_backtrack_selection(self, unsatisfied_names, **_):
return unsatisfied_names

# patch Resolution._get_updated_criteria to collect rejected states
rejected_criteria: list[Criterion] = []
get_updated_criteria_orig = (

0 comments on commit 6b3016e

Please sign in to comment.