Simplify tox environment dependencies

One source, no drift

Geschrieben von Timo Rieber am 16. Oktober 2023

An older project still configured its tox environments the hard way — duplicated dependency specs, redundant install commands. My newer projects had solved this years ago. Time to catch up.

In earlier years I specified the dependencies and their installation for my tox environments using two approaches. The first was to use the install_command option:

[testenv]
install_command = pip install -e '.[development]' -U {opts} {packages}
commands = pytest
Ini

It works perfectly fine and you don't need to explicitly specify the dependencies as they are already specified in setup.py:

development_extras = [
    'flake8>=6.0,<7.0',
    'pytest>=7.0,<8.0',
    'tox>=3.28,<4.0',
]

setup(
    ...
    extras_require={
        'development': development_extras,
    },
    ...
)
Python

But for some environments in larger projects, often linting stages like flake8 or isort, installing all dependencies was overkill. So I specified them explicitly as deps:

[testenv:flake8]
deps = flake8>=6.0,<7.0
commands = flake8 src
Ini

This is redundant. As soon as the required version of a dependency changes, I had to update it in two places. Everything I have to remember to update is a potential source of error.

tox has an extras option that handles exactly this. It installs the package's extras into the environment without duplicating version specs. First I split the dependencies within setup.py into more specific parts:

linting_extras = [
    'flake8>=6.0,<7.0',
]
testing_extras = [
    'pytest>=7.0,<8.0',
    'tox>=3.28,<4.0',
]
development_extras = linting_extras + testing_extras

extras_require = {
    'development': development_extras,
    'linting': linting_extras,
    'testing': testing_extras,
}
Python

Then I can use extras in tox to reference these groups directly:

[testenv]
extras = testing
commands = pytest

[testenv:flake8]
extras = linting
commands = flake8 src
Ini

One place to maintain, no way to forget. The tox config gets shorter and each environment only installs what it actually needs.

I've used this pattern in texsite, my open-source Wagtail CMS package, and in cloudapps, a course-management platform, both before migrating to uv.