pyproject.toml: Modern Python Dependency Management
Source: Dev.to
Introduction
pyproject.toml is the contract behind modern Python dependency management.
Older projects relied on setup.py or guessed build requirements, which led to fragile builds and non‑reproducible environments. This article breaks down the key PEPs (518, 517, 621) and shows how pyproject.toml works together with python -m venv + python -m pip for predictable installs.
The shift from executable configuration to static metadata
Historically, project configuration was written as executable Python code. While flexible, this approach allowed side effects and made static analysis difficult, resulting in unreliable builds.
pyproject.toml introduces a static, declarative format that tooling can parse without executing code.
Build system declaration
The [build-system] table tells installers what they need before building the project.
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
Because the interface is standardized, pip can work with multiple back‑ends (setuptools, hatchling, flit, poetry‑core) through a common API.
Project metadata
The [project] table standardizes name, version, dependencies, authors, and more—without any dynamic code.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "my-service"
version = "0.1.0"
description = "Internal API example"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.110",
"uvicorn[standard]>=0.29",
"pydantic>=2.7"
]
[project.optional-dependencies]
dev = [
"pytest>=8",
"ruff>=0.5",
"mypy>=1.10"
]
Optional dependencies
The optional-dependencies table lets you define groups such as dev, which can be installed with pip install -e ".[dev]".
Benefits
| Area | How pyproject.toml helps |
|---|---|
| Project clarity | All metadata lives in a single, readable file. |
| Reproducible installation | Tools know exact build requirements and supported Python versions. |
| Sustainable dependency management | Centralized declarations reduce drift and make lock‑file generation straightforward. |
Typical workflow
python -m venv .venv
source .venv/bin/activate
python -m pip install -e . # install in editable mode
python -m pip install -e ".[dev]" # install optional dev dependencies
In editable mode, source‑code changes are reflected without reinstalling the package each time.
Common pitfalls
- Missing or incorrect
[build-system]– builds fail or behave inconsistently across machines. - Duplicated dependency declarations (e.g., in both
requirements.txtandpyproject.toml) – leads to drift. - Omitting
requires-python– packages may install on incompatible interpreters and crash at runtime. - Relying on loose version ranges without a lock file – results in different dependency graphs between CI and local environments.
Tooling landscape
| Tool | Relationship to pyproject.toml |
|---|---|
| venv | Simple, standard library solution; works out‑of‑the‑box. |
| virtualenv | Offers more options and can be faster in some scenarios. |
| poetry | Provides an integrated workflow for dependency resolution, publishing, and lock‑file management, all driven by pyproject.toml. |
All of these can coexist with pyproject.toml, which has become the common language of Python packaging.
Troubleshooting environment issues
If an environment starts failing “out of nowhere” after many package experiments, the problem is often the environment state rather than the code. Recreating the virtual environment from pyproject.toml is usually faster than patching inconsistent installs one by one.
- Stale environments – Throwaway environments left behind by experiments can confuse imports. Tools (e.g., KillPy) can scan for and list stale environments by age/size, allowing safe removal.
Conclusion
pyproject.toml is not a passing trend; it is the modern foundation of Python environments and dependency management. For real reproducibility, reduced team friction, and predictable builds, place this file at the center of your workflow.