Simplify tox environment dependencies

Avoid duplication and potential sources of human error

Geschrieben von Timo Rieber am 16. Oktober 2023

Today I came across an older project of mine that uses tox, but since many years I'm configuring tox environments in a different way. So I decided to update the tox configuration of this project to my current knowledge.

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 there is no 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, it was expensive to install all dependencies though they were not necessary. So I decided to specify the dependencies explicitly as deps, as you can see in the [testenv:flake8] section:

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

This is redundant because the dependencies are already specified in setup.py. As soon as the required version of a dependency changes, I had to update it in two places. Everything a human has to remember is a potential source of error.

So I decided to simplify the tox environment dependency specification by using the extras parameter of the deps option. And this works for both ways of specifying the dependencies. 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

setup(
    ...
    extras_require={
        'development': development_extras,
        'linting': linting_extras,
        'testing': testing_extras,
    },
    ...
)
Python

Then I can use the extras parameter to specify the dependencies:

[testenv]
extras = testing
commands = pytest

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

This way I can specify the dependencies in one place and have only one alternative so tox will install the correct dependencies for each environment. It makes the tox configuration clearly more readable and stable.