197 lines
8.7 KiB
Python
197 lines
8.7 KiB
Python
![]() |
from __future__ import annotations
|
||
|
|
||
|
from typing import (
|
||
|
TYPE_CHECKING,
|
||
|
Generic,
|
||
|
Iterable,
|
||
|
Iterator,
|
||
|
Mapping,
|
||
|
Sequence,
|
||
|
)
|
||
|
|
||
|
from .structs import CT, KT, RT, Matches, RequirementInformation
|
||
|
|
||
|
if TYPE_CHECKING:
|
||
|
from typing import Any, Protocol
|
||
|
|
||
|
class Preference(Protocol):
|
||
|
def __lt__(self, __other: Any) -> bool: ...
|
||
|
|
||
|
|
||
|
class AbstractProvider(Generic[RT, CT, KT]):
|
||
|
"""Delegate class to provide the required interface for the resolver."""
|
||
|
|
||
|
def identify(self, requirement_or_candidate: RT | CT) -> KT:
|
||
|
"""Given a requirement or candidate, return an identifier for it.
|
||
|
|
||
|
This is used to identify, e.g. whether two requirements
|
||
|
should have their specifier parts merged or a candidate matches a
|
||
|
requirement via ``find_matches()``.
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def get_preference(
|
||
|
self,
|
||
|
identifier: KT,
|
||
|
resolutions: Mapping[KT, CT],
|
||
|
candidates: Mapping[KT, Iterator[CT]],
|
||
|
information: Mapping[KT, Iterator[RequirementInformation[RT, CT]]],
|
||
|
backtrack_causes: Sequence[RequirementInformation[RT, CT]],
|
||
|
) -> Preference:
|
||
|
"""Produce a sort key for given requirement based on preference.
|
||
|
|
||
|
As this is a sort key it will be called O(n) times per backtrack
|
||
|
step, where n is the number of `identifier`s, if you have a check
|
||
|
which is expensive in some sense. E.g. It needs to make O(n) checks
|
||
|
per call or takes significant wall clock time, consider using
|
||
|
`narrow_requirement_selection` to filter the `identifier`s, which
|
||
|
is applied before this sort key is called.
|
||
|
|
||
|
The preference is defined as "I think this requirement should be
|
||
|
resolved first". The lower the return value is, the more preferred
|
||
|
this group of arguments is.
|
||
|
|
||
|
:param identifier: An identifier as returned by ``identify()``. This
|
||
|
identifies the requirement being considered.
|
||
|
:param resolutions: Mapping of candidates currently pinned by the
|
||
|
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 are
|
||
|
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 (depended on) the
|
||
|
requirement, or ``None`` to indicate a root requirement.
|
||
|
|
||
|
The preference could depend on various issues, including (not
|
||
|
necessarily in this order):
|
||
|
|
||
|
* Is this package pinned in the current resolution result?
|
||
|
* How relaxed is the requirement? Stricter ones should probably be
|
||
|
worked on first? (I don't know, actually.)
|
||
|
* How many possibilities are there to satisfy this requirement? Those
|
||
|
with few left should likely be worked on first, I guess?
|
||
|
* Are there any known conflicts for this requirement? We should
|
||
|
probably work on those with the most known conflicts.
|
||
|
|
||
|
A sortable value should be returned (this will be used as the ``key``
|
||
|
parameter of the built-in sorting function). The smaller the value is,
|
||
|
the more preferred this requirement is (i.e. the sorting function
|
||
|
is called with ``reverse=False``).
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def find_matches(
|
||
|
self,
|
||
|
identifier: KT,
|
||
|
requirements: Mapping[KT, Iterator[RT]],
|
||
|
incompatibilities: Mapping[KT, Iterator[CT]],
|
||
|
) -> Matches[CT]:
|
||
|
"""Find all possible candidates that satisfy the given constraints.
|
||
|
|
||
|
:param identifier: An identifier as returned by ``identify()``. All
|
||
|
candidates returned by this method should produce the same
|
||
|
identifier.
|
||
|
:param requirements: A mapping of requirements that all returned
|
||
|
candidates must satisfy. Each key is an identifier, and the value
|
||
|
an iterator of requirements for that dependency.
|
||
|
:param incompatibilities: A mapping of known incompatibile candidates of
|
||
|
each dependency. Each key is an identifier, and the value an
|
||
|
iterator of incompatibilities known to the resolver. All
|
||
|
incompatibilities *must* be excluded from the return value.
|
||
|
|
||
|
This should try to get candidates based on the requirements' types.
|
||
|
For VCS, local, and archive requirements, the one-and-only match is
|
||
|
returned, and for a "named" requirement, the index(es) should be
|
||
|
consulted to find concrete candidates for this requirement.
|
||
|
|
||
|
The return value should produce candidates ordered by preference; the
|
||
|
most preferred candidate should come first. The return type may be one
|
||
|
of the following:
|
||
|
|
||
|
* A callable that returns an iterator that yields candidates.
|
||
|
* An collection of candidates.
|
||
|
* An iterable of candidates. This will be consumed immediately into a
|
||
|
list of candidates.
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def is_satisfied_by(self, requirement: RT, candidate: CT) -> bool:
|
||
|
"""Whether the given requirement can be satisfied by a candidate.
|
||
|
|
||
|
The candidate is guaranteed to have been generated from the
|
||
|
requirement.
|
||
|
|
||
|
A boolean should be returned to indicate whether ``candidate`` is a
|
||
|
viable solution to the requirement.
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def get_dependencies(self, candidate: CT) -> Iterable[RT]:
|
||
|
"""Get dependencies of a candidate.
|
||
|
|
||
|
This should return a collection of requirements that `candidate`
|
||
|
specifies as its dependencies.
|
||
|
"""
|
||
|
raise NotImplementedError
|
||
|
|
||
|
def narrow_requirement_selection(
|
||
|
self,
|
||
|
identifiers: 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]:
|
||
|
"""
|
||
|
An optional method to narrow the selection of requirements being
|
||
|
considered during resolution. This method is called O(1) time per
|
||
|
backtrack step.
|
||
|
|
||
|
:param identifiers: An iterable of `identifiers` as returned by
|
||
|
``identify()``. These identify all requirements currently being
|
||
|
considered.
|
||
|
:param resolutions: A mapping of candidates currently pinned by the
|
||
|
resolver. Each key is an identifier, and the value is a candidate
|
||
|
that may conflict with requirements from ``information``.
|
||
|
:param candidates: A mapping of each dependency's possible candidates.
|
||
|
Each value is an iterator of candidates.
|
||
|
:param information: A mapping of requirement information for each package.
|
||
|
Each value is an iterator of *requirement information*.
|
||
|
:param backtrack_causes: A sequence of *requirement information* that are
|
||
|
the requirements causing 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 (is depended on for)
|
||
|
the requirement, or ``None`` to indicate a root requirement.
|
||
|
|
||
|
Must return a non-empty subset of `identifiers`, with the default
|
||
|
implementation being to return `identifiers` unchanged. Those `identifiers`
|
||
|
will then be passed to the sort key `get_preference` to pick the most
|
||
|
prefered requirement to attempt to pin, unless `narrow_requirement_selection`
|
||
|
returns only 1 requirement, in which case that will be used without
|
||
|
calling the sort key `get_preference`.
|
||
|
|
||
|
This method is designed to be used by the provider to optimize the
|
||
|
dependency resolution, e.g. if a check cost is O(m) and it can be done
|
||
|
against all identifiers at once then filtering the requirement selection
|
||
|
here will cost O(m) but making it part of the sort key in `get_preference`
|
||
|
will cost O(m*n), where n is the number of `identifiers`.
|
||
|
|
||
|
Returns:
|
||
|
Iterable[KT]: A non-empty subset of `identifiers`.
|
||
|
"""
|
||
|
return identifiers
|