Gids voor pyproject.toml: packaging, dependencies en tools
Automatische vertaling Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.
Oudere Python-projecten verspreiden configuratie vaak over setup.py, setup.cfg, requirements-bestanden, MANIFEST.in en aparte bestanden voor developmenttools. pyproject.toml biedt deze verantwoordelijkheden een gemeenschappelijke plek, maar vervangt niet elk projectbestand en maakt niet elke sectie onderdeel van één standaard.
Deze gids behandelt het bestand van buildconfiguratie tot projectmetadata en toolinstellingen. Vier eigenaars houden de secties gescheiden: wat het project bouwt, wat het project publiceert, wat contributors lokaal nodig hebben en welke configuratie individuele tools beheren.
TL;DR. Gebruik
[build-system]voor de backend die een distribution bouwt,[project]voor gepubliceerde metadata en runtime requirements,[dependency-groups]voor niet-gepubliceerde development environments en[tool.*]alleen wanneer de documentatie van die tool dat voorschrijft. Een dependency declaration is geen lockfile.
Eén bestand, vier eigenaars
Drie packaging-standaarden definiëren de kernstructuur:
- PEP 518 definieert build-system requirements.
- PEP 621 definieert projectmetadata.
- PEP 735 definieert niet-gepubliceerde dependency groups.
Toolauteurs kunnen ook een namespace onder [tool] claimen. Er is niets gestandaardiseerd aan wat daar staat: elke tool definieert zijn eigen keys en gedrag.
Hier is een kleine packaged library:
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
[project]
name = "weather-client"
version = "0.1.0"
description = "A small weather API client"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.27",
]
[project.optional-dependencies]
cli = ["rich>=13"]
[project.scripts]
weather = "weather_client.cli:main"
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
[tool.pytest.ini_options]
testpaths = ["tests"]
[tool.ruff]
line-length = 100
target-version = "py311"
Elke dependency list beantwoordt een andere vraag.
[build-system]: hoe source code een distribution wordt
Een build frontend zoals python -m build, pip of uv roept een build backend aan. De backend bepaalt hoe de source tree wordt omgezet in een sdist of wheel en welke bestanden in die artifacts terechtkomen.
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
requires bevat dependencies die nodig zijn om de backend in zijn geïsoleerde build environment uit te voeren. Dit is niet de lijst met runtime dependencies van je package.
Declareer een build system wanneer het project een distribution produceert of eigen code als package moet installeren. Een non-package-project kan pyproject.toml alsnog gebruiken; PEP 735 staat zelfs een bestand toe dat alleen dependency groups bevat. Environment managers verschillen in de manier waarop ze omgaan met een project zonder build system, dus maak deze keuze bewust.
Kies de backend op basis van de build requirements:
- pure-Python layout en vereisten voor file selection
- compiled extensions of externe build systems
- vereisten voor dynamische versies of gegenereerde bestanden
- gedrag van editable installs
- volwassenheid van de backend in de release pipeline
Neem de actuele aanbevolen table van de backend over uit de documentatie. Voeg wheel niet uit gewoonte toe aan de build requirements; de backend declareert wat hij nodig heeft.
[project]: metadata die consumers ontvangen
De [project]-table beschrijft de distribution: de naam, versie, Python-compatibiliteit, runtime dependencies, entry points en andere indexmetadata.
[project]
name = "weather-client"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27"]
De dependency specifiers zijn constraints voor een resolver, geen snapshot van één environment. Ze worden onderdeel van de metadata van wheel en sdist, zodat downstream installers ze kunnen combineren met requirements van andere packages.
Extras zijn een publieke installation interface
[project.optional-dependencies] definieert extras die consumers kunnen opvragen:
[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]
Een consumer kan weather-client[cli] installeren. Omdat extra-namen en requirements worden gepubliceerd, moet je ze behandelen als product capabilities. Gebruik geen extra met de naam dev alleen om alle contributor-tools in onder te brengen.
Entry points verbinden geïnstalleerde commands met Python
[project.scripts]
weather = "weather_client.cli:main"
Nadat de distribution is geïnstalleerd, stelt de environment weather beschikbaar. Deze importeert en roept weather_client.cli:main aan. Test de command vanuit een gebouwde wheel, niet alleen vanuit de root van de repository; de wheel is wat users ontvangen.
[dependency-groups]: lokale, niet-gepubliceerde environments
PEP 735 dependency groups beschrijven development- of non-package-environments zonder ze als package metadata te publiceren.
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
]
Dit is de juiste grens voor tests, linters, documentation builders en vergelijkbare tools voor contributors. Het is ook nuttig voor applications of notebooks die geen distribution bouwen.
Dependency groups zijn gestandaardiseerde data, maar interfaces van installers verschillen nog steeds. Controleer hoe de gekozen environment manager ze installeert, lockt en resolved. PEP 735 definieert geen universele command-line-interface.
[tool.*]: configuratie die door één tool wordt beheerd
Tooltables delen geen schema:
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
Gebruik een [tool.*]-table alleen als de tool dit documenteert. Sommige instellingen horen nog steeds in hun eigen bestanden, omdat een ander ecosysteem ze gebruikt, het bestand een ander format nodig heeft of de configuratie daar duidelijker is. pyproject.toml is een coördinatiepunt. Het verplicht je niet om elke instelling te centraliseren.
Declarations, locks en requirements-bestanden lossen verschillende problemen op
Een veelvoorkomende bron van verwarring is dat elk dependency-artifact wordt behandeld als een concurrerende source of truth.
| Artifact | Primair doel | Typische inhoud |
|---|---|---|
[project.dependencies] | Gepubliceerd runtime-contract | Directe requirements en compatibele ranges |
[project.optional-dependencies] | Gepubliceerde opt-in capabilities | Extras voor consumers |
[dependency-groups] | Niet-gepubliceerde lokale environments | Groups voor tests, linting, docs of applications |
| Lockfile | Een resolved environment reproduceren | Exacte versies, sources en resolution metadata |
requirements.txt | pip-compatibele installation input | Requirements plus pip-specifieke opties, constraints, URLs of hashes |
Een library publiceert doorgaans compatibele constraints en test tegen een range. Een application commit meestal de lockfile van de environment manager. Exacte pins horen in het resolved deployment-artifact, niet zonder meer in de publieke metadata van een library.
Behoud een requirements-bestand wanneer een integratie het format of de features van pip vereist. Als een uv-managed project er één nodig heeft, exporteer het dan vanuit de lock in plaats van twee onafhankelijke dependency sets te onderhouden:
uv export --format requirements.txt --output-file requirements.txt
De export is afgeleide compatibility output. De lock blijft de resolved source voor die workflow.
Een migratie die gedrag behoudt
Begin niet met het verwijderen van setup.py of requirements.txt. Classificeer eerst wat elk bestaand bestand doet.
- Inventariseer buildlogica, metadata, runtime requirements, extras, development environments, toolinstellingen, package data en entry points.
- Selecteer een backend die de huidige file inclusion, compiled artifacts en editable installs kan reproduceren.
- Verplaats statische, gepubliceerde metadata naar
[project]; houd echt dynamische velden expliciet. - Verplaats requirements die uitsluitend voor contributors zijn naar
[dependency-groups], niet naar publieke extras. - Verplaats toolinstellingen alleen wanneer de tool equivalente semantics ondersteunt.
- Bouw zowel een sdist als een wheel, inspecteer hun inhoud en installeer de wheel in een schone environment.
- Voer entry points, tests, import checks en het daadwerkelijke deploymentpad uit.
- Verwijder oude configuratie pas nadat de artifacts en het gedrag overeenkomen.
MANIFEST.in kan bij sommige setuptools-layouts nog nodig zijn, en een kleine setup.py kan geldig blijven voor programmatische buildlogica. Modernisering verplaatst het eigenaarschap tussen bestanden. Bestanden verwijderen is niet het doel.
Een actuele uv-workflow
uv maakt bij het aanmaken van een project onderscheid tussen applications en libraries:
# Non-library application template
uv init weather-app
# Packaged library with a src layout and build system
uv init --lib weather-client
uv init maakt projectbestanden aan. De eerste projectoperatie, zoals uv run, uv sync of uv lock, maakt indien nodig de lockfile en persistente .venv aan.
cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build
uv plaatst lokale development requirements momenteel in gestandaardiseerde dependency groups. De native uv_build-backend is één optie voor pure-Python-projecten; compiled extensions hebben een geschikt alternatief nodig, zoals maturin of scikit-build-core. Zie uv’s documentatie over projectconfiguratie voor de actuele backendinstellingen.
Conclusie
pyproject.toml is duidelijk wanneer elke table één doelgroep heeft. Build isolation hoort in [build-system] en gepubliceerd gedrag in [project]. Contributor environments horen in [dependency-groups]. Toolinstellingen horen bij de tool die ze definieert.
Zodra deze grenzen stabiel zijn, wordt het bestand eenvoudiger te reviewen en zorgen migraties niet langer voor verwarring tussen package metadata en de resolved environment van één machine.