Skip to content

Commit

Permalink
Tree pruning seems to work?
Browse files Browse the repository at this point in the history
  • Loading branch information
uranusjr committed Jan 5, 2021
1 parent 3cd67b0 commit 353f6db
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 3 deletions.
32 changes: 29 additions & 3 deletions src/resolvelib/resolvers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import collections
import itertools

from .providers import AbstractResolver
from .structs import DirectedGraph, build_iter_view
Expand Down Expand Up @@ -143,6 +144,7 @@ def __init__(self, provider, reporter):
self._p = provider
self._r = reporter
self._states = []
self._known_failures = []

@property
def state(self):
Expand Down Expand Up @@ -199,15 +201,39 @@ def _get_criteria_to_update(self, candidate):
criteria[name] = crit
return criteria

def _match_known_failure_causes(self, updating_criteria):
try:
match_identically = self._p.match_identically
except AttributeError:
return False
criteria = self.state.criteria.copy()
criteria.update(updating_criteria)
for state in self._known_failures:
identical = match_identically(
itertools.chain.from_iterable(
crit.iter_requirement() for crit in criteria.values()
),
itertools.chain.from_iterable(
crit.iter_requirement() for crit in state.criteria.values()
),
)
if identical:
return True
return False

def _attempt_to_pin_criterion(self, name, criterion):
causes = []

for candidate in criterion.candidates:
try:
criteria = self._get_criteria_to_update(candidate)
except RequirementsConflicted as e:
causes.append(e.criterion)
continue

if self._match_known_failure_causes(criteria):
continue

# Check the newly-pinned candidate actually works. This should
# always pass under normal circumstances, but in the case of a
# faulty provider, we will raise an error to notify the implementer
Expand All @@ -226,7 +252,7 @@ def _attempt_to_pin_criterion(self, name, criterion):
self.state.mapping[name] = candidate
self.state.criteria.update(criteria)

return []
return None

# All candidates tried, nothing works. This criterion is a dead
# end, signal for backtracking.
Expand Down Expand Up @@ -260,7 +286,7 @@ def _backtrack(self):
"""
while len(self._states) >= 3:
# Remove the state that triggered backtracking.
del self._states[-1]
self._known_failures.append(self._states.pop())

# Retrieve the last candidate pin and known incompatibilities.
broken_state = self._states.pop()
Expand Down Expand Up @@ -345,7 +371,7 @@ def resolve(self, requirements, max_rounds):
)
failure_causes = self._attempt_to_pin_criterion(name, criterion)

if failure_causes:
if failure_causes is not None:
# Backtrack if pinning fails. The backtrack process puts us in
# an unpinned state, so we can work on it in the next round.
success = self._backtrack()
Expand Down
8 changes: 8 additions & 0 deletions tests/test_resolvers.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@ def is_satisfied_by(self, requirement, candidate):
and candidate.version in requirement.versions
)

def match_identically(self, reqs1, reqs2):
vers1 = collections.defaultdict(set)
vers2 = collections.defaultdict(set)
for rs, vs in [(reqs1, vers1), (reqs2, vers2)]:
for r in rs:
vs[r.name] = vs[r.name].union(r.versions)
return vers1 == vers2

def get_dependencies(self, candidate):
return candidate.dependencies

Expand Down

0 comments on commit 353f6db

Please sign in to comment.