Update vendored ResolveLib to 0.4.0

This commit is contained in:
Tzu-ping Chung 2020-05-19 17:08:12 +08:00
parent be48ec0d15
commit 46f433615e
7 changed files with 75 additions and 61 deletions

View File

@ -11,7 +11,7 @@ __all__ = [
"ResolutionTooDeep", "ResolutionTooDeep",
] ]
__version__ = "0.3.0" __version__ = "0.4.0"
from .providers import AbstractProvider, AbstractResolver from .providers import AbstractProvider, AbstractResolver

View File

@ -0,0 +1,6 @@
__all__ = ["Sequence"]
try:
from collections.abc import Sequence
except ImportError:
from collections import Sequence

View File

@ -27,7 +27,7 @@ class AbstractProvider(object):
* `requirement` specifies a requirement contributing to the current * `requirement` specifies a requirement contributing to the current
candidate list candidate list
* `parent` specifies the candidate that provids (dependend on) the * `parent` specifies the candidate that provides (dependend on) the
requirement, or `None` to indicate a root requirement. requirement, or `None` to indicate a root requirement.
The preference could depend on a various of issues, including (not The preference could depend on a various of issues, including (not
@ -48,23 +48,28 @@ class AbstractProvider(object):
""" """
raise NotImplementedError raise NotImplementedError
def find_matches(self, requirement): def find_matches(self, requirements):
"""Find all possible candidates that satisfy a requirement. """Find all possible candidates that satisfy the given requirements.
This should try to get candidates based on the requirement's type. This should try to get candidates based on the requirements' types.
For VCS, local, and archive requirements, the one-and-only match is For VCS, local, and archive requirements, the one-and-only match is
returned, and for a "named" requirement, the index(es) should be returned, and for a "named" requirement, the index(es) should be
consulted to find concrete candidates for this requirement. consulted to find concrete candidates for this requirement.
The returned candidates should be sorted by reversed preference, e.g. :param requirements: A collection of requirements which all of the the
the most preferred should be LAST. This is done so list-popping can be returned candidates must match. All requirements are guaranteed to
as efficient as possible. have the same identifier. The collection is never empty.
:returns: An iterable that orders candidates by preference, e.g. the
most preferred candidate should come first.
""" """
raise NotImplementedError raise NotImplementedError
def is_satisfied_by(self, requirement, candidate): def is_satisfied_by(self, requirement, candidate):
"""Whether the given requirement can be satisfied by a candidate. """Whether the given requirement can be satisfied by a candidate.
The candidate is guarenteed to have been generated from the
requirement.
A boolean should be returned to indicate whether `candidate` is a A boolean should be returned to indicate whether `candidate` is a
viable solution to the requirement. viable solution to the requirement.
""" """
@ -92,30 +97,13 @@ class AbstractResolver(object):
def resolve(self, requirements, **kwargs): def resolve(self, requirements, **kwargs):
"""Take a collection of constraints, spit out the resolution result. """Take a collection of constraints, spit out the resolution result.
Parameters This returns a representation of the final resolution state, with one
---------- guarenteed attribute ``mapping`` that contains resolved candidates as
requirements : Collection values. The keys are their respective identifiers.
A collection of constraints
kwargs : optional
Additional keyword arguments that subclasses may accept.
Raises :param requirements: A collection of constraints.
------ :param kwargs: Additional keyword arguments that subclasses may accept.
self.base_exception
Any raised exception is guaranteed to be a subclass of
self.base_exception. The string representation of an exception
should be human readable and provide context for why it occurred.
Returns :raises: ``self.base_exception`` or its subclass.
-------
retval : object
A representation of the final resolution state. It can be any object
with a `mapping` attribute that is a Mapping. Other attributes can
be used to provide resolver-specific information.
The `mapping` attribute MUST be key-value pair is an identifier of a
requirement (as returned by the provider's `identify` method) mapped
to the resolved candidate (chosen from the return value of the
provider's `find_matches` method).
""" """
raise NotImplementedError raise NotImplementedError

View File

@ -23,12 +23,18 @@ class BaseReporter(object):
"""Called before the resolution ends successfully. """Called before the resolution ends successfully.
""" """
def adding_requirement(self, requirement): def adding_requirement(self, requirement, parent):
"""Called when the resolver adds a new requirement into the resolve criteria. """Called when adding a new requirement into the resolve criteria.
:param requirement: The additional requirement to be applied to filter
the available candidaites.
:param parent: The candidate that requires ``requirement`` as a
dependency, or None if ``requirement`` is one of the root
requirements passed in from ``Resolver.resolve()``.
""" """
def backtracking(self, candidate): def backtracking(self, candidate):
"""Called when the resolver rejects a candidate during backtracking. """Called when rejecting a candidate during backtracking.
""" """
def pinning(self, candidate): def pinning(self, candidate):

View File

@ -1,5 +1,6 @@
import collections import collections
from .compat import collections_abc
from .providers import AbstractResolver from .providers import AbstractResolver
from .structs import DirectedGraph from .structs import DirectedGraph
@ -68,16 +69,18 @@ class Criterion(object):
def __repr__(self): def __repr__(self):
requirements = ", ".join( requirements = ", ".join(
"{!r} from {!r}".format(req, parent) "({!r}, via={!r})".format(req, parent)
for req, parent in self.information for req, parent in self.information
) )
return "<Criterion {}>".format(requirements) return "Criterion({})".format(requirements)
@classmethod @classmethod
def from_requirement(cls, provider, requirement, parent): def from_requirement(cls, provider, requirement, parent):
"""Build an instance from a requirement. """Build an instance from a requirement.
""" """
candidates = provider.find_matches(requirement) candidates = provider.find_matches([requirement])
if not isinstance(candidates, collections_abc.Sequence):
candidates = list(candidates)
criterion = cls( criterion = cls(
candidates=candidates, candidates=candidates,
information=[RequirementInformation(requirement, parent)], information=[RequirementInformation(requirement, parent)],
@ -98,11 +101,9 @@ class Criterion(object):
""" """
infos = list(self.information) infos = list(self.information)
infos.append(RequirementInformation(requirement, parent)) infos.append(RequirementInformation(requirement, parent))
candidates = [ candidates = provider.find_matches([r for r, _ in infos])
c if not isinstance(candidates, collections_abc.Sequence):
for c in self.candidates candidates = list(candidates)
if provider.is_satisfied_by(requirement, c)
]
criterion = type(self)(candidates, infos, list(self.incompatibilities)) criterion = type(self)(candidates, infos, list(self.incompatibilities))
if not candidates: if not candidates:
raise RequirementsConflicted(criterion) raise RequirementsConflicted(criterion)
@ -179,7 +180,7 @@ class Resolution(object):
self._states.append(state) self._states.append(state)
def _merge_into_criterion(self, requirement, parent): def _merge_into_criterion(self, requirement, parent):
self._r.adding_requirement(requirement) self._r.adding_requirement(requirement, parent)
name = self._p.identify(requirement) name = self._p.identify(requirement)
try: try:
crit = self.state.criteria[name] crit = self.state.criteria[name]
@ -218,13 +219,24 @@ class Resolution(object):
def _attempt_to_pin_criterion(self, name, criterion): def _attempt_to_pin_criterion(self, name, criterion):
causes = [] causes = []
for candidate in reversed(criterion.candidates): for candidate in criterion.candidates:
try: try:
criteria = self._get_criteria_to_update(candidate) criteria = self._get_criteria_to_update(candidate)
except RequirementsConflicted as e: except RequirementsConflicted as e:
causes.append(e.criterion) causes.append(e.criterion)
continue 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
# to fix find_matches() and/or is_satisfied_by().
satisfied = all(
self._p.is_satisfied_by(r, candidate)
for r in criterion.iter_requirement()
)
if not satisfied:
raise InconsistentCandidate(candidate, criterion)
# Put newly-pinned candidate at the end. This is essential because # Put newly-pinned candidate at the end. This is essential because
# backtracking looks at this mapping to get the last pin. # backtracking looks at this mapping to get the last pin.
self._r.pinning(candidate) self._r.pinning(candidate)
@ -232,13 +244,6 @@ class Resolution(object):
self.state.mapping[name] = candidate self.state.mapping[name] = candidate
self.state.criteria.update(criteria) self.state.criteria.update(criteria)
# 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
# to fix find_matches() and/or is_satisfied_by().
if not self._is_current_pin_satisfying(name, criterion):
raise InconsistentCandidate(candidate, criterion)
return [] return []
# All candidates tried, nothing works. This criterion is a dead # All candidates tried, nothing works. This criterion is a dead
@ -246,23 +251,32 @@ class Resolution(object):
return causes return causes
def _backtrack(self): def _backtrack(self):
# We need at least 3 states here: # Drop the current state, it's known not to work.
# (a) One known not working, to drop. del self._states[-1]
# (b) One to backtrack to.
# (c) One to restore state (b) to its state prior to candidate-pinning,
# so we can pin another one instead.
while len(self._states) >= 3:
del self._states[-1]
# Retract the last candidate pin, and create a new (b). # We need at least 2 states here:
name, candidate = self._states.pop().mapping.popitem() # (a) One to backtrack to.
# (b) One to restore state (a) to its state prior to candidate-pinning,
# so we can pin another one instead.
while len(self._states) >= 2:
# Retract the last candidate pin.
prev_state = self._states.pop()
try:
name, candidate = prev_state.mapping.popitem()
except KeyError:
continue
self._r.backtracking(candidate) self._r.backtracking(candidate)
# Create a new state to work on, with the newly known not-working
# candidate excluded.
self._push_new_state() self._push_new_state()
# Mark the retracted candidate as incompatible. # Mark the retracted candidate as incompatible.
criterion = self.state.criteria[name].excluded_of(candidate) criterion = self.state.criteria[name].excluded_of(candidate)
if criterion is None: if criterion is None:
# This state still does not work. Try the still previous state. # This state still does not work. Try the still previous state.
del self._states[-1]
continue continue
self.state.criteria[name] = criterion self.state.criteria[name] = criterion

View File

@ -16,7 +16,7 @@ requests==2.23.0
chardet==3.0.4 chardet==3.0.4
idna==2.9 idna==2.9
urllib3==1.25.8 urllib3==1.25.8
resolvelib==0.3.0 resolvelib==0.4.0
retrying==1.3.3 retrying==1.3.3
setuptools==44.0.0 setuptools==44.0.0
six==1.14.0 six==1.14.0