From 3d67a462ceb5ba14299d9a11b2b7b90c8f02d028 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Randy=20D=C3=B6ring?= <30527984+radoering@users.noreply.github.com> Date: Sat, 11 Jan 2025 06:12:39 +0100 Subject: [PATCH] fix: take `project.dynamic` into account to decide if poetry dependencies are used to define or only to enrich project dependencies This is especially relevant for projects that do not have any mandatory dependencies but optional dependencies and use `tool.poetry.dependencies` to define sources for these optional dependencies. Without this change the dependencies in `tool.poetry.dependencies` would have been considered mandatory dependencies in this case. --- src/poetry/core/factory.py | 8 ++- src/poetry/core/packages/dependency_group.py | 8 ++- tests/packages/test_dependency_group.py | 64 ++++++++++++++++---- 3 files changed, 66 insertions(+), 14 deletions(-) diff --git a/src/poetry/core/factory.py b/src/poetry/core/factory.py index 8e12ae5c4..d2d637214 100644 --- a/src/poetry/core/factory.py +++ b/src/poetry/core/factory.py @@ -284,10 +284,16 @@ def _configure_package_dependencies( dependencies = project.get("dependencies", {}) optional_dependencies = project.get("optional-dependencies", {}) + dynamic = project.get("dynamic", []) package_extras: dict[NormalizedName, list[Dependency]] if dependencies or optional_dependencies: - group = DependencyGroup(MAIN_GROUP) + group = DependencyGroup( + MAIN_GROUP, + mixed_dynamic=( + "dependencies" in dynamic or "optional-dependencies" in dynamic + ), + ) package.add_dependency_group(group) for constraint in dependencies: diff --git a/src/poetry/core/packages/dependency_group.py b/src/poetry/core/packages/dependency_group.py index 2da7f3065..a9536b6db 100644 --- a/src/poetry/core/packages/dependency_group.py +++ b/src/poetry/core/packages/dependency_group.py @@ -14,9 +14,12 @@ class DependencyGroup: - def __init__(self, name: str, optional: bool = False) -> None: + def __init__( + self, name: str, *, optional: bool = False, mixed_dynamic: bool = False + ) -> None: self._name: str = name self._optional: bool = optional + self._mixed_dynamic = mixed_dynamic self._dependencies: list[Dependency] = [] self._poetry_dependencies: list[Dependency] = [] @@ -27,8 +30,9 @@ def name(self) -> str: @property def dependencies(self) -> list[Dependency]: if not self._dependencies: + # legacy mode return self._poetry_dependencies - if self._poetry_dependencies: + if self._mixed_dynamic and self._poetry_dependencies: if all(dep.is_optional() for dep in self._dependencies): return [ *self._dependencies, diff --git a/tests/packages/test_dependency_group.py b/tests/packages/test_dependency_group.py index a3cb1ac72..14656ff36 100644 --- a/tests/packages/test_dependency_group.py +++ b/tests/packages/test_dependency_group.py @@ -43,6 +43,7 @@ def create_dependency( return dep +@pytest.mark.parametrize("mixed_dynamic", [False, True]) @pytest.mark.parametrize( ( "dependencies", @@ -57,12 +58,12 @@ def create_dependency( ( {Dependency("foo", "*", optional=True)}, {Dependency("bar", "*")}, - {"foo", "bar"}, + ({"foo"}, {"foo", "bar"}), ), ( {Dependency("foo", "*")}, {Dependency("bar", "*", optional=True)}, - {"foo", "bar"}, + ({"foo"}, {"foo", "bar"}), ), ( { @@ -70,7 +71,7 @@ def create_dependency( Dependency("baz", "*", optional=True), }, {Dependency("bar", "*")}, - {"foo", "bar", "baz"}, + ({"foo", "baz"}, {"foo", "bar", "baz"}), ), ( { @@ -83,20 +84,25 @@ def create_dependency( ( {Dependency("foo", "*", optional=True)}, {Dependency("bar", "*"), Dependency("baz", "*", optional=True)}, - {"foo", "bar"}, + ({"foo"}, {"foo", "bar"}), ), ], ) def test_dependencies( dependencies: set[Dependency], poetry_dependencies: set[Dependency], - expected_dependencies: set[str], + mixed_dynamic: bool, + expected_dependencies: set[str] | tuple[set[str], set[str]], ) -> None: - group = DependencyGroup(name="group") + group = DependencyGroup(name="group", mixed_dynamic=mixed_dynamic) group._dependencies = list(dependencies) group._poetry_dependencies = list(poetry_dependencies) - assert {d.name for d in group.dependencies} == set(expected_dependencies) + if isinstance(expected_dependencies, tuple): + expected_dependencies = ( + expected_dependencies[1] if mixed_dynamic else expected_dependencies[0] + ) + assert {d.name for d in group.dependencies} == expected_dependencies @pytest.mark.parametrize( @@ -147,6 +153,7 @@ def test_remove_dependency_removes_from_both_lists() -> None: assert {d.name for d in group._poetry_dependencies} == {"baz"} +@pytest.mark.parametrize("mixed_dynamic", [False, True]) @pytest.mark.parametrize( ( "dependencies", @@ -164,12 +171,24 @@ def test_remove_dependency_removes_from_both_lists() -> None: ( [create_dependency("foo", in_extras=("extra1",))], [create_dependency("bar")], - [create_dependency("foo", in_extras=("extra1",)), create_dependency("bar")], + ( + [create_dependency("foo", in_extras=("extra1",))], + [ + create_dependency("foo", in_extras=("extra1",)), + create_dependency("bar"), + ], + ), ), ( [create_dependency("foo")], [create_dependency("bar", in_extras=("extra1",))], - [create_dependency("foo"), create_dependency("bar", in_extras=("extra1",))], + ( + [create_dependency("foo")], + [ + create_dependency("foo"), + create_dependency("bar", in_extras=("extra1",)), + ], + ), ), # refine constraint ( @@ -311,6 +330,23 @@ def test_remove_dependency_removes_from_both_lists() -> None: [create_dependency("foo", source_name="src", optional=True)], [create_dependency("foo", source_name="src", marker="extra == 'extra1'")], ), + ( + [Dependency.create_from_pep_508("foo;extra=='extra1'")], + [create_dependency("foo", source_name="src")], + ( + [ + create_dependency( + "foo", source_name="src", marker="extra == 'extra1'" + ) + ], + [ + create_dependency( + "foo", source_name="src", marker="extra == 'extra1'" + ), + create_dependency("foo", source_name="src"), + ], + ), + ), # extras - special # root extras do not have an extra marker, they just have set _in_extras! ( @@ -398,12 +434,18 @@ def test_remove_dependency_removes_from_both_lists() -> None: def test_dependencies_for_locking( dependencies: list[Dependency], poetry_dependencies: list[Dependency], - expected_dependencies: list[Dependency], + mixed_dynamic: bool, + expected_dependencies: list[Dependency] | tuple[list[Dependency], list[Dependency]], ) -> None: - group = DependencyGroup(name="group") + group = DependencyGroup(name="group", mixed_dynamic=mixed_dynamic) group._dependencies = dependencies group._poetry_dependencies = poetry_dependencies + if isinstance(expected_dependencies, tuple): + expected_dependencies = ( + expected_dependencies[1] if mixed_dynamic else expected_dependencies[0] + ) + assert group.dependencies_for_locking == expected_dependencies # explicitly check attributes that are not considered in __eq__ assert [d.allows_prereleases() for d in group.dependencies_for_locking] == [