Normally when I change the deps
in my tox.ini
file tox
will notice the change and recreate the virtualenv with the new dependencies. But if I use deps = -r requirements.txt
to read my dependencies from a requirements.txt
file then tox
doesn't update the virtualenv when requirements.txt
changes. How can I automatically keep my tox virtualenvs in sync with my requirements.txt
files?
deps
in tox.ini
When using the deps
setting in tox.ini
to list your dependencies if you change the deps
then the next time you run a tox
command it'll notice the change and will create the virtualenv and install the new deps
into the new virtualenv. Here's a minimal tox.ini
file to demonstrate what I'm talking about:
# tox.ini
[tox]
skipsdist = true
[testenv]
deps = pytest
commands = pytest --version
If you run tox
in a directory containing this tox.ini
file then tox
will create a virtualenv in the .tox
directory, install pytest
into that virtualenv, and run pytest --version
in the virtualenv. If you run tox
again it'll reuse the existing virtualenv and just run pytest --version
without unnecessarily reinstalling pytest
again. If you make a change to the deps
in tox.ini
, for example like this:
diff --git a/tox.ini b/tox.ini
index 7d92601..e45a612 100644
--- a/tox.ini
+++ b/tox.ini
@@ -2,5 +2,5 @@
skipsdist = true
[testenv]
-deps = pytest
-commands = pytest --version
+deps = pylint
+commands = pylint --version
Then the next time I run tox
it'll recreate the virtualenv and reinstall the dependencies before running the commands:
$ tox
python recreate: /tmp/tox/.tox/python
python installdeps: pylint
python installed: astroid==2.12.4,dill==0.3.5.1,isort==5.10.1,lazy-object-proxy==1.7.1,mccabe==0.7.0,platformdirs==2.5.2,pylint==2.15.0,tomli==2.0.1,tomlkit==0.11.4,wrapt==1.14.1
python run-test-pre: PYTHONHASHSEED='4045882343'
python run-test: commands[0] | pylint --version
pylint 2.15.0
astroid 2.12.4
Python 3.10.4 (main, Jun 29 2022, 12:14:53) [GCC 11.2.0]
___________________________________ summary ____________________________________
python: commands succeeded
congratulations :)
deps
You can reference pip requirements files from tox.ini
with -r
, for example:
# tox.ini
[tox]
skipsdist = true
[testenv]
deps = -r requirements.txt
commands = pylint --version
Now when you run tox
it'll install the dependencies from requirements.txt
into the virtualenv.
requirements.txt
But there's a problem: tox
won't notice if the requirements.txt
file changes. Your virtualenv will still contain the dependencies from the old version of the requirements.txt
file. tox
only notices direct changes to the deps
setting in tox.ini
itself.
Ideally you wouldn't want tox
to recreate the virtualenv from scratch when requirements.txt
changes like it does when tox.ini
changes: requirements.txt
files are often quite large and reinstalling them from scratch can take a long time. Ideally tox
would update the virtualenv in place: installing, removing, updating and downgrading packages as necessary to synchronize the virtualenv with the requirements.txt
file.
You can use the pip-sync
command from pip-tools
to keep your tox
virtualenv synchronized with any changes to your requirements.txt
file:
# tox.ini
[tox]
skipsdist = true
[testenv]
deps = pip-tools
commands_pre = pip-sync requirements.txt
commands = pytest --version
How this works:
-r requirements.txt
from the tox.ini
's deps
: we're no longer using tox
to install our requirements.txt
file.pip-tools
in the deps
. This means tox
will install pip-tools
(the package that contains the pip-sync
command) whenever it creates a new virtualenv.pip-sync requirements.txt
to the commands_pre
setting in tox.ini
. Now every time we run tox
it will run pip-sync
before running whatever is in the commands
setting (pytest --version
in this example). This pip-sync
command will have no effect if the requirements.txt
file hasn't changed, but if requirements.txt
has changed then it'll update the virtualenv.pip-sync-faster
There's one problem: running pip-sync
every time you run tox
is slow, even when the requirements.txt
file hasn't changed and the virtualenv doesn't need to be updated. On my machine using a large requirements.txt
file from a real app it takes about 1.5s to run a simple command like pytest --version
in tox
. For comparison a simple tox.ini
file that doesn't call pip-sync
(and therefore doesn't update the virtualenv when requirements.txt
changes) runs pytest --version
in about 800ms.
pip-sync-faster
is a pip-sync
wrapper script that can speed things up by doing a very fast check of whether requirements.txt
has changed and only calling pip-sync
if it has.
You need to add pip-sync-faster
to your requirements.txt
file, otherwise it'll uninstall itself! This is because pip-sync-faster
calls pip-sync
which uninstalls any package that isn't in requirements.txt
, including pip-sync-faster
. Here's an example pinned requirements.txt
file containing pip-sync-faster
, pytest
, and their dependencies:
attrs==22.1.0
build==0.8.0
click==8.1.3
iniconfig==1.1.1
packaging==21.3
pep517==0.13.0
pip-sync-faster==0.0.2
pip-tools==6.8.0
pluggy==1.0.0
py==1.11.0
pyparsing==3.0.9
pytest==7.1.2
tomli==2.0.1
With a requirements.txt
like this you can now use pip-sync-faster
with a tox.ini
file like this:
# tox.ini
[tox]
skipsdist = true
[testenv]
deps = pip-sync-faster
commands_pre = pip-sync-faster requirements.txt
commands = pytest --version
With my real-world requirements.txt
running pytest --version
in tox
now takes about 850ms, a speed up of almost 600ms.
The deps = -r requirements.txt
gets tox
to install pip-sync-faster
whenever it creates a new virtualenv, the commands_pre = pip-sync-faster requirements.txt
then installs requirements.txt
into the virtualenv and updates the virtualenv if requirements.txt
changes, before running the commands
(remember: pip-sync-faster
needs to be in requirements.txt
or it'll uninstall itself!)
tox-faster
tox-faster
is a little tox
plugin that can shave a few hundred more milliseconds off your tox
startup time (depending on the size of your requirements.txt
file). Just add it to the requires
setting in your tox.ini
file:
# tox.ini
[tox]
skipsdist = true
requires = tox-faster
[testenv]
deps = -r requirements.txt
commands_pre = pip-sync-faster requirements.txt
commands = pytest --version
With my app's requirements.txt
file running pytest --version
in tox
now takes about 650ms, another 200ms
faster.