From 2c5394a8c014573422bb23147c2050109ad59ddc Mon Sep 17 00:00:00 2001 From: srikaaviya Date: Thu, 5 Mar 2026 14:56:47 -0800 Subject: [PATCH 1/3] fix: warn when group name is normalized in --with/--without/--only --- src/poetry/console/commands/group_command.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/poetry/console/commands/group_command.py b/src/poetry/console/commands/group_command.py index d876884fe55..1030fd0d09a 100644 --- a/src/poetry/console/commands/group_command.py +++ b/src/poetry/console/commands/group_command.py @@ -103,6 +103,16 @@ def activated_groups(self) -> set[NormalizedName]: key: {canonicalize_name(group) for group in key_groups} for key, key_groups in groups.items() } + # Warn if any group name was normalized + for key_groups in groups.values(): + for group in key_groups: + normalized = canonicalize_name(group) + if normalized != group: + self.line_error( + f"Group '{group}' was normalized to '{normalized}'." + " Consider updating your command to use the normalized name." + ) + norm_default_groups = {canonicalize_name(name) for name in self.default_groups} return norm_groups["only"] or norm_default_groups.union( @@ -121,7 +131,9 @@ def _validate_group_options(self, group_options: dict[str, set[str]]) -> None: invalid_options = defaultdict(set) for opt, groups in group_options.items(): for group in groups: - if not self.poetry.package.has_dependency_group(group): + if not self.poetry.package.has_dependency_group( + canonicalize_name(group) + ): invalid_options[group].add(opt) if invalid_options: message_parts = [] From e4c2d99f9bed40b17e0d6e77d913702760b781a9 Mon Sep 17 00:00:00 2001 From: srikaaviya Date: Thu, 5 Mar 2026 15:05:54 -0800 Subject: [PATCH 2/3] tests for fix: warn when group names are normalized options --- tests/console/commands/test_install.py | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index d5590dd77e1..5111b08f45a 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -396,6 +396,47 @@ def test_invalid_groups_with_without_only( ) +@pytest.mark.parametrize( + "option", + ["--with", "--without", "--only"], +) +def test_non_normalized_group_name_emits_warning( + tester: CommandTester, + mocker: MockerFixture, + option: str, +) -> None: + """ + A warning is emitted when a group name passed via --with/--without/--only + differs from its normalized form (e.g. 'Foo' instead of 'foo'). + """ + assert isinstance(tester.command, InstallerCommand) + mocker.patch.object(tester.command.installer, "run", return_value=0) + + # 'Foo' normalizes to 'foo', which exists in the fixture pyproject + tester.execute(f"{option} Foo") + + error_output = tester.io.fetch_error() + assert "was normalized to" in error_output + assert "'Foo'" in error_output + assert "'foo'" in error_output + + +def test_normalized_group_name_does_not_raise( + tester: CommandTester, + mocker: MockerFixture, +) -> None: + """ + Passing a non-normalized but valid group name (e.g. 'Foo' for group 'foo') + should not raise a GroupNotFoundError. + """ + assert isinstance(tester.command, InstallerCommand) + mocker.patch.object(tester.command.installer, "run", return_value=0) + + # Should not raise even though 'Foo' != 'foo', because they normalize to the same name + tester.execute("--with Foo") + assert tester.status_code != 1 or "not found" not in tester.io.fetch_error() + + def test_dry_run_populates_installer( tester: CommandTester, mocker: MockerFixture ) -> None: From 32fb4ecfda0df54fccd885ab9e0620518e11af21 Mon Sep 17 00:00:00 2001 From: srikaaviya Date: Fri, 6 Mar 2026 04:45:39 -0800 Subject: [PATCH 3/3] address review: deduplicate warnings and strengthen tests --- src/poetry/console/commands/group_command.py | 6 ++-- tests/console/commands/test_install.py | 36 ++++++++++++++------ 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/poetry/console/commands/group_command.py b/src/poetry/console/commands/group_command.py index 1030fd0d09a..86c031fd1ed 100644 --- a/src/poetry/console/commands/group_command.py +++ b/src/poetry/console/commands/group_command.py @@ -103,11 +103,13 @@ def activated_groups(self) -> set[NormalizedName]: key: {canonicalize_name(group) for group in key_groups} for key, key_groups in groups.items() } - # Warn if any group name was normalized + # Warn if any group name was normalized (deduplicate warnings) + warned: set[str] = set() for key_groups in groups.values(): for group in key_groups: normalized = canonicalize_name(group) - if normalized != group: + if normalized != group and group not in warned: + warned.add(group) self.line_error( f"Group '{group}' was normalized to '{normalized}'." " Consider updating your command to use the normalized name." diff --git a/tests/console/commands/test_install.py b/tests/console/commands/test_install.py index 5111b08f45a..155cd5bdde0 100644 --- a/tests/console/commands/test_install.py +++ b/tests/console/commands/test_install.py @@ -397,13 +397,21 @@ def test_invalid_groups_with_without_only( @pytest.mark.parametrize( - "option", - ["--with", "--without", "--only"], + ("option", "group_input", "normalized"), + [ + ("--with", "Foo", "foo"), + ("--without", "Foo", "foo"), + ("--only", "Foo", "foo"), + ("--with", "FOO", "foo"), + ("--with", "bim", "bim"), + ], ) def test_non_normalized_group_name_emits_warning( tester: CommandTester, mocker: MockerFixture, option: str, + group_input: str, + normalized: str, ) -> None: """ A warning is emitted when a group name passed via --with/--without/--only @@ -412,13 +420,15 @@ def test_non_normalized_group_name_emits_warning( assert isinstance(tester.command, InstallerCommand) mocker.patch.object(tester.command.installer, "run", return_value=0) - # 'Foo' normalizes to 'foo', which exists in the fixture pyproject - tester.execute(f"{option} Foo") - + tester.execute(f"{option} {group_input}") error_output = tester.io.fetch_error() - assert "was normalized to" in error_output - assert "'Foo'" in error_output - assert "'foo'" in error_output + + if group_input != normalized: + assert "was normalized to" in error_output + assert f"'{group_input}'" in error_output + assert f"'{normalized}'" in error_output + else: + assert "was normalized to" not in error_output def test_normalized_group_name_does_not_raise( @@ -433,8 +443,14 @@ def test_normalized_group_name_does_not_raise( mocker.patch.object(tester.command.installer, "run", return_value=0) # Should not raise even though 'Foo' != 'foo', because they normalize to the same name - tester.execute("--with Foo") - assert tester.status_code != 1 or "not found" not in tester.io.fetch_error() + # Use --no-root to avoid editable build failure in the test project fixture + tester.execute("--with Foo --no-root") + error_output = tester.io.fetch_error() + assert tester.status_code == 0 + assert "GroupNotFoundError" not in error_output + assert "not found" not in error_output + assert "was normalized to" in error_output + assert "'Foo'" in error_output def test_dry_run_populates_installer(