pyproject.toml
Sources:
Pyproject is like a blueprint that tells tools like pip how to construct your project.
The basic structure:
- [build-system]: Defines the tools needed to build your project (e.g., setuptools or flit_core).
- [project]: Houses metadata like your project’s name, version, and dependencies.
- [tool]: Configures settings for development tools like linters or formatters.
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-project"
version = "0.1.0"
description = "A simple project"
dependencies = ["requests>=2.25.0"]
Advantages of using pyproject.toml
- Standardization: consistency is very important in software engineering
- Isolation: The [build-system] section ensures build dependencies are isolated from your runtime environment. This means your project builds the same way on your laptop as it does on a CI server
- Flexibility:
pyproject.tomlis tool agnostic, allows to switch build backends (e.g. from setuptools to Poetry) with minimal changes to the file.
Problems with setup.py and requirements.txt
setup.py it is known to pose security risks, since the code is executed on download of a sdist format package by pip, and is editable by the author. So they may insert some malicious code. This is partially solved by wheels and setup.cfg but dummy setup.py may still be found.
requirements.txt isn’t flexible in different context. For example when you want some packets in production and some others in development. You might end up with multiple requirements. Furthermore, installing a project that is structured like this (with source files alongside configuration files) for local development has knock on effects on writing tests, as imports may be inaccurate reflections of their path when installed as a full package.
Managing dependencies with pyproject.toml
[project]
name = "cool-python-project"
version = "0.1.0"
dependencies = [
"numpy == 1.26.4",
"structlog == 22.1.0"
]- Main metadata comes under
[project]header dependencieskey is used to define dependencies
After that, you can install it with: pip install -e. The -e means that pip must perform an editable install, which means imports of the project resolve at the source code in the repo.
Dependencies will be installed into the site_packages folder in python virtual environments.
The pyproject.toml file allows to specify optional dependencies (for instance when you want a “development” and “production” different versions):
We can specify for example dependency groups in the following way:
[dependency-groups]
test = [
"moto[s3] == 4.2.11",
"pytest == 8.2.0",
"behave == 1.2.6",
]and install only the specifies dependencies in test by running: pip install -e .[test].
[dependency-groups]
test = [
"moto[s3] == 4.2.11",
"pytest == 8.2.0",
"behave == 1.2.6",
]
lint = [
"ruff == 0.4.4",
"mypy == 1.10.0",
]
dev = [
"cool-python-project[test,lint]",
]To add a linter or other fixing tools like ruff, we can add it like:
[tool.ruff]
line-length = 100
indent-width = 4
[tool.ruff.format]
quote-style = "double"
indent-style = "space"
line-ending = "auto"
[tool.ruff.lint]
select = [
"F", # pyflakes
"E", # pycodestyle
"I", # isort
"ANN", # flake8 type annotations
"RUF", # ruff-specific rules
]
fixable = ["ALL"]
[tool.ruff.lint.pydocstyle]
convention = "google"
Adding ruff into the pyproject.toml configuration file like this, as well as pinning the version of ruff in the dependencies, ensures that any other developers of our code will have the same working configuration of ruff present to keep their code style consistent with the already existing codebase. In this manner, a uniform development experience can be had by all contributors
Entrypoints
For example if the codebase is structured as follows:
cool-python-project
├── pyproject.toml
└── src
└── my_package
└── main.py
Assuming that main.py contains a main() function, and adding this:
[project.scripts]
coolprojectcli = "my_package.main:main"Now, with this line, setuptools will create a script called coolprojectcli inside the venv’s bin folder that runs the main function.
Furthermore, because we performed an editable install, the codebase is still the source of truth for this script, so modifications to main will be reflected when running the script.
More details on automation, CI, Docker and automatic name versioning can be find in the rest of the guide: #background-why-do-we-need-pyproject