Skip to content

pylock.select: fail on unknown extras and dependency groups - #1376

Open
sbidoul wants to merge 3 commits into
pypa:mainfrom
sbidoul:pylock-select-reject-unknown-groups-extras-sbi
Open

pylock.select: fail on unknown extras and dependency groups#1376
sbidoul wants to merge 3 commits into
pypa:mainfrom
sbidoul:pylock-select-reject-unknown-groups-extras-sbi

Conversation

@sbidoul

@sbidoul sbidoul commented Aug 9, 2026

Copy link
Copy Markdown
Member

With this PR, Pylock.select() now fails when provided extras or dependency_groups that are not declared in the lock file.

Strictly speaking this is a breaking change, since before it would silently skip packages.
However the API is young, and I feel that the current behaviour is surprising, so I propose this change.

Consumers that wish to ignore instead of failing can easily pre-filter (and likely warn their users in such cases).

Alternatives are enabling this new check with a flag, or doing nothing and leave such validation to consumers.

@sbidoul sbidoul changed the title pylocl.select: fail on unknown extras and dependency groups pylock.select: fail on unknown extras and dependency groups Aug 9, 2026
@sbidoul
sbidoul force-pushed the pylock-select-reject-unknown-groups-extras-sbi branch 2 times, most recently from e03728b to 99ab5eb Compare August 9, 2026 17:47
@sbidoul
sbidoul force-pushed the pylock-select-reject-unknown-groups-extras-sbi branch from 99ab5eb to c9cda9a Compare August 9, 2026 17:49
Comment thread src/packaging/pylock.py Outdated
…groups exist

The spec says membes of default-groups should not be listed in
dependency-groups.
@sbidoul
sbidoul force-pushed the pylock-select-reject-unknown-groups-extras-sbi branch from e11f62b to 588863c Compare August 10, 2026 06:43

@henryiii henryiii left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I also ran a review (below). We type this to a Sequence, so the correctness isn't technically correct, but if we could support Iterable instead easily (the old version did support one-shot iterables), I think that would be nice.

🤖 AI text below 🤖

  • Correctness: select() now iterates the extras and dependency_groups parameters twice (validation, then env building), so a one-shot iterable is exhausted by the new check and silently selects nothing instead of raising.
  • Simplification: The new validation block repeats the {canonicalize_name(x) for x in (seq or [])} comprehension four times, including two unioned comprehensions that could be one comprehension over the chained sequences.
Diff:
diff --git a/src/packaging/pylock.py b/src/packaging/pylock.py
index 81e51de..03107c0 100644
--- a/src/packaging/pylock.py
+++ b/src/packaging/pylock.py
@@ -653,34 +653,29 @@ class Pylock:
             Raise :class:`PylockSelectError` if passed extras or dependency groups
             that are not declared in the corresponding pylock fields.
         """
-        if extras:
-            unknown_extras = {canonicalize_name(extra) for extra in extras} - set(
-                self.extras or []
+        canonical_extras = {canonicalize_name(extra) for extra in extras or ()}
+        unknown_extras = canonical_extras - set(self.extras or ())
+        if unknown_extras:
+            raise PylockSelectError(
+                f"Undeclared extras: {', '.join(sorted(unknown_extras))}"
             )
-            if unknown_extras:
-                raise PylockSelectError(
-                    f"Undeclared extras: {', '.join(sorted(unknown_extras))}"
-                )
 
-        if dependency_groups:
-            unknown_dependency_groups = {
-                canonicalize_name(dependency_group)
-                for dependency_group in dependency_groups
-            } - (
-                {
-                    canonicalize_name(dependency_group)
-                    for dependency_group in self.dependency_groups or []
-                }
-                | {
-                    canonicalize_name(dependency_group)
-                    for dependency_group in self.default_groups or []
-                }
+        canonical_dependency_groups = {
+            canonicalize_name(dependency_group)
+            for dependency_group in dependency_groups or ()
+        }
+        unknown_dependency_groups = canonical_dependency_groups - {
+            canonicalize_name(dependency_group)
+            for dependency_group in [
+                *(self.dependency_groups or ()),
+                *(self.default_groups or ()),
+            ]
+        }
+        if unknown_dependency_groups:
+            raise PylockSelectError(
+                f"Undeclared dependency groups: "
+                f"{', '.join(sorted(unknown_dependency_groups))}"
             )
-            if unknown_dependency_groups:
-                raise PylockSelectError(
-                    f"Undeclared dependency groups: "
-                    f"{', '.join(sorted(unknown_dependency_groups))}"
-                )
 
         compatible_tags_selector = create_compatible_tags_selector(
             tags if tags is not None else sys_tags()
@@ -696,11 +691,11 @@ class Pylock:
             "dict[str, str | frozenset[str]]",
             dict(
                 environment or {},  # Marker.evaluate will fill-up
-                extras=frozenset(extras or []),
+                extras=frozenset(canonical_extras),
                 dependency_groups=frozenset(
                     (self.default_groups or [])
                     if dependency_groups is None  # to allow selecting no group
-                    else dependency_groups
+                    else canonical_dependency_groups
                 ),
             ),
         )

@sbidoul

sbidoul commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

@henryiii I applied the suggested diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants