Ga naar inhoud

Automatische vertaling

Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.

pyproject.toml-gids: Python-packaging, afhankelijkheden en toolconfiguratie

Als je ooit een Python-project hebt geopend en hebt geprobeerd uit te zoeken waar afhankelijkheden, buildinstellingen en toolconfiguraties nu eigenlijk staan, dan ken je die frustratie. setup.py, setup.cfg, requirements.txt, MANIFEST.in, plus een handvol dotfiles voor elke linter en formatter — allemaal lezen ze van verschillende plekken.

pyproject.toml brengt het meeste daarvan samen in één bestand.

TL;DR

pyproject.toml is ongeveer de package.json voor Python. Eén bestand bevat je projectmetadata, afhankelijkheden en toolinstellingen. Of je nu .venv, pyenv of uv gebruikt, alles hier plaatsen maakt setup en samenwerking eenvoudiger.

Wat is pyproject.toml?

Het is een configuratiebestand dat in de root van je Python-project staat, geschreven in TOML (denk aan INI-bestanden, maar dan met een echte specificatie). Twee PEP's hebben gevormd wat het vandaag doet:

  • PEP 518 (2016) introduceerde de tabel [build-system] zodat buildtools op een gestandaardiseerde manier hun vereisten konden declareren.
  • PEP 621 (2020) voegde de tabel [project] toe voor kernmetadata van packages: naam, versie, afhankelijkheden, dat soort dingen.

Tegenwoordig leest de meeste Python-tooling (Black, isort, pytest, Ruff, mypy) zijn configuratie uit [tool.*]-secties in dit bestand.

Structuur

Waarom het belangrijk is

Minder bestanden om na te jagen

Een typisch legacy-project jongleerde met setup.py, setup.cfg, requirements.txt, MANIFEST.in en een zwerm dotfiles (.flake8, .coveragerc en vergelijkbare). Het meeste daarvan valt nu samen op één plek.

Backend-agnostische builds

Wanneer je pip install . uitvoert, leest pip pyproject.toml en installeert het alle buildtools die je project nodig heeft: setuptools, flit, hatchling, wat je maar wilt. Je zit niet langer vast aan setuptools.

Eén plek voor toolconfiguratie

Linters, formatters, testrunners en typecheckers weten allemaal dat ze hier moeten kijken. Je IDE, CI-pipeline en teamgenoten lezen uit hetzelfde bestand.

Tool-ecosysteem

Anatomie van een pyproject.toml

Een typisch bestand heeft drie hoofdsecties:

# 1. Build system - tells pip/build how to package your project
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

# 2. Project metadata and dependencies
[project]
name = "awesome-app"
version = "0.1.0"
description = "Short demo of pyproject.toml"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
  "fastapi>=0.111",
  "uvicorn[standard]>=0.30",
]

# Expose CLI commands
[project.scripts]
awesome-cli = "awesome_app.cli:main"

# Optional dependencies (e.g., for development)
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]

# 3. Tool configuration
[tool.ruff]
line-length = 100
target-version = "py312"

[tool.pytest.ini_options]
addopts = "-ra -q"
testpaths = ["tests"]

Uiteengezet

[build-system]

Verplicht als je je project wilt packagen of distribueren. Vertelt pip en buildtools (zoals python -m build) welke backend ze moeten gebruiken.

[project]

Je packagemetadata. Hier staan afhankelijkheden in plaats van in requirements.txt.

  • dependencies: de runtime-vereisten voor je package.
  • optional-dependencies: groepen extra afhankelijkheden (dev, test, docs).
  • scripts: maakt uitvoerbare commands aan. In het voorbeeld hierboven geeft het installeren van het package je een awesome-cli-command dat de functie main in awesome_app/cli.py uitvoert.

[tool.*]

Configuratie voor elke tool die dit ondersteunt. Elke tool krijgt zijn eigen namespace: [tool.pytest.ini_options], [tool.mypy], [tool.ruff] en de rest.

Vervangt het requirements.txt?

Voor moderne workflows: ja. Tools zoals Poetry, PDM, Hatch en uv slaan afhankelijkheden direct op in de sectie [project] en genereren lockfiles voor reproduceerbaarheid.

