Managing Dependencies & Groups

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the Python Reference bibliography.

This page covers the commands that change a Poetry project’s dependencies and the way Poetry 2.5.1 organises them in groups. The examples ran on CPython 3.14.6 on 2026-10-22, on a copy of the Poetry flavour of birdtally whose docs group is marked optional. The syntax of each dependency is on Dependency Specification.

poetry add

poetry add adds one or more requirements, writes them into pyproject.toml, updates poetry.lock and installs the result.

Option Effect

-G, --group <name>

Add to a dependency group (created if needed). -D/--dev is a deprecated alias for --group dev.

-e, --editable

Add a path or VCS dependency in editable (develop) mode.

-E, --extras <extra>

Extras of the added package to activate.

--optional <extra>

Add it as an optional dependency of your extra <extra> (since 2.0 the option takes the extra’s name).

--python <constraint>

Only for these Pythons (becomes a python_version marker).

--platform <platform>

Only for these platforms (a sys_platform marker).

--markers <marker>

Any PEP 508 marker.

--source <name>

Pin it to a package source.

--allow-prereleases

Accept pre-releases.

--dry-run

Show the operations, change nothing.

--lock

Update pyproject.toml and the lock, do not install.

The argument forms: rich, "rich>=15", rich@^15, rich@latest, "rich[jupyter]@^15", git+https://…/repo.git#ref, git+ssh://…, "git+https://…#ref&subdirectory=sub", a local path (../birdtally), a wheel or sdist file or URL. Quote anything with [, <, > or ^ so that the shell does not interpret it.

Adding a test tool to the existing PEP 735 test group:

poetry add --group test pytest-cov
…
Package operations: 8 installs, 0 updates, 0 removals

  - Installing iniconfig (2.3.1)
  - Installing packaging (26.3)
  - Installing pluggy (1.7.0)
  - Installing pygments (2.21.0)
  - Installing pytest (9.1.1)
  - Installing coverage (7.16.2)
  - Installing ruff (0.17.1)
  - Installing pytest-cov (7.1.0)

Writing lock file
[dependency-groups]
test = ["pytest>=8", "pytest-cov (>=7.1.0,<8.0.0)"]
lint = ["ruff>=0.6"]
docs = ["mkdocs>=1.6"]
dev = [{ include-group = "test" }, { include-group = "lint" }]

[tool.poetry.group.docs]
optional = true

Poetry kept the file’s PEP 735 style and also installed the rest of the non-optional groups (lint arrived with this call), but not docs.

--optional takes an extra name. poetry add --optional click therefore fails with Not enough arguments (missing: "name"): click was consumed as the extra’s name and no package was left. Write poetry add --optional pretty rich-pixels.

poetry remove

poetry remove rich removes a dependency from pyproject.toml, the lock and the environment; -G/--group for a group, --dry-run and --lock as for add. Unlike pip uninstall, it also removes the dependencies that nothing else needs any more.

poetry show

poetry show lists the locked packages; poetry show <name> details one. With --with, --without and --only it accepts the group options of install.

poetry show --only test
coverage   7.16.2 Code coverage measurement for Python
iniconfig  2.3.1  brain-dead simple config-ini parsing
packaging  26.3   Core utilities for Python packages
pluggy     1.7.0  plugin and hook calling mechanisms for python
pygments   2.21.0 Pygments is a syntax highlighting package written in Python.
pytest     9.1.1  pytest: simple powerful testing with Python
pytest-cov 7.1.0  Pytest plugin for measuring coverage.
poetry show --tree --only test
pytest 9.1.1 pytest: simple powerful testing with Python
├── colorama >=0.4
├── exceptiongroup >=1
│   └── typing-extensions >=4.6.0
├── iniconfig >=1.0.1
├── packaging >=22
├── pluggy >=1.5,<2
├── pygments >=2.7.2
└── tomli >=1
pytest-cov 7.1.0 Pytest plugin for measuring coverage.
├── coverage >=7.10.6
│   └── tomli *
…

--why explains where a package comes from, on the list or together with --tree for one package (alone with a package name it answers Error: --why cannot be used without --tree when displaying a single package.):

poetry show --why --only test
poetry show --tree --why pygments
coverage   7.16.2 from pytest-cov        Code coverage measurement for Python
iniconfig  2.3.1  from pytest            brain-dead simple config-ini parsing
packaging  26.3   from mkdocs,pytest     Core utilities for Python packages
pluggy     1.7.0  from pytest,pytest-cov plugin and hook calling mechanisms...
pygments   2.21.0 from pytest,rich       Pygments is a syntax highlighting ...
pytest     9.1.1  from pytest-cov        pytest: simple powerful testing wi...
pytest-cov 7.1.0                         Pytest plugin for measuring coverage.

pytest 9.1.1 pytest: simple powerful testing with Python
└── pygments >=2.7.2
rich 15.0.0 Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal
└── pygments >=2.13.0,<3.0.0
poetry show -T            # --top-level: direct dependencies only
poetry show --outdated    # -o; --latest (-l) shows the newest version of everything
poetry show -f json --only lint
pytest             9.1.1  pytest: simple powerful testing with Python
pytest-cov         7.1.0  Pytest plugin for measuring coverage.
rich           (!) 15.0.0 Render rich text, tables, progress bars, syntax h...
ruff               0.17.1 An extremely fast Python linter and code formatte...
coverage 7.16.2 7.17.0 Code coverage measurement for Python
[{"name": "ruff", "installed_status": "installed", "version": "0.17.1", "description": "An extremely fast Python linter and code formatter, written in Rust."}]

