pyproject.toml (PEP 517 / 518 / 621) is the modern Python project file. It replaces setup.py, setup.cfg, and a dozen tool-specific config files. Inside it: build system, package metadata (name, version, dependencies), and tool configurations (ruff, mypy, pytest, etc.).
The two essential sections
[build-system] tells pip how to build your package — the build backend (e.g., hatchling, setuptools, poetry-core). [project] contains the metadata: name, version, description, dependencies, Python version requirement, license. Tool sections ([tool.ruff], [tool.mypy], etc.) are optional.
Dependencies — pinned vs unpinned
In [project.dependencies], list runtime dependencies. Pin loosely (requests >= 2.31) for libraries — over-pinning makes you incompatible with everyone. Pin tightly (requests == 2.31.0) for applications — you want reproducible deploys. [project.optional-dependencies.dev] for dev-only deps (pytest, ruff).
Editable installs — the dev pattern
pip install -e . (or uv pip install -e .) installs your package in "editable" mode — Python imports from your source directory, so edits show up immediately without re-installing. Indispensable when developing a package.
Pythonic Way: Every Python project gets a pyproject.toml, even small ones. It's the canonical place for tool configuration (ruff/mypy/pytest), and every modern tool reads it. Setup.py is deprecated for new projects.
Create a tiny package directory /tmp/mini_pkg/ with: pyproject.toml (using hatchling as build backend, name 'mini-pkg', version '0.1', a single dependency on 'click'), mini_pkg/__init__.py (defines __version__ = '0.1'), mini_pkg/cli.py (a click command that prints 'hi'). Run pip install -e /tmp/mini_pkg/ in a venv and confirm from mini_pkg import __version__ works.
Progress
Progress is local-only — sign in to sync across devices.