pyproject.toml isn’t just a new place to put your package metadata; it’s a fundamental shift in how Python projects are built, configured, and distributed.
Let’s see this in action. Imagine you have a simple Python package, my_package, that you want to distribute.
# pyproject.toml
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "my-package"
version = "0.1.0"
authors = [
{ name="Your Name", email="your.email@example.com" },
]
description = "A simple example package"
readme = "README.md"
requires-python = ">=3.8"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
"Homepage" = "https://github.com/yourusername/my-package"
"Bug Tracker" = "https://github.com/yourusername/my-package/issues"
Now, if you were to build this package, you’d use a tool like pip or build:
pip install build
python -m build
This command, when executed in the same directory as your pyproject.toml, will use the [build-system] section to determine how to build your package. It knows to use setuptools as the build backend because you specified "setuptools.build_meta". The [project] section then provides all the metadata that setuptools (or any other build backend) needs to construct your package distribution files (like .whl and .tar.gz).
Before pyproject.toml, this metadata lived in setup.py or setup.cfg. pyproject.toml centralizes this configuration, making it the single source of truth for your project’s build process and metadata. The [build-system] table is the key innovation here: it decouples the build system itself from the project metadata, allowing for different build backends (like flit, poetry, pdm, or setuptools) to be used without changing the core project information. This means your project’s build process is no longer dictated solely by setuptools; you can choose the build tool that best suits your needs.
The [project] table adheres to PEP 621, providing a standardized way to declare project metadata that is consumable by any PEP 517-compliant build frontend. This includes essential details like the package name, version, authors, description, and requires-python. It also supports more advanced features like dependencies, optional-dependencies (for features like [project.optional-dependencies]), and entry-points. The readme field, for instance, tells the build backend where to find your README.md file, which will then be included in your package’s distribution.
Most people understand pyproject.toml as a replacement for setup.py for package metadata. What they often miss is that the [build-system] table itself is a configuration for the installer, not the project. When pip installs your package, it first reads pyproject.toml to understand how to build the package if it’s not already built (e.g., from a source distribution). It uses the requires list to install the necessary build tools into an isolated environment, and then invokes the build-backend to perform the actual build. This isolation is critical; your project’s build process doesn’t interfere with your main Python environment, and vice-versa.
The next concept you’ll naturally explore is how to manage development dependencies and environment isolation using tools that leverage pyproject.toml, such as Poetry or PDM.