A Guide to Python's pyproject.toml

Discover the pyproject.toml file, the modern, unified standard for configuring Python projects. Learn how it replaces older files like setup.py and requirements.txt and how to use it to manage your project's dependencies and tools.

For years, configuring a Python project was a confusing affair. You might have a setup.py, a requirements.txt, a setup.cfg, and a MANIFEST.in, all for managing different aspects of your project's packaging and dependencies. Thankfully, the Python community has moved towards a single, unified standard: the pyproject.toml file.

Introduced in PEP 518, pyproject.toml is a TOML (Tom's Obvious, Minimal Language) file that is designed to be the central configuration file for your Python project. It's the modern way to define your project's metadata, dependencies, and the configuration for your development tools.

Why pyproject.toml?

  • Unified: It provides a single place to configure everything, from your project's name and version to the settings for your linter, formatter, and test runner.
  • Standardized: It's an official standard, which means that a wide range of modern Python tools know how to read and use it.
  • Declarative: Unlike the older setup.py, pyproject.toml is a static, declarative configuration file. This makes it easier for tools to parse and understand your project's build requirements without having to execute any code.

The Key Sections of pyproject.toml

Let's break down a typical pyproject.toml file.

The [build-system] Section

This is the only mandatory section. It tells pip and other build tools what backend to use to build your project. For most modern projects that use setuptools (the most common build backend), this will look the same:

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

The [project] Section

This is where you define all the metadata for your project. This section, defined in PEP 621, is the modern replacement for most of what used to go in setup.py.

[project]
name = "my-cool-package"
version = "0.1.0"
description = "A short description of my cool package."
readme = "README.md"
requires-python = ">=3.8"
license = { text = "MIT" }
authors = [
    { name = "Your Name", email = "your@email.com" },
]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: MIT License",
]

# This is where you list your project's dependencies
# It's the replacement for requirements.txt for libraries
dependencies = [
    "requests>=2.28",
    "pydantic>2.0",
]

Optional Dependencies

You can also define optional sets of dependencies (sometimes called "extras"). This is useful for dependencies that are only needed for certain features.

[project.optional-dependencies]
dev = [
    "pytest",
    "mypy",
    "ruff",
]
docs = [
    "sphinx",
]

A user can then install these with pip install my-cool-package[dev,docs].

The [tool] Section: Configuring Your Tools

This is where the real power of a unified configuration file shines. Most modern Python development tools can be configured in their own sub-table under the [tool] section.

# --- Configuration for Ruff (linter) ---
[tool.ruff]
line-length = 88
select = ["E", "F", "W", "I"]

# --- Configuration for Mypy (type checker) ---
[tool.mypy]
python_version = "3.11"
worn_return_any = true

# --- Configuration for Pytest (test runner) ---
[tool.pytest.ini_options]
minversion = "6.0"
addopts = "-ra -q"
testpaths = [
    "tests",
]

This is incredibly clean. Instead of having separate configuration files like .ruff.toml, mypy.ini, and pytest.ini, everything lives in one predictable place.

What About requirements.txt?

For a library that you intend to publish, you should define your dependencies in the dependencies array in pyproject.toml. This defines your abstract dependencies (what your library needs to run).

For an application, you may still want to use a requirements.txt file, but it should be used to "pin" the exact versions of all your dependencies (including sub-dependencies) for reproducible builds. You can generate this file from your abstract dependencies using a tool like pip-tools.

However, for many modern workflows, tools like Poetry or PDM manage this for you with a poetry.lock or pdm.lock file, which serves the same purpose as a fully-pinned requirements.txt.

Conclusion

The pyproject.toml file is a huge step forward for the Python ecosystem. It provides a single, standardized, and declarative way to manage your project's configuration. By embracing it, you can create projects that are easier to maintain, easier for others to contribute to, and aligned with the modern standards of the Python community.