An Opinionated tox.ini File

Automate all of your Python project’s tasks with a succinct tox.ini file.

This post assumes some knowledge of Tox. For a beginner’s introduction see
my Tox tutorial.

Below is an excerpted version of the tox.ini file from hypothesis/h as of November
2018
(yes we’re still using Python 2). It demonstrates a particular way of using Tox
in which all of the Tox commands are of the form tox -e pyXY-<task>. That
is: use a particular version of Python (the pyXY part) to run a particular
development task (the <task>) part:

tox -e py27-dev runs the app in the development server.tox -e py36-dev runs the app in the development server using Python 3.

tox -e py36-docs builds the docs and serves them locally with live reloading.

tox -e py36-coverage prints the coverage report.

Not shown below, but tox -e py36-format formats the code using Black.

Other tasks, such as deploying a new release, could be added too.

We see Tox as a general, virtualenv-based development task automation tool, and
don’t just use it for running tests. We use Tox to standardise and automate any
development task that benefits from being run in its own isolated venv.

Using Tox’s factors and conditionals (see below) you can implement this
pyXY-<task> approach in a simple and concise tox.ini, without duplication or
boilerplate:

[tox]envlist=py27-testsskipsdist=true# Enable the venv_update extension so that changes to requirements files are
# automatically detected.
requires=tox-pip-extensionstox_pip_extensions_ext_venv_update=true[testenv]skip_install=truepassenv=dev:AUTHORITYdev:BOUNCER_URLdev:CLIENT_OAUTH_ID…{tests,functests}:TEST_DATABASE_URL{tests,functests}:ELASTICSEARCH_URL…functests:BROKER_URLcodecov:CITRAVIS*deps=tests:coverage{tests,functests}:pytest{tests,functests}:factory-boytests:mocktests:hypothesislint:flake8lint:flake8-future-importcoverage:coveragecodecov:codecovfunctests:webtestdocs:sphinx-autobuilddocs:sphinxdocs:sphinx_rtd_theme{tests,functests}:-rrequirements.txtdev:ipythondev:ipdbdev:-rrequirements-dev.inwhitelist_externals=dev:shchangedir=docs:docscommands=dev:shbin/hypothesis--devinitdev:{posargs:shbin/hypothesisdevserver}lint:flake8hlint:flake8teststests:coveragerun…functests:pytest-Werror{posargs:tests/functional/}docs:sphinx-autobuild…coverage:-coveragecombinecoverage:coveragereportcodecov:codecov

How it Works

The tox.ini file is written entirely using Tox’s factors and conditionals,
and doesn’t use any of the separate [testenv:NAME] file sections that you
see in most tox.ini files. When you pass an envlist to Tox like tox -e py36-tests you’re telling Tox to run a single testenv, named py36-tests, but
the testenv name contains two factorspy36 and tox.

Builtin factors: The py36 factor is a Tox builtin factor that sets the
Python version to 3.6. Tox comes with builtin factors for all the Python
versions: py27, py37, etc. You’ll notice that we don’t define the pyXY’s
anywhere in our tox.ini file. They’re builtin, so we can just go ahead and
use them with -e on the command line.

Custom factors: The tests part in -e py36-tests is a custom factor,
defined by us in our tox.ini file. Some of the lines in our tox.ini use
tests: as a conditional, meaning those lines should only be applied if
tests is in the testenv name. For example the coverage and mock
dependencies will only be installed if tests is involved (for example when
running tox -e py27-tests or tox -e py36-tests), whereas flake8 will only
be installed if lint is in the tox -e command:

deps =
tests: coverage
tests: mock
lint: flake8
…

By using tests: and lint: as conditionals in the tox.ini file we
implicitly define tests and lint factors in addition to the builtin pyXY
factors, and can use them in commands like tox -e py36-lint.

What Tox does when you give it a command like tox -e py36-tests is:

First, it invokes the builtin py36 factor, which sets the version of
Python to be used to 3.6.

Second, it parses the tox.ini file looking for lines that match any factor
in the given testenv name py36-tests:

Any line that doesn’t begin with a factor: prefix is unconditional,
and is always applied whenever Tox runs.

Any line that begins with tests: matches the tests conditional, so
it’s applied. Any tests: dependencies will be installed, any tests:
commands will be run, and so on.

A line like {tests,functests}:pytest matches eithertests or
functests, so the pytest dependency will be installed for tox -e py36-tests too. More complex conditionals are possible too.

After collecting all the matching lines Tox runs the testenv:

Any matched environment variables are passed through to the test
environment

Matched dependencies are installed in the order that they were given in
the tox.ini file

Matched commands are run in file order

Here’s a view of the tox.ini file with the lines that apply when tox -e py36-tests is run highlighted. The non-highlighted lines are ignored:

Benefits

Automating our development tasks like this has a bunch of advantages:

It simplifies things for developers, especially new developers.
You don’t need to learn about virtualenv and create and activate a
development virtualenv and install dependencies. You don’t need virtualenv
management tools like virtualenvwrapper. You just run tox.

Tasks for running the tests, linting, releasing, etc can be defined in one
place in tox.ini and run exactly the same everywhere: on any developer’s
machine or on CI.

Tox isolates every task using a Python virtualenv, PATH isolation and
environment variable isolation, so problems caused by differences between
environments are minimised.

Because Tox uses a different virtualenv for each task (it creates one
virtualenv for the dev server, another for the tests, another for building
the docs, and so on) the chances of conflicts between dependencies are
reduced. You don’t need your documentation tools installed in your devserver
environment.

When Python 3.8 comes out and it’s time to test your app in it, you can
easily run the dev server with tox -e py38-dev or the tests with tox -e py38-tests, without having to modify tox.ini first.

Tox isn’t just for tests! Consider using it to standardise all of your
project’s development tasks. For details of all the
Tox concepts used here and more see
my Tox tutorial.