Back in 2018, I wrote about using
setup.py in your Django/Python project. Five years later,
setup.py is being phased out in favor of
pyproject.toml. I’m a big fan of this change. With
setup.py you could really go off the rails making everything dynamic or even executing malicious code during the installation process. In contrast,
pyproject.toml moves the ecosystem towards a configuration file that can be parsed without executing arbitrary code. You can read more about the rationale behind
pyproject.toml in PEP-517, PEP-518, PEP-621, and PEP-660.
If you’re using
pdm, or any of the other newer Python build systems, you’re already using
pyproject.toml. How about folks that are using plain old
pip-tools? You can still take advantage of this new file format and ditch
setup.cfg as well. Most third-party tooling supports configuration via the
tool section defined in PEP-518.
To start, define the build system for your project. To avoid introducing new tools, we’re going to use good ol’ setuptools:
Next, define your project and its dependencies:
At this point, you can run this to bootstrap a local development environment:
In our previous post about using
setup.py we showed a trick that would allow you to remove
manage.py from your repo and have it installed as a “proper” script on the
PATH. You can add the functionality to
myproject/__init__.py like this:
Now add this to your
When you install your project, it will create a
manage.py script on your path so you can run it like any other command,
$ manage.py ....
pip-tools dependency locking
What we have so far is all well and good, but you really should be locking/freezing your dependencies to ensure the exact same requirements are installed every time.
pip-tools allows you to create these in a format where only
pip is required to install them elsewhere. For your primary dependencies, you can replace where you might have previously used
You could do the same with your dev requirements by using the
--extra dev flag:
There’s an issue here, however. We want to ensure
requirements-dev.txt isn’t installing packages that are incompatible with what’s in
pip-tools suggests a workflow for this involving the
--constraint flag, but it is incompatible with
pyproject.toml dependencies. Here’s an ugly workaround for this situation:
--strip-extrasflag when you build your
requirements.txtfile so it can be used as a pip constraint. Don’t worry, the same dependencies will be installed.
Pass that constraint in when you generate your dev requirements. To avoid adding more files to our project, we pass it via stdin.
Tip: I like to hide these long commands in a
Justfile so developers only need to remember
To install dependencies from your lock file and also install your project, you can pass the
--no-deps flag to
pip to make sure it doesn’t try to reinstall the un-pinned dependencies in
It’s nice to trade
.coveragerc, etc. with one file which defines everything Python related for your project. I hope we get the same for lock files, but with the rejection of PEP 665 we’ll have to wait a bit longer for that.