Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Let downstream library to define causes object #99

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/extras_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,6 @@ def get_dependencies(self, candidate):
req = self.get_base_requirement(candidate)
deps.append(req)
return deps

def causes(self, causes):
return [i for c in causes for i in c.information]
14 changes: 3 additions & 11 deletions src/resolvelib/providers.pyi
Original file line number Diff line number Diff line change
@@ -1,14 +1,4 @@
from typing import (
Any,
Collection,
Generic,
Iterable,
Iterator,
Mapping,
Optional,
Protocol,
Union,
)
from typing import Any, Generic, Iterable, Iterator, Mapping, Protocol, Union

from .reporters import BaseReporter
from .resolvers import RequirementInformation
Expand All @@ -25,6 +15,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: Any
) -> Preference: ...
def find_matches(
self,
Expand All @@ -34,6 +25,7 @@ class AbstractProvider(Generic[RT, CT, KT]):
) -> Matches: ...
def is_satisfied_by(self, requirement: RT, candidate: CT) -> bool: ...
def get_dependencies(self, candidate: CT) -> Iterable[RT]: ...
def causes(self, causes: Any) -> Any: ...

class AbstractResolver(Generic[RT, CT, KT]):
base_exception = Exception
Expand Down
25 changes: 17 additions & 8 deletions src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import collections
import operator
from copy import copy

from .providers import AbstractResolver
from .structs import DirectedGraph, IteratorMapping, build_iter_view
Expand Down Expand Up @@ -130,7 +131,7 @@ def _push_new_state(self):
state = State(
mapping=base.mapping.copy(),
criteria=base.criteria.copy(),
backtrack_causes=base.backtrack_causes[:],
backtrack_causes=copy(base.backtrack_causes),
)
self._states.append(state)

Expand Down Expand Up @@ -234,11 +235,11 @@ def _attempt_to_pin_criterion(self, name):
self.state.mapping.pop(name, None)
self.state.mapping[name] = candidate

return []
return self._p.causes(causes=[])

# All candidates tried, nothing works. This criterion is a dead
# end, signal for backtracking.
return causes
return self._p.causes(causes=causes)

def _backtrack(self):
"""Perform backtracking.
Expand Down Expand Up @@ -340,7 +341,7 @@ def resolve(self, requirements, max_rounds):
State(
mapping=collections.OrderedDict(),
criteria={},
backtrack_causes=[],
backtrack_causes=self._p.causes(causes=[]),
)
]
for r in requirements:
Expand Down Expand Up @@ -373,16 +374,24 @@ def resolve(self, requirements, max_rounds):
failure_causes = self._attempt_to_pin_criterion(name)

if failure_causes:
causes = [i for c in failure_causes for i in c.information]
# Backtrack if pinning fails. The backtrack process puts us in
# an unpinned state, so we can work on it in the next round.
self._r.resolving_conflicts(causes=causes)
self._r.resolving_conflicts(causes=failure_causes)
success = self._backtrack()
self.state.backtrack_causes[:] = causes

# Dead ends everywhere. Give up.
if not success:
raise ResolutionImpossible(self.state.backtrack_causes)
raise ResolutionImpossible(failure_causes)

# Recreate state with updated causes
current_state = self._states.pop()
self._states.append(
State(
mapping=current_state.mapping,
criteria=current_state.criteria,
backtrack_causes=failure_causes,
)
)
else:
# Pinning was successful. Push a new state to do another pin.
self._push_new_state()
Expand Down
3 changes: 2 additions & 1 deletion src/resolvelib/resolvers.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from typing import (
Any,
Collection,
Generic,
Iterable,
Expand Down Expand Up @@ -50,7 +51,7 @@ class InconsistentCandidate(ResolverException, Generic[RT, CT, KT]):
criterion: Criterion[RT, CT, KT]

class ResolutionImpossible(ResolutionError, Generic[RT, CT]):
causes: List[RequirementInformation[RT, CT]]
causes: Any

class ResolutionTooDeep(ResolutionError):
round_count: int
Expand Down
3 changes: 3 additions & 0 deletions tests/functional/cocoapods/test_resolvers_cocoapods.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,9 @@ def is_satisfied_by(self, requirement, candidate):
def get_dependencies(self, candidate):
return candidate.deps

def causes(self, causes):
return [i for c in causes for i in c.information]


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

def causes(self, causes):
return [i for c in causes for i in c.information]


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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ def _iter_dependencies(self, candidate):
def get_dependencies(self, candidate):
return list(self._iter_dependencies(candidate))

def causes(self, causes):
return [i for c in causes for i in c.information]


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

def causes(self, causes):
return [i for c in causes for i in c.information]

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

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

def causes(self, causes):
return [i for c in causes for i in c.information]

# 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).
Expand Down Expand Up @@ -131,6 +137,9 @@ def find_matches(self, identifier, requirements, incompatibilities):
def is_satisfied_by(self, requirement, candidate):
return candidate[1] in requirement[1]

def causes(self, causes):
return [i for c in causes for i in c.information]

def run_resolver(*args):
reporter = Reporter()
resolver = Resolver(Provider(), reporter)
Expand Down