In this post I’ll describe all (hopefully all!) the various ways you can specify a single dependency for a Python package.
This assumes pip is used for installation. The list of dependencies then goes either in
parameter of the
setup function within setup.py, or as a separate requirements.txt file. Commonly, it will actually
go in both places, with the latter being the canonical source of truth:
from setuptools import setup with open('requirements.txt') as rf: setup( # ... install_requires=rf.readlines(), )
More details about this approach can be found in one of my previous posts.
Here, I will concentrate on the format of a single line in requirements.txt that defines a dependency. There are numerous variants that pip supports, and they are all described in excruciating detail in PEP 440. This post shall serve as a short reference on the most useful ones.
Package name (and version)
The simplest and most common option is to identify a dependency by its package name:
Without any further modifiers, pip will download and install the “current” version of the package — either the newest, or the one designated explicitly by a maintainer. This obviously makes the dependency somewhat unpredictable, for it can mean unintended upgrades that introduce breaking changes to your code.
To prevent this, you’d normally pin the dependency to an exact version2:
Other comparison operators are also available:
and can even be combined:
Specs like that will make pip find the newest version that’s within given range. Assuming your dependency follows the semantic versioning scheme, this will allow you to stay on top of any minor bugfixes and improvements to an older release (0.9.x here), without the risk of accidentally upgrading to a new one (1.x) that your code is not compatible with yet.
Sometimes you want to live on the bleeding edge, though, and depend not just on the latest release, but the head commit to the package’s repository. This makes sense especially in large systems that are distributed among multiple repos, and where development happens in lockstep.
For those occasions, and a few others, pip can recognize direct repository URLs. They are in the format:
$PROTOCOL part can be optional if the version control system has a default there. That’s for example
the case for
git, which is of course the most important VCS you’d be interested in3:
Note that the
#egg=$PACKAGE part is not a part of the
$URL, and it’s only there to give a local name for the package
distribution. This is what makes it possible to refer to it later via pip, if only to remove it with
pip uninstall $PACKAGE.
Of course, the sanest practice is to use the PyPI moniker if possible.
$LABEL is given, pip will use the HEAD, trunk, tip, or the equivalent default/current revision from the repo.
Often though (at least in case of Git), you would also pick a branch, tag, or even a particular commit hash:
git+https://github.com/Xionemail@example.com#egg=unmatcher git+ssh://github.com/You/yourpackage.git@master#egg=yourpackage git+https://github.com/mitsuhiko/jinja2.git@5b498453b5898257b2287f14ef6c363799f1405a#egg=Jinja2
The last two options could be a good choice even with third party packages, when you don’t want to wait for a new PyPI release to get a necessary feature or an urgent bug fix.
Lastly, you can ask pip to install a package from a local directory or archive. The former option is often used
--editable) flag for
pip install. This installs the package in the so-called development mode,
allowing you to edit its source code in-place:
$ pip install -e /home/me/Code/myotherpackage
You almost certainly don’t want to put this line in requirements.txt: you should still be pulling the other package from PyPI. But if it’s your own one — maybe a self-contained utility library used by your main program — this setup will be very helpful for making changes to it, informed by your own usage of the package.
This can be changed with
pip install. Running local indexes is a good practice for Python shops, especially those that rely on
pip installas part of their deployment process. ↩
If the package uses semantic versioning, a possible alternative to
~=, which means “compatible” version. The precise meaning of this is somewhat complicated, but it roughly means that upgrades are permitted as long as nothing in the public interface changes. ↩