Skip to content

Feature: Add --with-groups flag to show groups for each direct dependency#10687

Closed
ammargit93 wants to merge 7 commits intopython-poetry:mainfrom
ammargit93:feat/show-with-groups
Closed

Feature: Add --with-groups flag to show groups for each direct dependency#10687
ammargit93 wants to merge 7 commits intopython-poetry:mainfrom
ammargit93:feat/show-with-groups

Conversation

@ammargit93
Copy link
Contributor

@ammargit93 ammargit93 commented Jan 12, 2026

Pull Request Check List

  • Added tests for changed code.
  • Updated documentation for changed code.

This PR aims to resolve #7519 and is closely related to #8164.

Changes

Added a boolean flag --with-groups to poetry show.
When enabled:

  • Adds a new column after the version showing the groups each dependency belongs to.
  • Only direct dependencies (from pyproject.toml) are annotated with groups.
  • Transitive dependencies are not labeled, to avoid misleading inheritance of groups.

JSON output:

  • Adds a "groups" field containing a list of groups for each dependency.

--tree:

  • Using --with-groups together with --tree raises a clear error (tree view doesn’t support group annotations).

output

image ## Summary by Sourcery

Add support for displaying dependency groups in poetry show, including CLI flag handling and output formatting updates.

New Features:

  • Introduce a --with-groups flag to poetry show to display the groups for each dependency in tabular and JSON output.
  • Add a dependency-to-group mapping based on pyproject.toml project dependencies and dependency groups, exposing group information per package.

Enhancements:

  • Prevent using --tree together with --with-groups by returning a clear error when both flags are supplied.
  • Extend column width and layout calculations in poetry show to account for the new groups column in the human-readable output.

Summary by Sourcery

Add support for displaying dependency groups in poetry show, including a new CLI flag and corresponding output changes.

New Features:

  • Introduce a --with-groups flag to poetry show to display the groups for each dependency.
  • Expose dependency group information for direct dependencies in both human-readable and JSON outputs of poetry show.

Enhancements:

  • Prevent using --tree together with --with-groups by returning a clear error when both flags are supplied.
  • Adjust column width and layout calculations in poetry show to account for the new groups column in the tabular output.

Documentation:

  • Document the new --with-groups option in the CLI reference.

@sourcery-ai
Copy link

sourcery-ai bot commented Jan 12, 2026

Reviewer's Guide

Adds a --with-groups flag to poetry show that annotates direct dependencies with their groups in both table and JSON output, updates layout calculations to account for the new column, and prevents using the flag together with --tree.

Sequence diagram for poetry show with --with-groups flag handling and group mapping

sequenceDiagram
    actor User
    participant CLI
    participant ShowCommand
    participant GroupCommand
    participant ProjectPackage

    User->>CLI: run poetry show --with-groups [--tree]
    CLI->>ShowCommand: instantiate and parse options
    CLI->>ShowCommand: handle()

    alt with-groups and tree
        ShowCommand-->>ShowCommand: option("with-groups") and option("tree")
        ShowCommand->>CLI: line_error("Error: Cannot use --tree and --with-groups at the same time.")
        ShowCommand-->>CLI: return 1
    else with-groups without tree
        ShowCommand-->>ShowCommand: _display_packages_information(...)
        ShowCommand-->>ShowCommand: show_with_groups = option("with-groups")
        ShowCommand->>ShowCommand: dep_group_map()
        alt dep_group_map not cached
            ShowCommand->>GroupCommand: build_dependency_group_map()
            GroupCommand->>ProjectPackage: project_with_activated_groups_only()
            ProjectPackage-->>GroupCommand: root
            GroupCommand-->>GroupCommand: map main and group dependencies
            GroupCommand-->>ShowCommand: dependency_group_map
            ShowCommand-->>ShowCommand: cache _dep_group_map
        else dep_group_map cached
            ShowCommand-->>ShowCommand: reuse _dep_group_map
        end

        loop for each locked package
            ShowCommand->>ShowCommand: get_groups_from_package(locked.pretty_name)
            ShowCommand-->>ShowCommand: lookup in dep_group_map
            ShowCommand-->>ShowCommand: attach groups to package info
        end

        ShowCommand-->>CLI: formatted output with groups column and JSON groups field
    end
