Understanding the purpose of tox.ini
- Introduction
- What is tox?
- Example tox.ini
- Configuring other packages
- Why do multiple tools support
tox.ini
? - Bonus Section
Introduction
I was confused by the Python tool tox for a long time, and was unsure what it was really used for considering lots of different Python packages appeared to rely on tox’s configuration file (e.g. tox.ini
).
In this post I’m going to briefly explain what the tox tool is, and what a tox.ini
configuration file looks like and why that configuration file can be used for more than just serving the purposes of the tox tool.
What is tox?
Tox is a tool that creates virtual environments, and installs the configured dependencies for those environments, for the purpose of testing a Python package (i.e. something that will be shared via PyPi, and so it only works with code that defines a setup.py
).
PyPi == Python Package Index (i.e. where you typically install all your Python packages from, when executing
pip install
).
The file that you use to configure tox can be one of the following…
tox.ini
pyproject.toml
(see PEP 518)setup.cfg
(see official guide to distributing packages)
Note: these ^^ are all ini file formats – which is information that will become more relevant/important later on.
Example tox.ini
All configuration options for tox can be found here: tox.readthedocs.io/en/latest/config.html but the below example is a simple demonstration of how you might configure tox for a real package (e.g. this is an actual tox.ini
file I’ve used).
[tox]
envlist =
py37, lint
toxworkdir =
{env:TOX_WORK_DIR}
[testenv]
setenv =
PYTHONDONTWRITEBYTECODE = 1
whitelist_externals =
/bin/bash
deps =
-rrequirements-dev.txt
commands =
py.test --cov={envsitepackagesdir}/bf_tornado -m "not integration"
[testenv:dev]
usedevelop=True
recreate = False
commands =
# to run arbitrary commands: tox -e dev -- bash
{posargs:py.test --cov=bf_tornado}
[testenv:lint]
deps =
flake8==3.8.3
mypy==0.782
commands =
flake8 bf_tornado
mypy --verbose --ignore-missing-imports --package bf_tornado
Note: the name after
testenv:
is the name of the virtual environment that will be created (e.g.testenv:foo
will create a “foo” virtual environment).
Configuring other packages
A tox.ini
file can be used to configure different types of packages, which is confusing at first because the tox home page suggests that tox is used to test your own packages you plan on distributing to PyPi.
What the tox authors mean by that description, is that the tox
command itself is used to handle testing your packages, while the tox.ini
configuration file is just one such file that can be used to contain configuration information.
This is why other packages, such as Flake8 allow you to configure Flake8 using the tox.ini
file, as well as alternatives such as: setup.cfg
or .flake8
.
The key to understanding why this works is as follows: each of these files conform to the ini file format. So you’re free to use whatever file name you feel best suits your project, while the format of the file will stay consistent to what is expected of an .ini
file.
Why do multiple tools support tox.ini
?
The benefit of these various tools supporting the tox.ini
filename specifically is to help reduce the number of configuration files a project requires.
Imagine you needed a unique configuration file for every Python command line tool you use in your project. How many would that result in? How hard would it be to remember all the various file names?
With that in mind, below is an example that shows various Python packages being configured within a tox.ini
file.
And in case it’s unclear, the configuration inside of the tox.ini
file is used instead of having to pass those configuration values via the command line.
So in the case of a tool such as flake8
, instead of using flake8 --max-line-length=120
you could just call flake8
and the flag value is extracted from the configuration file.
[flake8]
max_line_length = 120
ignore = E261,E265,E402 # http://pep8.readthedocs.org/en/latest/intro.html#error-codes
[coverage:run]
branch = True
[coverage:report]
show_missing = True
exclude_lines =
raise NotImplementedError
return NotImplemented
def __repr__
omit = bf_tornado/testing.py
[pytest]
addopts =
--strict -p no:cacheprovider --showlocals
markers =
integration: mark a test as an integration test that makes http calls.
Bonus Section
Although not directly related to the conversation about tox.ini
I thought it worth mentioning that I discovered recently the Python logging library allows you to be able to configure logging via an ini configuration file!
Reference: docs.python.org/3/howto/logging.html
The ini file might look something like the following:
[loggers]
keys=root,simpleExample
[handlers]
keys=consoleHandler
[formatters]
keys=simpleFormatter
[logger_root]
level=DEBUG
handlers=consoleHandler
[logger_simpleExample]
level=DEBUG
handlers=consoleHandler
qualname=simpleExample
propagate=0
[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=simpleFormatter
args=(sys.stdout,)
[formatter_simpleFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=
The Python program that uses this file might look like the following:
import logging
import logging.config
logging.config.fileConfig('logging.conf')
# create logger
logger = logging.getLogger('simpleExample')
# 'application' code
logger.debug('debug message')
logger.info('info message')
logger.warning('warn message')
logger.error('error message')
logger.critical('critical message')
Notice though, the name of the configuration file we imported was logging.conf
(no .ini
extension). It doesn’t matter that the file doesn’t use the .ini
extension as long as the format of the file itself conforms to the ini format.
But before we wrap up... time (once again) for some self-promotion 🙊