Some time ago I started seeing pyproject.toml in almost every Python package and also tools like Poetry getting common. So, I started using it too but wondered what happened to setup.py and simple old pip.

Here is how a setup.py would usually look like, you could install this package using pip install astromech-drivers.

from setuptools import setup, find_packages

setup(
    name='astromech-drivers',
    version='2.2.0',
    description='Drivers for the class-2 astromech droids.',
    url='https://starwars.fandom.com/wiki/Astromech_droid',
    author='sh4x2',
    author_email='sh4x2@industrialautomation.com',
    classifiers=[
        'Programming Language :: Python :: 3.8',
        'Programming Language :: Python :: 3 :: Only',
    ],
    packages=find_packages(where='src'),
    package_dir={"": "src"},
    python_requires='>=3.8',
)

So I started looking for answer to the question…

Why pyproject.toml?

To package your python module, you need to build its distribution. This distribution can then be used by the users to install the package on their system.

Python separates the build tooling into backend and frontend. backend refers to tools like setuptools that are used to build a distribution. frontend refers to tools like pip that serves as an interface to communicate with a backend. The setup.py above uses a setuptools backend and pip as a frontend. For a long time, setuptools acted as a gatekeeper for Python build systems making it impossible to use anything else in its place.

PEP 517 came with a change, it specifies a standard format to develop other build backends like setuptools. Complimentarily, PEP 518 specifies the format in which the build frontend and backend can interface using a pyproject.toml file. pyproject.toml is now the default way to build distribution and setup.py is only used if pyproject.toml is not preset.

Here is how a corresponding pyproject.toml file looks like for the astromech-drivers package.

[project]
name = "astromech-drivers"
version = "2.2.0"
description = "Drivers for the class-2 astromech droids"
authors = [
    {name = "sh4x2", email = "sh4x2@industrialautomation.com"},
]
requires-python = ">=3.8"

[tool.setuptools.packages.find]
where = ["src"]

[build-system]
requires = ["setuptools", "setuptools-scm"]
build-backend = "setuptools.build_meta"

Key thing to notice here is the build-system specification. This is used to specify the requirements as to which build backend is supposed to be used. In setup.py, you had to assume that this requirement was already satisfied before attempting to build the package. Using pyproject.toml makes it easier for tools like pip or poetry to first fetch the build backend requirements before attempting to build the package.

Poetry

I was using simple old pip up until recently. Yes, it gives you occassional headaches but for most simpler projects it was sufficient. Combine it with pyenv and pyenv-virtualenv and it makes it straightforward to setup and start working on a new project. Part of the problem with pip is also its simplicity. So you have to always combine it with other libraries to improve your life. Sometimes I don’t care so much about a light-weight setup and just have all the features in place for me to start development.

For a very long time I tried avoiding the switch to anything else but given the popularity of Poetry, it is not too far away (if not already) that most people will start using it. It has got most of the tools in one place and with support to add more plugins I can imagine things only getting better. Now I am not diving deep into features of Poetry, you the documentation for that. But here are is how I usually use it.

I still manage python versions using pyenv because I love the simplicity, however, I use the virtualenv feature of poetry because it does the automatic switching.

pyenv install 3.10.10
poetry env use 3.10.10

Another thing I like about poetry is grouping dependencies together, this is way better than requirements-{suffix}.txt.

poetry add droidio==2.2.0 --group dev 

Distribution

There are two types of distributions

  • Source Distribution: Commonly referred to as sdist is a distribution of source code and any other files needed to compile and install the package.
  • Build Distribution: Commonly referred to as wheel is a binary distribution that contains the pre-compiled binaries of the package. This means no additionally compilation step is necessary during installation and it is faster to install.

Build frontends first look for build distributions and fall back to source distributions while installation. Most packages provide both, preferably build distribution but at least source distribution. When you publish your package to a repository like PyPI, you basically upload your distribution for others to download, for example with pip install astromech-drivers. You might have seen python eggs, these are older distribution format in setuptools that are now deprecated in favour of wheels