From 644710ce9e5a70b899710a4bf03df615cd9f0d75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 9 Oct 2021 19:26:38 +0200 Subject: [PATCH 1/4] Fix for "poetry lock --no-update" so that dependencies with extras are not updated unnecessarily --- src/poetry/mixology/version_solver.py | 19 ++++++++--- .../mixology/version_solver/test_with_lock.py | 32 +++++++++++++++++++ 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 0be0bf04220..fe95e6925fb 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -2,6 +2,7 @@ import time +from contextlib import suppress from typing import TYPE_CHECKING from poetry.core.packages.dependency import Dependency @@ -365,10 +366,20 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: ) return dependency.complete_name - try: - version = packages[0] - except IndexError: - version = None + version = None + if dependency.name not in self._use_latest: + # prefer locked version of compatible (not exact same) dependency; + # required in order to not unnecessarily update dependencies with + # extras, e.g. "coverage" vs. "coverage[toml]" + locked = self._locked.get(dependency.name, None) + if locked is not None: + for version in packages: + if version.version <= locked.version: + if version.version != locked.version: + version = None + break + with suppress(IndexError): + version = version or packages[0] if version is None: # If there are no versions that satisfy the constraint, diff --git a/tests/mixology/version_solver/test_with_lock.py b/tests/mixology/version_solver/test_with_lock.py index 42b8d77fb53..cc9e28f12d6 100644 --- a/tests/mixology/version_solver/test_with_lock.py +++ b/tests/mixology/version_solver/test_with_lock.py @@ -136,3 +136,35 @@ def test_with_compatible_locked_dependencies_use_latest( }, use_latest=["foo"], ) + + +def test_with_compatible_locked_dependencies_with_extras( + root: ProjectPackage, provider: Provider, repo: Repository +): + root.add_dependency(Factory.create_dependency("foo", "^1.0")) + + package_foo_0 = get_package("foo", "1.0.0") + package_foo_1 = get_package("foo", "1.0.1") + bar_extra_dep = Factory.create_dependency( + "bar", {"version": "^1.0", "extras": "extra"} + ) + for package_foo in (package_foo_0, package_foo_1): + package_foo.add_dependency(bar_extra_dep) + repo.add_package(package_foo) + + bar_deps = {"baz": {"version": "^1.0", "extras": ["extra"]}} + add_to_repo(repo, "bar", "1.0.0", bar_deps) + add_to_repo(repo, "bar", "1.0.1", bar_deps) + add_to_repo(repo, "baz", "1.0.0") + add_to_repo(repo, "baz", "1.0.1") + + check_solver_result( + root, + provider, + result={"foo": "1.0.0", "bar": "1.0.0", "baz": "1.0.0"}, + locked={ + "foo": get_package("foo", "1.0.0"), + "bar": get_package("bar", "1.0.0"), + "baz": get_package("baz", "1.0.0"), + }, + ) From d1dbddb33cf9c53fffeb76b6d653aa8f0a91bf15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 21 Nov 2021 07:57:11 +0100 Subject: [PATCH 2/4] None check instead of "or" --- src/poetry/mixology/version_solver.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index fe95e6925fb..0c2ea0a7596 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -378,8 +378,9 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: if version.version != locked.version: version = None break - with suppress(IndexError): - version = version or packages[0] + if version is None: + with suppress(IndexError): + version = packages[0] if version is None: # If there are no versions that satisfy the constraint, From 253986cd111a51be998ee992f56a034e3b5c59be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sun, 21 Nov 2021 09:27:09 +0100 Subject: [PATCH 3/4] Simplified logic (giving up possibility of early return when no matching version is found) --- src/poetry/mixology/version_solver.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 0c2ea0a7596..22ecc4e028c 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -372,12 +372,10 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: # required in order to not unnecessarily update dependencies with # extras, e.g. "coverage" vs. "coverage[toml]" locked = self._locked.get(dependency.name, None) - if locked is not None: - for version in packages: - if version.version <= locked.version: - if version.version != locked.version: - version = None - break + if locked is not None: + version = next( + (p for p in packages if p.version == locked.version), None + ) if version is None: with suppress(IndexError): version = packages[0] From 2234cebd0bb54df89068a4a47a5a79b6086a999b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Mon, 28 Feb 2022 17:30:44 +0100 Subject: [PATCH 4/4] Rename local variable for clarity --- src/poetry/mixology/version_solver.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/poetry/mixology/version_solver.py b/src/poetry/mixology/version_solver.py index 22ecc4e028c..0b581eaf877 100644 --- a/src/poetry/mixology/version_solver.py +++ b/src/poetry/mixology/version_solver.py @@ -366,21 +366,21 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: ) return dependency.complete_name - version = None + package = None if dependency.name not in self._use_latest: # prefer locked version of compatible (not exact same) dependency; # required in order to not unnecessarily update dependencies with # extras, e.g. "coverage" vs. "coverage[toml]" locked = self._locked.get(dependency.name, None) if locked is not None: - version = next( + package = next( (p for p in packages if p.version == locked.version), None ) - if version is None: + if package is None: with suppress(IndexError): - version = packages[0] + package = packages[0] - if version is None: + if package is None: # If there are no versions that satisfy the constraint, # add an incompatibility that indicates that. self._add_incompatibility( @@ -389,12 +389,12 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: return dependency.complete_name else: - version = locked + package = locked - version = self._provider.complete_package(version) + package = self._provider.complete_package(package) conflict = False - for incompatibility in self._provider.incompatibilities_for(version): + for incompatibility in self._provider.incompatibilities_for(package): self._add_incompatibility(incompatibility) # If an incompatibility is already satisfied, then selecting version @@ -409,9 +409,9 @@ def _get_min(dependency: Dependency) -> tuple[bool, int]: ) if not conflict: - self._solution.decide(version) + self._solution.decide(package) self._log( - f"selecting {version.complete_name} ({version.full_pretty_version})" + f"selecting {package.complete_name} ({package.full_pretty_version})" ) return dependency.complete_name