Summary
In this post, we cover a pattern for automatically generating a requirements.txt
file that has the latest
compatible versions of required software, and that specifies the full and exact version of each package to
make the Python environment reproducible.
This will turn a requirements input file (called requirements.txt.in
for example) that looks like
numpy
into a requirements file that specifies the exact version of numpy
and all dependencies, like
numpy==1.18.1
By the end of this post, you'll be able to do this to refresh and update the versions of all the software your project depends on:
make requirements.txt
All of this code comes from the Human Cell Atlas data-store project!
What is requirements.txt?
When developing a Python project, the requirements.txt
file is a plain text file that contains a list of
Python software packages that need to be installed for the current Python software package to work. The software
can be installed using the command
pip install -r requirements.txt
For example, if a package foobar
has import numpy
at the top of a Python file in the project, the numpy
package
must be installed before importing foobar
. In this case, the requirements.txt
could just contain
numpy
or it could specify a particular version of numpy, or a minimum version of numpy:
numpy >= 1.10
Start by creating a requirements.txt.in
, which should look like a normal requirements.txt
file,
listing software packages for pip to install (and optionally version information - but version information
does not need to be specified).
This file is a looser set of specifications of software versions.
Example requirements.txt.in
:
numpy
pandas > 0.22
sphinx
Converting requirements.txt.in to requirements.txt
Next, we use the requirements.txt.in
file to install the latest versions of each software package (and all
dependent software packages) into a virtual environment.
From that virtual environment, we can use pip freeze
to output the names of each software package installed in
the virtual environment, along with its exact version. This can be used to make a requirements.txt
file.
The manual steps are
virtualenv -p $(which python3) venv
venv/bin/pip install -r requirements.txt
venv/bin/pip install -r requirements.txt.in
venv/bin/pip freeze >> requirements.txt
rm -fr venv
Using pip freeze means the resulting results.txt
contains detailed version numbers:
alabaster==0.7.12
Babel==2.7.0
certifi==2019.11.28
chardet==3.0.4
docutils==0.15.2
idna==2.8
imagesize==1.1.0
Jinja2==2.10.3
MarkupSafe==1.1.1
numpy==1.17.4
packaging==19.2
pandas==0.25.3
Pygments==2.5.2
pyparsing==2.4.5
python-dateutil==2.8.1
pytz==2019.3
requests==2.22.0
six==1.13.0
snowballstemmer==2.0.0
Sphinx==2.2.2
sphinxcontrib-applehelp==1.0.1
sphinxcontrib-devhelp==1.0.1
sphinxcontrib-htmlhelp==1.0.2
sphinxcontrib-jsmath==1.0.1
sphinxcontrib-qthelp==1.0.2
sphinxcontrib-serializinghtml==1.1.3
urllib3==1.25.7
This is automated with a make rule next.
Automating the step with a make rule
We have a nice Makefile rule that can be dropped into any Makefile that allows users to run
make requirements.txt
and it will use requirements.txt.in
, perform the above steps, and output an updated requirements.txt
with the
latest compatible versions of software.
Here is the Makefile rule:
requirements.txt: %.txt : %.txt.in
[ ! -e .requirements-env ] || exit 1
virtualenv -p $(shell which python3) .$<-env
.$<-env/bin/pip install -r $@
.$<-env/bin/pip install -r $<
echo "# You should not edit this file directly. Instead, you should edit $<." >| $@
.$<-env/bin/pip freeze >> $@
rm -rf .$<-env
Summary of the make rule:
-
The first two lines create a virtual environment at
.requirements-env/
-
The next two lines run
pip install
, first onrequirements.txt
(the existing version), thenrequirements.txt.in
(which installs/updates any software packages inrequirements.txt.in
) -
A comment is added to the top of the
requirements.txt
file to help give users a hint about where to update software requirements. -
The
pip freeze
command is used to create arequirements.txt
file from the current virtual environment
Refreshing requirements
To update the requirements, update the requirements.txt
with these manual steps:
refresh_all_requirements:
@cat /dev/null > requirements.txt
@if [ $$(uname -s) == "Darwin" ]; then sleep 1; fi # this is require because Darwin HFS+ only has second-resolution for timestamps.
@touch requirements.txt.in
@$(MAKE) requirements.txt
Now requirements.txt
can be updated with
make refresh_all_requirements
This can be done periodically, and the new requirements.txt
updated in the version control system.