Loading

Class diagram for updated ShowCommand and GroupCommand with dependency group mapping

classDiagram
    class GroupCommand {
        +_group_dependency_options() list_Option
        +_validate_group_options(group_options dict_str_set_str) void
        +build_dependency_group_map() dict_str_str
        +project_with_activated_groups_only() ProjectPackage
    }

    class EnvCommand

    class ShowCommand {
        +name : str
        +description : str
        -_dep_group_map : dict_str_str
        +handle() int
        +_display_packages_information(locked_repository Repository, root ProjectPackage) int
        +_display_packages_tree_information(locked_repository Repository, root ProjectPackage) int
        +get_groups_from_package(package_name str) str
        +dep_group_map() dict_str_str
    }

    GroupCommand <|-- ShowCommand
    EnvCommand <|-- ShowCommand
Loading

File-Level Changes

Change Details Files
Add --with-groups CLI flag to group-aware commands and validate incompatible usage with --tree.
  • Extend group-related CLI options with a boolean with-groups flag on group-capable commands.
  • In show command handler, detect simultaneous use of --with-groups and --tree and emit an error with non-zero exit status.
  • Cache per-command dependency-to-group mapping on the ShowCommand instance.
src/poetry/console/commands/group_command.py
src/poetry/console/commands/show.py
Compute and expose dependency-to-group mappings based on the active project configuration.
  • Implement build_dependency_group_map to derive groups for each dependency from root.requires and dependency groups.
  • Normalize group lists per dependency by de-duplicating and sorting before joining into a canonical string key.
  • Expose lazy-initialized dep_group_map and a helper accessor get_groups_from_package to retrieve a package’s groups by name.
src/poetry/console/commands/group_command.py
src/poetry/console/commands/show.py
Extend poetry show tabular and JSON output to include dependency groups and adjust layout decisions accordingly.
  • When --with-groups is enabled, precompute maximum rendered width of group values to participate in column width calculations.
  • Update write_version/write_latest/why_end_column calculations to include the new groups column width and compute write_groups toggle based on terminal width.
  • In JSON output mode, attach a list-valued groups field per package, derived from the group map.
  • In text output mode, append a groups column for each listed package when enabled, falling back to '-' when no groups are available.
src/poetry/console/commands/show.py
Document the new with-groups option in the CLI docs.
  • Add --with-groups description to the show command’s documented options in cli.md.
docs/cli.md

Assessment against linked issues

Issue Objective Addressed Explanation
#7519 Provide a way for poetry show to display which dependency groups each dependency belongs to (e.g., as an extra column / field in the output).
#7519 Provide a way for poetry show to display which extras a dependency is pulled in by. The PR only adds a --with-groups flag and a groups column/JSON field based on dependency groups. There is no implementation or output related to extras or which extras a dependency is pulled in by.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 4 issues, and left some high level feedback:

  • The groups mapping is rebuilt multiple times inside _display_packages_information (and again in get_groups_from_package), which is unnecessary work; consider building the dependency→groups map once per command invocation and reusing it for both width calculation and rendering.
  • get_groups_from_package returns the sentinel string "-" and the caller then splits/compares against it for JSON output, which couples formatting with data; it would be cleaner to have this helper return a list of groups (or []/None) and let the caller handle display formatting.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The groups mapping is rebuilt multiple times inside `_display_packages_information` (and again in `get_groups_from_package`), which is unnecessary work; consider building the dependency→groups map once per command invocation and reusing it for both width calculation and rendering.
- `get_groups_from_package` returns the sentinel string `"-"` and the caller then splits/compares against it for JSON output, which couples formatting with data; it would be cleaner to have this helper return a list of groups (or `[]`/`None`) and let the caller handle display formatting.

## Individual Comments

