未验证 提交 87aaae0e 编写于 作者: P Paul Moore 提交者: GitHub

Merge pull request #7942 from uranusjr/installed-candidate

New resolver: Installed candidate
......@@ -14,7 +14,7 @@ from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .base import Candidate, format_name
if MYPY_CHECK_RUNNING:
from typing import Any, Optional, Sequence, Set, Tuple
from typing import Any, Optional, Sequence, Set, Tuple, Union
from pip._vendor.packaging.version import _BaseVersion
from pip._vendor.pkg_resources import Distribution
......@@ -45,6 +45,28 @@ def make_install_req_from_link(link, parent):
)
def make_install_req_from_dist(dist, parent):
# type: (Distribution, InstallRequirement) -> InstallRequirement
# TODO: Do we need to support editables?
ireq = install_req_from_line(
"{}=={}".format(
canonicalize_name(dist.project_name),
dist.parsed_version,
),
comes_from=parent.comes_from,
use_pep517=parent.use_pep517,
isolated=parent.isolated,
constraint=parent.constraint,
options=dict(
install_options=parent.install_options,
global_options=parent.global_options,
hashes=parent.hash_options
),
)
ireq.satisfied_by = dist
return ireq
class LinkCandidate(Candidate):
def __init__(self, link, parent, factory):
# type: (Link, InstallRequirement, Factory) -> None
......@@ -132,7 +154,48 @@ class LinkCandidate(Candidate):
return self._ireq
class ExtrasCandidate(LinkCandidate):
class AlreadyInstalledCandidate(Candidate):
def __init__(
self,
dist, # type: Distribution
parent, # type: InstallRequirement
factory, # type: Factory
):
# type: (...) -> None
self.dist = dist
self._ireq = make_install_req_from_dist(dist, parent)
self._factory = factory
# This is just logging some messages, so we can do it eagerly.
# The returned dist would be exactly the same as self.dist because we
# set satisfied_by in make_install_req_from_dist.
# TODO: Supply reason based on force_reinstall and upgrade_strategy.
skip_reason = "already satisfied"
factory.preparer.prepare_installed_requirement(self._ireq, skip_reason)
@property
def name(self):
# type: () -> str
return canonicalize_name(self.dist.project_name)
@property
def version(self):
# type: () -> _BaseVersion
return self.dist.parsed_version
def get_dependencies(self):
# type: () -> Sequence[Requirement]
return [
self._factory.make_requirement_from_spec(str(r), self._ireq)
for r in self.dist.requires()
]
def get_install_requirement(self):
# type: () -> Optional[InstallRequirement]
return None
class ExtrasCandidate(Candidate):
"""A candidate that has 'extras', indicating additional dependencies.
Requirements can be for a project with dependencies, something like
......@@ -156,11 +219,14 @@ class ExtrasCandidate(LinkCandidate):
version 2.0. Having those candidates depend on foo=1.0 and foo=2.0
respectively forces the resolver to recognise that this is a conflict.
"""
def __init__(self, base, extras):
# type: (LinkCandidate, Set[str]) -> None
def __init__(
self,
base, # type: Union[AlreadyInstalledCandidate, LinkCandidate]
extras, # type: Set[str]
):
# type: (...) -> None
self.base = base
self.extras = extras
self.link = base.link
@property
def name(self):
......
from pip._vendor.pkg_resources import (
DistributionNotFound,
VersionConflict,
get_distribution,
)
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
from .candidates import ExtrasCandidate, LinkCandidate, RequiresPythonCandidate
from .candidates import (
AlreadyInstalledCandidate,
ExtrasCandidate,
LinkCandidate,
RequiresPythonCandidate,
)
from .requirements import (
ExplicitRequirement,
NoMatchRequirement,
......@@ -11,8 +22,10 @@ if MYPY_CHECK_RUNNING:
from typing import Dict, Optional, Set, Tuple
from pip._vendor.packaging.specifiers import SpecifierSet
from pip._vendor.pkg_resources import Distribution
from pip._internal.index.package_finder import PackageFinder
from pip._internal.models.candidate import InstallationCandidate
from pip._internal.models.link import Link
from pip._internal.operations.prepare import RequirementPreparer
from pip._internal.req.req_install import InstallRequirement
......@@ -27,6 +40,7 @@ class Factory(object):
finder, # type: PackageFinder
preparer, # type: RequirementPreparer
make_install_req, # type: InstallRequirementProvider
ignore_installed, # type: bool
ignore_requires_python, # type: bool
py_version_info=None, # type: Optional[Tuple[int, ...]]
):
......@@ -34,33 +48,78 @@ class Factory(object):
self.finder = finder
self.preparer = preparer
self._python_candidate = RequiresPythonCandidate(py_version_info)
self._ignore_requires_python = ignore_requires_python
self._make_install_req_from_spec = make_install_req
self._candidate_cache = {} # type: Dict[Link, LinkCandidate]
self._ignore_installed = ignore_installed
self._ignore_requires_python = ignore_requires_python
self._link_candidate_cache = {} # type: Dict[Link, LinkCandidate]
def make_candidate(
def _make_candidate_from_dist(
self,
dist, # type: Distribution
extras, # type: Set[str]
parent, # type: InstallRequirement
):
# type: (...) -> Candidate
base = AlreadyInstalledCandidate(dist, parent, factory=self)
if extras:
return ExtrasCandidate(base, extras)
return base
def _make_candidate_from_link(
self,
link, # type: Link
extras, # type: Set[str]
parent, # type: InstallRequirement
):
# type: (...) -> Candidate
if link not in self._candidate_cache:
self._candidate_cache[link] = LinkCandidate(
if link not in self._link_candidate_cache:
self._link_candidate_cache[link] = LinkCandidate(
link, parent, factory=self,
)
base = self._candidate_cache[link]
base = self._link_candidate_cache[link]
if extras:
return ExtrasCandidate(base, extras)
return base
def _get_installed_distribution(self, name, version):
# type: (str, str) -> Optional[Distribution]
if self._ignore_installed:
return None
specifier = "{}=={}".format(name, version)
try:
dist = get_distribution(specifier)
except (DistributionNotFound, VersionConflict):
return None
return dist
def make_candidate_from_ican(
self,
ican, # type: InstallationCandidate
extras, # type: Set[str]
parent, # type: InstallRequirement
):
# type: (...) -> Candidate
dist = self._get_installed_distribution(ican.name, ican.version)
if dist is None:
return self._make_candidate_from_link(
link=ican.link,
extras=extras,
parent=parent,
)
return self._make_candidate_from_dist(
dist=dist,
extras=extras,
parent=parent,
)
def make_requirement_from_install_req(self, ireq):
# type: (InstallRequirement) -> Requirement
if ireq.link:
cand = self.make_candidate(ireq.link, extras=set(), parent=ireq)
cand = self._make_candidate_from_link(
ireq.link, extras=set(), parent=ireq,
)
return ExplicitRequirement(cand)
else:
return SpecifierRequirement(ireq, factory=self)
return SpecifierRequirement(ireq, factory=self)
def make_requirement_from_spec(self, specifier, comes_from):
# type: (str, InstallRequirement) -> Requirement
......
......@@ -79,8 +79,8 @@ class SpecifierRequirement(Requirement):
hashes=self._ireq.hashes(trust_internet=False),
)
return [
self._factory.make_candidate(
link=ican.link,
self._factory.make_candidate_from_ican(
ican=ican,
extras=self.extras,
parent=self._ireq,
)
......
......@@ -43,6 +43,7 @@ class Resolver(BaseResolver):
finder=finder,
preparer=preparer,
make_install_req=make_install_req,
ignore_installed=ignore_installed,
ignore_requires_python=ignore_requires_python,
py_version_info=py_version_info,
)
......
......@@ -191,3 +191,65 @@ def test_new_resolver_requires_python(
script.pip(*args)
assert_installed(script, base="0.1.0", dep=dep_version)
def test_new_resolver_installed(script):
create_basic_wheel_for_package(
script,
"base",
"0.1.0",
depends=["dep"],
)
create_basic_wheel_for_package(
script,
"dep",
"0.1.0",
)
satisfied_output = "Requirement already satisfied: base==0.1.0 in"
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"base",
)
assert satisfied_output not in result.stdout, str(result)
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"base",
)
assert satisfied_output in result.stdout, str(result)
assert script.site_packages / "base" not in result.files_updated, (
"base 0.1.0 reinstalled"
)
def test_new_resolver_ignore_installed(script):
create_basic_wheel_for_package(
script,
"base",
"0.1.0",
)
satisfied_output = "Requirement already satisfied: base==0.1.0 in"
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"base",
)
assert satisfied_output not in result.stdout, str(result)
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index", "--ignore-installed",
"--find-links", script.scratch_path,
"base",
)
assert satisfied_output not in result.stdout, str(result)
assert script.site_packages / "base" in result.files_updated, (
"base 0.1.0 not reinstalled"
)
......@@ -52,6 +52,7 @@ def factory(finder, preparer):
finder=finder,
preparer=preparer,
make_install_req=install_req_from_line,
ignore_installed=False,
ignore_requires_python=False,
py_version_info=None,
)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册