Poetry is one of the most famous dependency management tools for Python, and I frequently use it in place of the basic requirements.txt
to simplify version management in my libraries. Despite its widespread adoption, Poetry is not fully compliant with Python’s PEPs and this sometimes leads to challenges during the development process. One such challenge I recently encountered was integrating Poetry’s “dev dependencies” with Tox.
In my projects, I usually specify two groups of dependencies with Poetry:
- the main dependency group: these dependencies will be installed with my library as part of the regular installation process (for example:
pip install my-project
). - An additional dependency group called
dev
: these additional dependencies are only used during development and testing and can only be installed through Poetry (for example:poetry install --with dev
).
Assuming this simple setup, the initial pyproject.toml
file would look like this:
[tool.poetry]
name = "my-project"
version = "0.1.0"
authors = ["me"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.dependencies]
python = "^3.11"
[tool.poetry.group.dev.dependencies]
pytest = "^8.3.2"
tox = "^4.23.2"
As you can see, pytest
and tox
are both part of the tool.poetry.group.dev.dependencies
group: we don’t want them to be installed by default with our library (since most users won’t need them) but we still need to declare them, as they are necessary for development and testing.
Tox is a “generic virtual environment management and test command line tool” that can be used to run tests in multiple, isolated environments (for example different Python versions with different dependencies). For this example, let’s pretend we want Tox to run pytest
on two environments: Python 3.11 and 3.12.
The following tox.ini
configuration looks reasonable, but it won’t work:
[tox]
envlist =
py312
[testenv]
description = Run tests using pytest
allowlist_externals = pytest
passenv = *
commands =
pytest
The problem with this configuration is that Tox will not automatically install the dev dependencies we declared in pyproject.toml
. Unfortunately, in fact, Poetry’s dependency groups (like our tool.poetry.group.dev.dependencies
above) are not a standard and do not comply with existing PEPs, so Tox won’t detect tool.poetry.group.dev.dependencies
and won’t install them.
One simple solution would be to just re-install all the dependencies needed by Tox (pytest
, in our example) in tox.ini
:
[tox]
envlist =
py312
[testenv]
allowlist_externals = pytest
passenv = *
commands_pre =
pip install pytest==8.3.4 # Install pytest explicitly before running the command below
commands =
pytest
However, this means that we will duplicate our dependency declarations (pytest
and its version are now declared both in pyproject.toml
and tox.ini
). This could introduce bugs and unexpected inconsistencies between our Tox environment and the one defined by pyproject.toml
.
Another possible (but discouraged) way to do it would be to directly use Poetry in tox.ini
to install the dev dependencies:
[tox]
envlist =
py311
py312
[testenv]
allowlist_externals = poetry, pytest
passenv = *
commands =
poetry install --with dev
pytest
While this might work, it will also make Tox aware of Poetry, which is suboptimal and unnecessary: Tox shouldn’t care about how dependencies are installed, nor should it explicitly install them. In fact, by default Tox looks into the [build-system]
section of pyproject.toml
and uses its value to delegate the dependencies’ build process to the correct backend (poetry.core.masonry.api
in our case). This means that we don’t need to explicitly mention poetry
in our tox.ini
, because Tox already knows about its build system.
The solution
So Tox will use Poetry’s build-backend
to install dependencies. But how do we make Tox aware of Poetry’s development dependency group? Simply put: we don’t. As I mentioned, Poetry dependency groups are non-standard and Tox is not aware of them. However, we can use another field that is, in fact, defined in the standard specification for Python packaging (PEP 508): extras
.
The pyproject.toml
then becomes:
[tool.poetry]
name = "my-project"
version = "0.1.0"
authors = ["me"]
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.dependencies]
python = "^3.11"
# Specify the optional dependencies that will be used below as "extras"
pytest = { version = "^8.3.2", optional = true }
# List the extra dependencies from the dependencies we defined above.
# This is used in place of tool.poetry.group.dev.dependencies.
[tool.poetry.extras]
dev = ["pytest"]
# Define dependencies that will be installed by developers using Poetry
[tool.poetry.group.dev.dependencies]
tox = "^4.23.2"
Final notes
Poetry’s popularity reflects the the broad appreciation of Python’s community. However, its widespread adoption also creates constrains regarding backward compatibility, making it challenging for the team to support new PEP standards especially when this involves dropping existing features that users rely on. At the same time, I would feel more confident using tools that fully comply with current standards. Package managers like Hatch or PDM seem to offer this, although their user bases seem significantly smaller than Poetry’s.
On the bright side, PEP 735, a new standard for dependency groups, was recently accepted. Hopefully, this can be a step towards moving Python dependency management tools in the direction of further standardization.
Resources
- PEP 508 - Dependency specification for Python Software Packages: the initial specification and format used in Python packaging (including the definition for the
extras
dependencies). - PEP 621 - Storing project metadata in pyproject.toml: this PEP defines a standard to define project’s metadata in pyproject.toml.
- PEP 631 – Dependency specification in pyproject.toml based on PEP 508: this PEP declares that metadata stored in pyproject.toml files should be structured as defined by PEP 508. The PEP was merged into PEP 621 after being accepted. Poetry does not comply with this PEP (see this comment by Sébastien Eustace sdispater, Poetry’s creator).