### Comment 1
<location> `src/poetry/console/commands/show.py:349-351` </location>
<code_context>
                         ),
                     )

+                    if show_with_groups:
+                        group_map = self.build_dependency_group_map()
+                        max_len = max((len(v) for v in group_map.values()), default=0)
+                        groups_length = max_len
+
</code_context>

<issue_to_address>
**suggestion (performance):** Avoid recomputing the dependency group map and max length inside the loop.

These values are recalculated on every iteration and in both branches of the `if installed_package` conditional. Since the group mapping doesn’t depend on `locked`, build it once before iterating over `locked_packages` and derive `groups_length` from it there. This avoids unnecessary work and keeps the loop logic simpler, especially with large dependency graphs.

Suggested implementation:

```python
        name_length = version_length = latest_length = required_by_length = (
            groups_length
        ) = 0
        latest_packages = {}
        latest_statuses = {}
        installed_repo = InstalledRepository.load(self.env)

        group_map = {}
        if show_with_groups:
            group_map = self.build_dependency_group_map()
            groups_length = max((len(v) for v in group_map.values()), default=0)
                        ),
                    )

```

```python

```

1. Ensure that `show_with_groups` is defined *before* the new precomputation block (e.g., typically as `show_with_groups = self.option("with-groups")` or similar). If it is currently defined after `installed_repo = InstalledRepository.load(self.env)`, move its definition above that line.
2. If the loop body relies on `group_map` (e.g., accessing `group_map[package]`), no change is needed because it is now defined once above; just confirm there is no other shadowing or redefinition of `group_map` inside the loop.
</issue_to_address>

### Comment 2
<location> `src/poetry/console/commands/show.py:383` </location>
<code_context>

         if self.option("format") == OutputFormats.JSON:
             packages = []
+            groups = self.build_dependency_group_map()

             for locked in locked_packages:
</code_context>

<issue_to_address>
**issue:** The `groups` map built here is never used and gets shadowed later.

In the JSON branch, `groups` is initialized with `build_dependency_group_map()` but then overwritten in the loop with the string from `get_groups_from_package`, so the original dict is never used. Either pass the precomputed map into `get_groups_from_package`/use it directly, or remove this initialization to avoid dead code and extra work.
</issue_to_address>

### Comment 3
<location> `src/poetry/console/commands/show.py:514-516` </location>
<code_context>
                 else:
                     line += " " * required_by_length

+            if write_groups:
+                groups = self.get_groups_from_package(locked.pretty_name)
+                line += " " * (groups_length - len(groups)) + groups
+
             if write_description:
</code_context>

<issue_to_address>
**suggestion:** Guard against negative padding when `groups_length` is smaller than the string length.

When `groups_length` is 0 or less than `len(groups)`, `(groups_length - len(groups))` is negative, so the padding becomes `""` and the column is no longer fixed-width. Using `max(0, groups_length - len(groups))` makes the intent explicit and avoids misaligned output in these edge cases.

```suggestion
            if write_groups:
                groups = self.get_groups_from_package(locked.pretty_name)
                padding = max(0, groups_length - len(groups))
                line += " " * padding + groups
```
</issue_to_address>

### Comment 4
<location> `src/poetry/console/commands/show.py:536-540` </location>
<code_context>

         return 0

+    def get_groups_from_package(self, package_name):
+        dep_map = self.build_dependency_group_map()
+        if package_name in dep_map:
</code_context>

<issue_to_address>
**suggestion (performance):** `get_groups_from_package` rebuilds the dependency group map on every call.

This calls `build_dependency_group_map()` for every package and is used inside loops over `locked_packages` for both table and JSON output. Since the mapping comes from static pyproject data, compute it once (e.g. cache on the instance or pass it into `get_groups_from_package`) to avoid repeated parsing and improve performance with many dependencies.

