Python Packaging

As of 2020 (PEP 621), the standard way of packging a python project is by using a pyproject.toml file. Using setup.cfg as config file for building a python project is deprecated. You can still use setup.py (if you use setuptools as your build backend) as your build config file, but it is recommended to not use it as a command line tool. For example, instead of using python setup.py install, use pip install .. For details see here.

If you still prefer using setup.py as your build configuration file and setuptools as your build backend, it is still recommended to have a pyproject.toml file with the [build-system] section specifing the use of setuptools as your build backend. Here, I choose to use hatchling as the build backend.

Below is a minimum pyproject.toml file for packaging a python project

Minimal pyproject.toml file

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "my_project"
version = "1.0"

The [build-system] section is required, and there are many build-backends available. In the requires key, we should specify all the dependencies required at build time. If we are simply running pip install in non-editable mode these build dependencies will be install in an isolated build environment. For the build-backend field, we specify the module to be called to build the project.

There are many other build backend we can use. For a pure python project, hatchling is a good choice as is has some good defaults out of the box.

For projects with C/C++/Cython extensions, we can use meson-python (if you prefer meson as your C/C++ build system) or scikit-build-core (if you prefer CMake as your C/C++ build system). I will probably cover this in another post.

Other useful sections

For a complete example of pyproject.toml, see here. Here, I want to just highlight a few sections that I find useful.

[project]
requires-python = ">= 3.10"
dependencies = [
  "numpy",
  "pandas<2.0",
]

The above specifies the minimum python supported by your project and the run time dependencies of your project.

[project.scripts]
command-one = "my_proj:main"
command-two = "my_proj.my_module:func"

The [project.scripts] section lets you sepcify specific functions in your package to expose as command-line executables. In the example above, after installing your package, running command-one directly from the command line will call your main function in the my_proj module. Similarly command-two executes your function func in the module my_proj.my_module.