Skip to content

Commit

Permalink
fix: take project.dynamic into account to decide if poetry dependen…
Browse files Browse the repository at this point in the history
…cies 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.
  • Loading branch information
radoering committed Jan 11, 2025
1 parent 8b66b86 commit 3d67a46
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 14 deletions.
8 changes: 7 additions & 1 deletion src/poetry/core/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
8 changes: 6 additions & 2 deletions src/poetry/core/packages/dependency_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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] = []

Expand All @@ -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,
Expand Down
64 changes: 53 additions & 11 deletions tests/packages/test_dependency_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def create_dependency(
return dep


@pytest.mark.parametrize("mixed_dynamic", [False, True])
@pytest.mark.parametrize(
(
"dependencies",
Expand All @@ -57,20 +58,20 @@ 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"}),
),
(
{
Dependency("foo", "*", optional=True),
Dependency("baz", "*", optional=True),
},
{Dependency("bar", "*")},
{"foo", "bar", "baz"},
({"foo", "baz"}, {"foo", "bar", "baz"}),
),
(
{
Expand All @@ -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(
Expand Down Expand Up @@ -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",
Expand All @@ -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
(
Expand Down Expand Up @@ -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!
(
Expand Down Expand Up @@ -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] == [
Expand Down

0 comments on commit 3d67a46

Please sign in to comment.