-
Notifications
You must be signed in to change notification settings - Fork 3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #7851 from pfmoore/resolvelib_provider
Implement PipProvider
- Loading branch information
Showing
8 changed files
with
335 additions
and
148 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
from pip._vendor.packaging.utils import canonicalize_name | ||
|
||
from pip._internal.req.constructors import install_req_from_line | ||
from pip._internal.req.req_install import InstallRequirement | ||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
from .base import Candidate | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from typing import Any, Dict, Optional, Sequence | ||
|
||
from pip._internal.models.link import Link | ||
from pip._internal.operations.prepare import RequirementPreparer | ||
from pip._internal.resolution.base import InstallRequirementProvider | ||
|
||
from pip._vendor.packaging.version import _BaseVersion | ||
from pip._vendor.pkg_resources import Distribution | ||
|
||
|
||
_CANDIDATE_CACHE = {} # type: Dict[Link, Candidate] | ||
|
||
|
||
def make_candidate( | ||
link, # type: Link | ||
preparer, # type: RequirementPreparer | ||
parent, # type: InstallRequirement | ||
make_install_req # type: InstallRequirementProvider | ||
): | ||
# type: (...) -> Candidate | ||
if link not in _CANDIDATE_CACHE: | ||
_CANDIDATE_CACHE[link] = LinkCandidate( | ||
link, | ||
preparer, | ||
parent=parent, | ||
make_install_req=make_install_req | ||
) | ||
return _CANDIDATE_CACHE[link] | ||
|
||
|
||
def make_install_req_from_link(link, parent): | ||
# type: (Link, InstallRequirement) -> InstallRequirement | ||
# TODO: Do we need to support editables? | ||
return install_req_from_line( | ||
link.url, | ||
comes_from=parent.comes_from, | ||
use_pep517=parent.use_pep517, | ||
isolated=parent.isolated, | ||
wheel_cache=parent._wheel_cache, | ||
constraint=parent.constraint, | ||
options=dict( | ||
install_options=parent.install_options, | ||
global_options=parent.global_options, | ||
hashes=parent.hash_options | ||
), | ||
) | ||
|
||
|
||
class LinkCandidate(Candidate): | ||
def __init__( | ||
self, | ||
link, # type: Link | ||
preparer, # type: RequirementPreparer | ||
parent, # type: InstallRequirement | ||
make_install_req, # type: InstallRequirementProvider | ||
): | ||
# type: (...) -> None | ||
self.link = link | ||
self._preparer = preparer | ||
self._ireq = make_install_req_from_link(link, parent) | ||
self._make_install_req = make_install_req | ||
|
||
self._name = None # type: Optional[str] | ||
self._version = None # type: Optional[_BaseVersion] | ||
self._dist = None # type: Optional[Distribution] | ||
|
||
def __eq__(self, other): | ||
# type: (Any) -> bool | ||
if isinstance(other, self.__class__): | ||
return self.link == other.link | ||
return False | ||
|
||
# Needed for Python 2, which does not implement this by default | ||
def __ne__(self, other): | ||
# type: (Any) -> bool | ||
return not self.__eq__(other) | ||
|
||
@property | ||
def name(self): | ||
# type: () -> str | ||
"""The normalised name of the project the candidate refers to""" | ||
if self._name is None: | ||
self._name = canonicalize_name(self.dist.project_name) | ||
return self._name | ||
|
||
@property | ||
def version(self): | ||
# type: () -> _BaseVersion | ||
if self._version is None: | ||
self._version = self.dist.parsed_version | ||
return self._version | ||
|
||
@property | ||
def dist(self): | ||
# type: () -> Distribution | ||
if self._dist is None: | ||
abstract_dist = self._preparer.prepare_linked_requirement( | ||
self._ireq | ||
) | ||
self._dist = abstract_dist.get_pkg_resources_distribution() | ||
# TODO: Only InstalledDistribution can return None here :-( | ||
assert self._dist is not None | ||
# These should be "proper" errors, not just asserts, as they | ||
# can result from user errors like a requirement "foo @ URL" | ||
# when the project at URL has a name of "bar" in its metadata. | ||
assert (self._name is None or | ||
self._name == canonicalize_name(self._dist.project_name)) | ||
assert (self._version is None or | ||
self._version == self.dist.parsed_version) | ||
return self._dist | ||
|
||
def get_dependencies(self): | ||
# type: () -> Sequence[InstallRequirement] | ||
return [ | ||
self._make_install_req(str(r), self._ireq) | ||
for r in self.dist.requires() | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
from pip._vendor.resolvelib.providers import AbstractProvider | ||
|
||
from pip._internal.utils.typing import MYPY_CHECK_RUNNING | ||
|
||
from .requirements import make_requirement | ||
|
||
if MYPY_CHECK_RUNNING: | ||
from typing import Any, Optional, Sequence, Tuple, Union | ||
|
||
from pip._internal.index.package_finder import PackageFinder | ||
from pip._internal.operations.prepare import RequirementPreparer | ||
from pip._internal.req.req_install import InstallRequirement | ||
from pip._internal.resolution.base import InstallRequirementProvider | ||
|
||
from .base import Requirement, Candidate | ||
|
||
|
||
class PipProvider(AbstractProvider): | ||
def __init__( | ||
self, | ||
finder, # type: PackageFinder | ||
preparer, # type: RequirementPreparer | ||
make_install_req # type: InstallRequirementProvider | ||
): | ||
# type: (...) -> None | ||
self._finder = finder | ||
self._preparer = preparer | ||
self._make_install_req = make_install_req | ||
|
||
def make_requirement(self, ireq): | ||
# type: (InstallRequirement) -> Requirement | ||
return make_requirement( | ||
ireq, | ||
self._finder, | ||
self._preparer, | ||
self._make_install_req | ||
) | ||
|
||
def get_install_requirement(self, c): | ||
# type: (Candidate) -> InstallRequirement | ||
|
||
# The base Candidate class does not have an _ireq attribute, so we | ||
# fetch it dynamically here, to satisfy mypy. In practice, though, we | ||
# only ever deal with LinkedCandidate objects at the moment, which do | ||
# have an _ireq attribute. When we have a candidate type for installed | ||
# requirements we should probably review this. | ||
# | ||
# TODO: Longer term, make a proper interface for this on the candidate. | ||
return getattr(c, "_ireq", None) | ||
|
||
def identify(self, dependency): | ||
# type: (Union[Requirement, Candidate]) -> str | ||
return dependency.name | ||
|
||
def get_preference( | ||
self, | ||
resolution, # type: Optional[Candidate] | ||
candidates, # type: Sequence[Candidate] | ||
information # type: Sequence[Tuple[Requirement, Candidate]] | ||
): | ||
# type: (...) -> Any | ||
# Use the "usual" value for now | ||
return len(candidates) | ||
|
||
def find_matches(self, requirement): | ||
# type: (Requirement) -> Sequence[Candidate] | ||
return requirement.find_matches() | ||
|
||
def is_satisfied_by(self, requirement, candidate): | ||
# type: (Requirement, Candidate) -> bool | ||
return requirement.is_satisfied_by(candidate) | ||
|
||
def get_dependencies(self, candidate): | ||
# type: (Candidate) -> Sequence[Requirement] | ||
return [ | ||
make_requirement( | ||
r, | ||
self._finder, | ||
self._preparer, | ||
self._make_install_req | ||
) | ||
for r in candidate.get_dependencies() | ||
] |
Oops, something went wrong.