Je wilt waarschijnlijk nog steeds requirements.txt als:

  • Je werkt met een legacy-deploymentsysteem dat dit verwacht.
  • Je een CI-script hebt dat nog niet is bijgewerkt.

De meeste moderne tools kunnen er indien nodig een exporteren vanuit je pyproject.toml:

uv export > requirements.txt

Een build-backend kiezen

De build-backend is een van de meer verwarrende keuzes. Hier is een snelle vergelijking:

Backend Beste voor Voordelen Nadelen
Hatchling Moderne standaard Snel, uitbreidbaar, ondersteunt plugins Nieuwer, minder legacy-ondersteuning
Flit Eenvoudige packages Extreem eenvoudig, zero config Niet voor complexe builds (C-extensies)
Setuptools Legacy, complex Ondersteunt alles (C-extensies, etc.) Trager, meer configuratie
Poetry Poetry-gebruikers Geïntegreerd met het Poetry-ecosysteem Vast aan de Poetry-workflow

Voor nieuwe pure-Python-projecten is Hatchling een redelijke standaardkeuze. Dat is wat uv init voor je schrijft.

Een bestaand project migreren

Als je een legacy Python-project hebt, kun je het zo moderniseren:

Migratieflow

  1. Voeg [build-system] toe. Als je niet zeker weet welke backend je moet gebruiken, begin dan met requires = ["setuptools>=61", "wheel"].
  2. Verplaats metadata naar [project]. Zet naam, versie en afhankelijkheden over vanuit setup.py of setup.cfg.
  3. Converteer dev-afhankelijkheden. Zet ze in [project.optional-dependencies].dev.
  4. Configureer tools. Voeg [tool.*]-secties toe voor Black, pytest, mypy, enzovoort.
  5. Bepaal wat je met requirements.txt doet. Verwijder het, of genereer het vanuit je lockfile voor legacy-systemen die het nodig hebben.

Na de migratie kun je meestal setup.py, setup.cfg en de meeste van die config-dotfiles verwijderen.

Een paar nuttige functies

CLI-entrypoints

Gebruik in plaats van het oude blok console_scripts in setup.py gewoon [project.scripts]:

[project.scripts]
my-tool = "my_package.main:run"

Wanneer iemand je package installeert, kan die my-tool in de terminal typen.

Workspaces (monorepo's)

Tools zoals uv en hatch ondersteunen workspaces, waarmee je meerdere packages in één repo kunt beheren:

[tool.uv.workspace]
members = ["packages/*"]

Je kunt meerdere onderling afhankelijke packages ontwikkelen en ze allemaal in één virtual environment installeren om te testen.

Typische workflows met uv

Een nieuw project starten

uv init my_app          # creates folder with pyproject.toml and .venv
cd my_app
uv add requests fastapi # adds to [project.dependencies] and installs
uv run pytest           # runs tests in the venv

Scripts uitvoeren

Je kunt scripts definiëren in pyproject.toml (met een task runner zoals poe of hatch) of gewoon uv run aanroepen:

uv run python main.py

Praktische tips

  1. Pin geen exacte versies in libraries. Gebruik bereiken zoals requests>=2.30 zodat je library niet botst met wat er verder ook in iemands omgeving is geïnstalleerd.
  2. Pin versies juist wel in applicaties. Gebruik een lockfile (uv.lock, poetry.lock) voor reproduceerbare builds.
  3. Groepeer dev-afhankelijkheden. Houd afhankelijkheden voor testen, linting en documentatie in aparte optionele groepen (dev, test, docs).
  4. Gooi niet elke optie in pyproject.toml. Houd het bij projectbrede defaults en laat tool-specifieke configuratiebestanden de rest afhandelen als het rommelig wordt.

De echte winst van pyproject.toml is vooral het wegnemen van kleine, dagelijkse frictie. Je stopt met zoeken welk bestand eigenaar is van de build. De linterconfiguratie staat naast de afhankelijkheden. Pip en je IDE zijn het eindelijk eens over wat er is geïnstalleerd. Kies een build-backend, zet je afhankelijkheden in [project] en laat je tools van één plek lezen. Als je opnieuw begint, brengt uv init je met één command al een heel eind.