```suggestion
    def get_groups_from_package(self, package_name):
        # Lazily build and cache the dependency group map so it is only
        # constructed once per command invocation instead of on every call.
        if not hasattr(self, "_dependency_group_map"):
            self._dependency_group_map = self.build_dependency_group_map()

        dep_map = self._dependency_group_map
        if package_name in dep_map:
            return dep_map[package_name]

        return "-"
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ammargit93 ammargit93 marked this pull request as draft January 12, 2026 19:33
@ammargit93 ammargit93 marked this pull request as ready for review January 13, 2026 12:13
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Consider keeping dep_group_map as dict[str, list[str]] instead of a comma-joined str, so you don't need to join/split for different outputs and can avoid string parsing in _display_packages_information.
  • build_dependency_group_map relies on the private root._dependency_groups attribute; if there is any public API for accessing dependency groups, prefer that to reduce coupling to internal implementation details.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Consider keeping `dep_group_map` as `dict[str, list[str]]` instead of a comma-joined `str`, so you don't need to join/split for different outputs and can avoid string parsing in `_display_packages_information`.
- `build_dependency_group_map` relies on the private `root._dependency_groups` attribute; if there is any public API for accessing dependency groups, prefer that to reduce coupling to internal implementation details.

## Individual Comments

### Comment 1
<location> `src/poetry/console/commands/group_command.py:142-151` </location>
<code_context>
                 message_parts.append(f"{group} (via {opts})")
             raise GroupNotFoundError(f"Group(s) not found: {', '.join(message_parts)}")
+
+    def build_dependency_group_map(self) -> dict[str, str]:
+        root = self.project_with_activated_groups_only()
+        dep_group_map: dict[str, list[str]] = defaultdict(list)
+        # main dependencies
+        for dep in root.requires:
+            dep_group_map[dep.name].append("main")
+        # dependency groups
+        for group_name, group in root._dependency_groups.items():
+            for dep in group.dependencies:
+                dep_group_map[dep.name].append(group_name)
+
+        return {
+            name: ",".join(sorted(set(groups)))
+            for name, groups in dep_group_map.items()
+        }
</code_context>

<issue_to_address>
**suggestion:** Consider returning a list of groups instead of a comma-joined string from `build_dependency_group_map`.

`build_dependency_group_map` currently returns `dict[str, str]` where the value is a comma-joined list of group names, and some callers then split this string back into a list. This couples data representation to display and adds unnecessary join/split work.

Returning `dict[str, list[str]]` would keep the map as a structured data model, and callers like `_display_packages_information` could handle any string joining needed for display.

Suggested implementation:

```python
    def build_dependency_group_map(self) -> dict[str, list[str]]:
        root = self.project_with_activated_groups_only()
        dep_group_map: dict[str, list[str]] = defaultdict(list)
        # main dependencies
        for dep in root.requires:
            dep_group_map[dep.name].append("main")
        # dependency groups
        for group_name, group in root._dependency_groups.items():
            for dep in group.dependencies:
                dep_group_map[dep.name].append(group_name)

        return {
            name: sorted(set(groups))
            for name, groups in dep_group_map.items()
        }

```

Callers that assume `build_dependency_group_map` returns a comma-joined `str` must be updated to work with `list[str]` instead. In particular:
1. Any code that currently does something like `groups = group_map[name].split(",")` should be changed to use the list directly (e.g. `groups = group_map[name]`).
2. Any display logic (for example in `_display_packages_information`) that expects a string should explicitly join the list for presentation, e.g. `", ".join(groups)` (possibly sorted, if ordering is important).
3. If this method is part of a public API used outside this module, corresponding type hints and any external callers must be updated to the new `dict[str, list[str]]` return type.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@ammargit93 ammargit93 force-pushed the feat/show-with-groups branch from 59f6fcd to 37d1edc Compare January 21, 2026 14:58
@ammargit93 ammargit93 force-pushed the feat/show-with-groups branch from 37d1edc to 00e2a06 Compare January 21, 2026 14:59
@ammargit93 ammargit93 closed this Jan 23, 2026
@ammargit93 ammargit93 deleted the feat/show-with-groups branch January 23, 2026 08:19
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.

RFE: include dependency group in 'poetry show' output

2 participants

Comments