惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
V2EX
aimingoo的专栏
aimingoo的专栏
S
SegmentFault 最新的问题
博客园_首页
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
博客园 - 【当耐特】
月光博客
月光博客
C
Check Point Blog
T
The Blog of Author Tim Ferriss
罗磊的独立博客
博客园 - Franky
MongoDB | Blog
MongoDB | Blog
H
Help Net Security
Microsoft Security Blog
Microsoft Security Blog
B
Blog
阮一峰的网络日志
阮一峰的网络日志
腾讯CDC
美团技术团队
N
Netflix TechBlog - Medium
Stack Overflow Blog
Stack Overflow Blog
Y
Y Combinator Blog
L
LangChain Blog
The Cloudflare Blog

Keenformatics

PyLaDe Ports blocked by browsers Manually verifying an SSL certificate How to generate documentation with Sphinx How To Override Sublime Text Packages Shortcuts and Preferences How To Find out Sublime Text Key Binding Commands How To Make Terminator Behave Like Guake (Ubuntu) Sentiment Analysis lexicons and datasets Model-Driven approach vs hardcore coding
Using Tox with Poetry dependency groups
fievelk · 2024-12-26 · via Keenformatics

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