Merge pull request #8126 from pfmoore/nr_upgrade_strategy

Implement upgrade strategies for the new resolver
This commit is contained in:
Paul Moore 2020-05-03 12:39:45 +01:00 committed by GitHub
commit 67e42423d0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 123 additions and 1 deletions

View File

@ -42,6 +42,8 @@ if MYPY_CHECK_RUNNING:
class Factory(object):
_allowed_strategies = {"eager", "only-if-needed", "to-satisfy-only"}
def __init__(
self,
finder, # type: PackageFinder
@ -50,15 +52,21 @@ class Factory(object):
force_reinstall, # type: bool
ignore_installed, # type: bool
ignore_requires_python, # type: bool
upgrade_strategy, # type: str
py_version_info=None, # type: Optional[Tuple[int, ...]]
):
# type: (...) -> None
assert upgrade_strategy in self._allowed_strategies
self.finder = finder
self.preparer = preparer
self._python_candidate = RequiresPythonCandidate(py_version_info)
self._make_install_req_from_spec = make_install_req
self._force_reinstall = force_reinstall
self._ignore_requires_python = ignore_requires_python
self._upgrade_strategy = upgrade_strategy
self.root_reqs = set() # type: Set[str]
self._link_candidate_cache = {} # type: Cache[LinkCandidate]
self._editable_candidate_cache = {} # type: Cache[EditableCandidate]
@ -110,13 +118,23 @@ class Factory(object):
return ExtrasCandidate(base, extras)
return base
def _eligible_for_upgrade(self, dist_name):
# type: (str) -> bool
if self._upgrade_strategy == "eager":
return True
elif self._upgrade_strategy == "only-if-needed":
return (dist_name in self.root_reqs)
return False
def iter_found_candidates(self, ireq, extras):
# type: (InstallRequirement, Set[str]) -> Iterator[Candidate]
name = canonicalize_name(ireq.req.name)
if not self._force_reinstall:
installed_dist = self._installed_dists.get(name)
can_upgrade = self._eligible_for_upgrade(name)
else:
installed_dist = None
can_upgrade = False
found = self.finder.find_best_candidate(
project_name=ireq.req.name,
@ -126,6 +144,12 @@ class Factory(object):
for ican in found.iter_applicable():
if (installed_dist is not None and
installed_dist.parsed_version == ican.version):
if can_upgrade:
yield self._make_candidate_from_dist(
dist=installed_dist,
extras=extras,
parent=ireq,
)
continue
yield self._make_candidate_from_link(
link=ican.link,
@ -138,6 +162,7 @@ class Factory(object):
# Return installed distribution if it matches the specifier. This is
# done last so the resolver will prefer it over downloading links.
if (installed_dist is not None and
not can_upgrade and
installed_dist.parsed_version in ireq.req.specifier):
yield self._make_candidate_from_dist(
dist=installed_dist,
@ -147,6 +172,9 @@ class Factory(object):
def make_requirement_from_install_req(self, ireq):
# type: (InstallRequirement) -> Requirement
if ireq.is_direct and ireq.name:
self.root_reqs.add(canonicalize_name(ireq.name))
if ireq.link:
# TODO: Get name and version from ireq, if possible?
# Specifically, this might be needed in "name @ URL"

View File

@ -53,6 +53,7 @@ class Resolver(BaseResolver):
force_reinstall=force_reinstall,
ignore_installed=ignore_installed,
ignore_requires_python=ignore_requires_python,
upgrade_strategy=upgrade_strategy,
py_version_info=py_version_info,
)
self.ignore_dependencies = ignore_dependencies
@ -61,6 +62,13 @@ class Resolver(BaseResolver):
def resolve(self, root_reqs, check_supported_wheels):
# type: (List[InstallRequirement], bool) -> RequirementSet
# The factory should not have retained state from any previous usage.
# In theory this could only happen if self was reused to do a second
# resolve, which isn't something we do at the moment. We assert here
# in order to catch the issue if that ever changes.
# The persistent state that we care about is `root_reqs`.
assert len(self.factory.root_reqs) == 0, "Factory is being re-used"
# FIXME: Implement constraints.
if any(r.constraint for r in root_reqs):
raise InstallationError("Constraints are not yet supported.")

View File

@ -531,6 +531,91 @@ def test_new_resolver_handles_prerelease(
assert_installed(script, pkg=expected_version)
def test_new_resolver_upgrade_needs_option(script):
# Install pkg 1.0.0
create_basic_wheel_for_package(script, "pkg", "1.0.0")
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"pkg",
)
# Now release a new version
create_basic_wheel_for_package(script, "pkg", "2.0.0")
# This should not upgrade because we don't specify --upgrade
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"pkg",
)
assert "Requirement already satisfied" in result.stdout, str(result)
assert_installed(script, pkg="1.0.0")
# This should upgrade
result = script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"--upgrade",
"PKG", # Deliberately uppercase to check canonicalization
)
assert "Uninstalling pkg-1.0.0" in result.stdout, str(result)
assert "Successfully uninstalled pkg-1.0.0" in result.stdout, str(result)
assert script.site_packages / "pkg" in result.files_updated, (
"pkg not upgraded"
)
assert_installed(script, pkg="2.0.0")
def test_new_resolver_upgrade_strategy(script):
create_basic_wheel_for_package(script, "base", "1.0.0", depends=["dep"])
create_basic_wheel_for_package(script, "dep", "1.0.0")
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"base",
)
assert_installed(script, base="1.0.0")
assert_installed(script, dep="1.0.0")
# Now release new versions
create_basic_wheel_for_package(script, "base", "2.0.0", depends=["dep"])
create_basic_wheel_for_package(script, "dep", "2.0.0")
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"--upgrade",
"base",
)
# With upgrade strategy "only-if-needed" (the default), dep should not
# be upgraded.
assert_installed(script, base="2.0.0")
assert_installed(script, dep="1.0.0")
create_basic_wheel_for_package(script, "base", "3.0.0", depends=["dep"])
script.pip(
"install", "--unstable-feature=resolver",
"--no-cache-dir", "--no-index",
"--find-links", script.scratch_path,
"--upgrade", "--upgrade-strategy=eager",
"base",
)
# With upgrade strategy "eager", dep should be upgraded.
assert_installed(script, base="3.0.0")
assert_installed(script, dep="2.0.0")
class TestExtraMerge(object):
"""
Test installing a package that depends the same package with different

View File

@ -55,6 +55,7 @@ def factory(finder, preparer):
force_reinstall=False,
ignore_installed=False,
ignore_requires_python=False,
upgrade_strategy="to-satisfy-only",
py_version_info=None,
)

View File

@ -24,7 +24,7 @@ def resolver(preparer, finder):
ignore_installed="not-used",
ignore_requires_python="not-used",
force_reinstall="not-used",
upgrade_strategy="not-used",
upgrade_strategy="to-satisfy-only",
)
return resolver