Change venv
This commit is contained in:
@@ -11,7 +11,7 @@ __all__ = [
|
||||
"ResolutionTooDeep",
|
||||
]
|
||||
|
||||
__version__ = "0.7.1"
|
||||
__version__ = "1.0.1"
|
||||
|
||||
|
||||
from .providers import AbstractProvider, AbstractResolver
|
||||
@@ -19,8 +19,8 @@ from .reporters import BaseReporter
|
||||
from .resolvers import (
|
||||
InconsistentCandidate,
|
||||
RequirementsConflicted,
|
||||
Resolver,
|
||||
ResolutionError,
|
||||
ResolutionImpossible,
|
||||
ResolutionTooDeep,
|
||||
Resolver,
|
||||
)
|
||||
|
@@ -1,5 +1,5 @@
|
||||
class AbstractProvider(object):
|
||||
"""Delegate class to provide requirement interface for the resolver."""
|
||||
"""Delegate class to provide the required interface for the resolver."""
|
||||
|
||||
def identify(self, requirement_or_candidate):
|
||||
"""Given a requirement, return an identifier for it.
|
||||
@@ -9,7 +9,14 @@ class AbstractProvider(object):
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def get_preference(self, identifier, resolutions, candidates, information):
|
||||
def get_preference(
|
||||
self,
|
||||
identifier,
|
||||
resolutions,
|
||||
candidates,
|
||||
information,
|
||||
backtrack_causes,
|
||||
):
|
||||
"""Produce a sort key for given requirement based on preference.
|
||||
|
||||
The preference is defined as "I think this requirement should be
|
||||
@@ -17,23 +24,25 @@ class AbstractProvider(object):
|
||||
this group of arguments is.
|
||||
|
||||
:param identifier: An identifier as returned by ``identify()``. This
|
||||
identifies the dependency matches of which should be returned.
|
||||
identifies the dependency matches which should be returned.
|
||||
:param resolutions: Mapping of candidates currently pinned by the
|
||||
resolver. Each key is an identifier, and the value a candidate.
|
||||
resolver. Each key is an identifier, and the value is a candidate.
|
||||
The candidate may conflict with requirements from ``information``.
|
||||
:param candidates: Mapping of each dependency's possible candidates.
|
||||
Each value is an iterator of candidates.
|
||||
:param information: Mapping of requirement information of each package.
|
||||
Each value is an iterator of *requirement information*.
|
||||
:param backtrack_causes: Sequence of requirement information that were
|
||||
the requirements that caused the resolver to most recently backtrack.
|
||||
|
||||
A *requirement information* instance is a named tuple with two members:
|
||||
|
||||
* ``requirement`` specifies a requirement contributing to the current
|
||||
list of candidates.
|
||||
* ``parent`` specifies the candidate that provides (dependend on) the
|
||||
* ``parent`` specifies the candidate that provides (depended on) the
|
||||
requirement, or ``None`` to indicate a root requirement.
|
||||
|
||||
The preference could depend on a various of issues, including (not
|
||||
The preference could depend on various issues, including (not
|
||||
necessarily in this order):
|
||||
|
||||
* Is this package pinned in the current resolution result?
|
||||
@@ -52,7 +61,7 @@ class AbstractProvider(object):
|
||||
raise NotImplementedError
|
||||
|
||||
def find_matches(self, identifier, requirements, incompatibilities):
|
||||
"""Find all possible candidates that satisfy given constraints.
|
||||
"""Find all possible candidates that satisfy the given constraints.
|
||||
|
||||
:param identifier: An identifier as returned by ``identify()``. This
|
||||
identifies the dependency matches of which should be returned.
|
||||
@@ -83,7 +92,7 @@ class AbstractProvider(object):
|
||||
def is_satisfied_by(self, requirement, candidate):
|
||||
"""Whether the given requirement can be satisfied by a candidate.
|
||||
|
||||
The candidate is guarenteed to have been generated from the
|
||||
The candidate is guaranteed to have been generated from the
|
||||
requirement.
|
||||
|
||||
A boolean should be returned to indicate whether ``candidate`` is a
|
||||
|
@@ -30,7 +30,13 @@ class BaseReporter(object):
|
||||
requirements passed in from ``Resolver.resolve()``.
|
||||
"""
|
||||
|
||||
def backtracking(self, candidate):
|
||||
def resolving_conflicts(self, causes):
|
||||
"""Called when starting to attempt requirement conflict resolution.
|
||||
|
||||
:param causes: The information on the collision that caused the backtracking.
|
||||
"""
|
||||
|
||||
def rejecting_candidate(self, criterion, candidate):
|
||||
"""Called when rejecting a candidate during backtracking."""
|
||||
|
||||
def pinning(self, candidate):
|
||||
|
@@ -1,10 +1,10 @@
|
||||
import collections
|
||||
import itertools
|
||||
import operator
|
||||
|
||||
from .providers import AbstractResolver
|
||||
from .structs import DirectedGraph, IteratorMapping, build_iter_view
|
||||
|
||||
|
||||
RequirementInformation = collections.namedtuple(
|
||||
"RequirementInformation", ["requirement", "parent"]
|
||||
)
|
||||
@@ -99,7 +99,7 @@ class ResolutionTooDeep(ResolutionError):
|
||||
|
||||
|
||||
# Resolution state in a round.
|
||||
State = collections.namedtuple("State", "mapping criteria")
|
||||
State = collections.namedtuple("State", "mapping criteria backtrack_causes")
|
||||
|
||||
|
||||
class Resolution(object):
|
||||
@@ -131,6 +131,7 @@ class Resolution(object):
|
||||
state = State(
|
||||
mapping=base.mapping.copy(),
|
||||
criteria=base.criteria.copy(),
|
||||
backtrack_causes=base.backtrack_causes[:],
|
||||
)
|
||||
self._states.append(state)
|
||||
|
||||
@@ -173,6 +174,31 @@ class Resolution(object):
|
||||
raise RequirementsConflicted(criterion)
|
||||
criteria[identifier] = criterion
|
||||
|
||||
def _remove_information_from_criteria(self, criteria, parents):
|
||||
"""Remove information from parents of criteria.
|
||||
|
||||
Concretely, removes all values from each criterion's ``information``
|
||||
field that have one of ``parents`` as provider of the requirement.
|
||||
|
||||
:param criteria: The criteria to update.
|
||||
:param parents: Identifiers for which to remove information from all criteria.
|
||||
"""
|
||||
if not parents:
|
||||
return
|
||||
for key, criterion in criteria.items():
|
||||
criteria[key] = Criterion(
|
||||
criterion.candidates,
|
||||
[
|
||||
information
|
||||
for information in criterion.information
|
||||
if (
|
||||
information.parent is None
|
||||
or self._p.identify(information.parent) not in parents
|
||||
)
|
||||
],
|
||||
criterion.incompatibilities,
|
||||
)
|
||||
|
||||
def _get_preference(self, name):
|
||||
return self._p.get_preference(
|
||||
identifier=name,
|
||||
@@ -185,6 +211,7 @@ class Resolution(object):
|
||||
self.state.criteria,
|
||||
operator.attrgetter("information"),
|
||||
),
|
||||
backtrack_causes=self.state.backtrack_causes,
|
||||
)
|
||||
|
||||
def _is_current_pin_satisfying(self, name, criterion):
|
||||
@@ -211,6 +238,7 @@ class Resolution(object):
|
||||
try:
|
||||
criteria = self._get_updated_criteria(candidate)
|
||||
except RequirementsConflicted as e:
|
||||
self._r.rejecting_candidate(e.criterion, candidate)
|
||||
causes.append(e.criterion)
|
||||
continue
|
||||
|
||||
@@ -239,8 +267,8 @@ class Resolution(object):
|
||||
# end, signal for backtracking.
|
||||
return causes
|
||||
|
||||
def _backtrack(self):
|
||||
"""Perform backtracking.
|
||||
def _backjump(self, causes):
|
||||
"""Perform backjumping.
|
||||
|
||||
When we enter here, the stack is like this::
|
||||
|
||||
@@ -256,22 +284,46 @@ class Resolution(object):
|
||||
|
||||
Each iteration of the loop will:
|
||||
|
||||
1. Discard Z.
|
||||
2. Discard Y but remember its incompatibility information gathered
|
||||
1. Identify Z. The incompatibility is not always caused by the latest
|
||||
state. For example, given three requirements A, B and C, with
|
||||
dependencies A1, B1 and C1, where A1 and B1 are incompatible: the
|
||||
last state might be related to C, so we want to discard the
|
||||
previous state.
|
||||
2. Discard Z.
|
||||
3. Discard Y but remember its incompatibility information gathered
|
||||
previously, and the failure we're dealing with right now.
|
||||
3. Push a new state Y' based on X, and apply the incompatibility
|
||||
4. Push a new state Y' based on X, and apply the incompatibility
|
||||
information from Y to Y'.
|
||||
4a. If this causes Y' to conflict, we need to backtrack again. Make Y'
|
||||
5a. If this causes Y' to conflict, we need to backtrack again. Make Y'
|
||||
the new Z and go back to step 2.
|
||||
4b. If the incompatibilities apply cleanly, end backtracking.
|
||||
5b. If the incompatibilities apply cleanly, end backtracking.
|
||||
"""
|
||||
incompatible_reqs = itertools.chain(
|
||||
(c.parent for c in causes if c.parent is not None),
|
||||
(c.requirement for c in causes),
|
||||
)
|
||||
incompatible_deps = {self._p.identify(r) for r in incompatible_reqs}
|
||||
while len(self._states) >= 3:
|
||||
# Remove the state that triggered backtracking.
|
||||
del self._states[-1]
|
||||
|
||||
# Retrieve the last candidate pin and known incompatibilities.
|
||||
broken_state = self._states.pop()
|
||||
name, candidate = broken_state.mapping.popitem()
|
||||
# Ensure to backtrack to a state that caused the incompatibility
|
||||
incompatible_state = False
|
||||
while not incompatible_state:
|
||||
# Retrieve the last candidate pin and known incompatibilities.
|
||||
try:
|
||||
broken_state = self._states.pop()
|
||||
name, candidate = broken_state.mapping.popitem()
|
||||
except (IndexError, KeyError):
|
||||
raise ResolutionImpossible(causes)
|
||||
current_dependencies = {
|
||||
self._p.identify(d)
|
||||
for d in self._p.get_dependencies(candidate)
|
||||
}
|
||||
incompatible_state = not current_dependencies.isdisjoint(
|
||||
incompatible_deps
|
||||
)
|
||||
|
||||
incompatibilities_from_broken = [
|
||||
(k, list(v.incompatibilities))
|
||||
for k, v in broken_state.criteria.items()
|
||||
@@ -280,8 +332,6 @@ class Resolution(object):
|
||||
# Also mark the newly known incompatibility.
|
||||
incompatibilities_from_broken.append((name, [candidate]))
|
||||
|
||||
self._r.backtracking(candidate=candidate)
|
||||
|
||||
# Create a new state from the last known-to-work one, and apply
|
||||
# the previously gathered incompatibility information.
|
||||
def _patch_criteria():
|
||||
@@ -335,7 +385,13 @@ class Resolution(object):
|
||||
self._r.starting()
|
||||
|
||||
# Initialize the root state.
|
||||
self._states = [State(mapping=collections.OrderedDict(), criteria={})]
|
||||
self._states = [
|
||||
State(
|
||||
mapping=collections.OrderedDict(),
|
||||
criteria={},
|
||||
backtrack_causes=[],
|
||||
)
|
||||
]
|
||||
for r in requirements:
|
||||
try:
|
||||
self._add_to_criteria(self.state.criteria, r, parent=None)
|
||||
@@ -361,20 +417,38 @@ class Resolution(object):
|
||||
self._r.ending(state=self.state)
|
||||
return self.state
|
||||
|
||||
# keep track of satisfied names to calculate diff after pinning
|
||||
satisfied_names = set(self.state.criteria.keys()) - set(
|
||||
unsatisfied_names
|
||||
)
|
||||
|
||||
# Choose the most preferred unpinned criterion to try.
|
||||
name = min(unsatisfied_names, key=self._get_preference)
|
||||
failure_causes = self._attempt_to_pin_criterion(name)
|
||||
|
||||
if failure_causes:
|
||||
# Backtrack if pinning fails. The backtrack process puts us in
|
||||
causes = [i for c in failure_causes for i in c.information]
|
||||
# Backjump if pinning fails. The backjump process puts us in
|
||||
# an unpinned state, so we can work on it in the next round.
|
||||
success = self._backtrack()
|
||||
self._r.resolving_conflicts(causes=causes)
|
||||
success = self._backjump(causes)
|
||||
self.state.backtrack_causes[:] = causes
|
||||
|
||||
# Dead ends everywhere. Give up.
|
||||
if not success:
|
||||
causes = [i for c in failure_causes for i in c.information]
|
||||
raise ResolutionImpossible(causes)
|
||||
raise ResolutionImpossible(self.state.backtrack_causes)
|
||||
else:
|
||||
# discard as information sources any invalidated names
|
||||
# (unsatisfied names that were previously satisfied)
|
||||
newly_unsatisfied_names = {
|
||||
key
|
||||
for key, criterion in self.state.criteria.items()
|
||||
if key in satisfied_names
|
||||
and not self._is_current_pin_satisfying(key, criterion)
|
||||
}
|
||||
self._remove_information_from_criteria(
|
||||
self.state.criteria, newly_unsatisfied_names
|
||||
)
|
||||
# Pinning was successful. Push a new state to do another pin.
|
||||
self._push_new_state()
|
||||
|
||||
|
@@ -117,13 +117,14 @@ class _FactoryIterableView(object):
|
||||
|
||||
def __init__(self, factory):
|
||||
self._factory = factory
|
||||
self._iterable = None
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({})".format(type(self).__name__, list(self._factory()))
|
||||
return "{}({})".format(type(self).__name__, list(self))
|
||||
|
||||
def __bool__(self):
|
||||
try:
|
||||
next(self._factory())
|
||||
next(iter(self))
|
||||
except StopIteration:
|
||||
return False
|
||||
return True
|
||||
@@ -131,7 +132,11 @@ class _FactoryIterableView(object):
|
||||
__nonzero__ = __bool__ # XXX: Python 2.
|
||||
|
||||
def __iter__(self):
|
||||
return self._factory()
|
||||
iterable = (
|
||||
self._factory() if self._iterable is None else self._iterable
|
||||
)
|
||||
self._iterable, current = itertools.tee(iterable)
|
||||
return current
|
||||
|
||||
|
||||
class _SequenceIterableView(object):
|
||||
|
Reference in New Issue
Block a user