(!) marks a locked package that is not installed (here rich, needed only for the pretty extra). -a/--all also shows packages that do not apply to the current platform, and --no-truncate stops shortening the descriptions. --format json (2.2) cannot be combined with --tree.

Source: Poetry CLI: show.

Dependency groups

The dependencies in [project] dependencies (or [tool.poetry.dependencies]) form the implicit main group: what the project needs at run time, and the only group that reaches the published metadata. Everything else goes in named groups, in either of two syntaxes:

PEP 735 [dependency-groups] (Poetry 2.2) [tool.poetry.group.<name>] (Poetry 1.2)
[dependency-groups]
test = ["pytest>=8"]
lint = ["ruff>=0.6"]
docs = ["mkdocs>=1.6"]
dev = [
    { include-group = "test" },
    { include-group = "lint" },
]

[tool.poetry.group.docs]
optional = true
[tool.poetry.group.test.dependencies]
pytest = ">=8"

[tool.poetry.group.lint.dependencies]
ruff = ">=0.6"

[tool.poetry.group.docs]
optional = true

[tool.poetry.group.docs.dependencies]
mkdocs = ">=1.6"

[tool.poetry.group.dev]
include-groups = ["test", "lint"]
  • PEP 735 groups are read by pip, uv and other tools as well; prefer them for new projects. Requiring Poetry 2.2 or later is the cost, and since 2.3 the groups are part of the lock’s content hash, so a project that uses them should set requires-poetry = ">=2.3" (an older Poetry sees a 2.3 lock as outdated).

  • Both syntaxes can describe the same group; [tool.poetry.group.<name>] then only adds Poetry settings (optional, sources).

  • Optional groups (optional = true) are not installed by default. They are still resolved with everything else (below).

  • Including groups: { include-group = "test" } in PEP 735, include-groups = [...] in the Poetry syntax.

  • Names are normalized like project names, so Docs and docs are one group.

Installing groups

poetry install installs main and all non-optional groups. The selection options, here with --dry-run:

Command Result

poetry install --with docs --dry-run

Package operations: 16 installs, 0 updates, 0 removals, 11 skipped (mkdocs and its dependencies added)

poetry install --with docs --without docs --dry-run

Package operations: 0 installs, 0 updates, 0 removals, 11 skipped: --without wins over --with

poetry install --only main

Only the runtime dependencies (and the project).

poetry install --only docs

Only that group, not even main.

poetry install --all-groups

Every group, optional ones included.

--only main is how production images and poetry export usually select the runtime set.

All groups are resolved together

There is one lock for the whole project, so every group, optional or not, is resolved in a single solution. A constraint in a group you never install still affects the others. Adding pygments<2.13 to the optional docs group:

docs = ["mkdocs>=1.6", "pygments<2.13"]
poetry lock
grep -A1 -E '^name = "(rich|pygments)"' poetry.lock
Writing lock file
name = "pygments"
version = "2.12.0"
--
name = "rich"
version = "13.2.0"

The library’s pretty extra is now locked to rich 13.2.0 (the newest rich that accepts an old Pygments) for everybody, including production installs with --only main -E pretty. If two groups really need incompatible versions, they cannot live in one Poetry project; use separate environments (for example tox or nox sessions).

Synchronizing, and extras

poetry sync (2.0, replacing install --sync) makes the environment match the lock and the selected groups exactly, removing everything else:

poetry sync --only main --dry-run
Package operations: 0 installs, 0 updates, 8 removals, 3 skipped
  - Removing coverage (7.16.2)
  - Removing iniconfig (2.3.1)
  - Removing packaging (26.3)
  - Removing pluggy (1.7.0)
  - Removing pygments (2.21.0)
  - Removing pytest (9.1.1)
  - Removing pytest-cov (7.1.0)
  - Removing ruff (0.17.1)

Extras are handled separately from groups: with sync, any extra that is not selected is removed. After poetry install -E pretty, a plain poetry install left rich in place in 2.5.1, while a plain poetry sync removed it:

Package operations: 0 installs, 0 updates, 3 removals
  - Removing markdown-it-py (4.2.0)
  - Removing mdurl (0.1.2)
  - Removing rich (15.0.0)

(The pyproject.toml documentation says that extras not specified are removed by install too; with Poetry 2.5.1 only sync did it.)

Layering optional groups

Because install (unlike sync) never removes groups that are already installed, you can add optional groups in steps, for example in successive stages of a multi-stage Docker build: poetry install --only main in one stage, poetry install --with docs in the next.

pip and Poetry

Poetry pip

poetry add rich

Edit pyproject.toml, then pip install -e .

poetry add --group test pytest

Edit [dependency-groups], then pip install --group test

poetry install --with docs

pip install --group docs

poetry sync

Recreate the venv (pip never removes what you did not name)

poetry show --tree

No equivalent in pip