pyproject.toml-gids: packaging, dependencies en tools
Automatische vertaling
Dit artikel is automatisch vertaald vanuit de oorspronkelijke Engelse versie.
Oudere Python-projecten verspreiden hun configuratie vaak over setup.py, setup.cfg, requirements-bestanden, MANIFEST.in en aparte bestanden voor ontwikkeltools. pyproject.toml geeft die onderdelen een gemeenschappelijke plek, al vervangt het niet elk projectbestand en maakt het van elke sectie nog geen onderdeel van één standaard.
Deze gids is bedoeld voor Python-ontwikkelaars die een package of een applicatie onderhouden. Je leest wat elk van de vier hoofdtabellen bevat ([build-system], [project], [dependency-groups], [tool.*]), hoe ze verschillen van een lockfile en requirements.txt, welke build backend je kiest, en hoe je een ouder project migreert en een nieuw project opzet met uv.
Eén bestand, vier tabellen
Drie packagingstandaarden leggen de kernstructuur vast:
- PEP 518 definieert de vereisten van het build system.
- PEP 621 definieert projectmetadata.
- PEP 735 definieert niet-gepubliceerde dependency groups.
Toolauteurs kunnen ook een namespace onder [tool] claimen. Niets standaardiseert wat daar staat: elke tool bepaalt zelf zijn keys en gedrag.
De figuur splitst het voorbeeldbestand uit de volgende sectie in zijn vier tabellen en laat zien welk programma elke tabel leest. Alleen [project] bereikt gebruikers, via de metadata in de gebouwde wheel.
Hier is een kleine library met packaging:
[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"
Het bestand bevat vier dependency-lijsten. dependencies bevat wat het package tijdens runtime nodig heeft, optional-dependencies wat gebruikers optioneel kunnen kiezen, [dependency-groups] wat contributors lokaal installeren, en [build-system].requires wat de backend nodig heeft om te bouwen.
[build-system]: hoe source een distributie wordt
Een build frontend zoals python -m build, pip of uv roept een build backend aan. De backend bepaalt hoe de source tree een sdist of wheel wordt en welke bestanden in die artefacten terechtkomen. In het voorbeeld noemt build-backend hatchling, en requires bevat wat de frontend installeert om die backend uit te voeren.
Een sdist is een bronarchief dat een installer vóór de installatie bouwt; een wheel bevat de bestanden die klaar zijn om te installeren.
Een build verloopt in deze volgorde. De frontend leest [build-system], installeert requires in een tijdelijke build-omgeving en start de backend. De backend leest [project] en schrijft de sdist en wheel.
requires bevat de dependencies die nodig zijn om de backend in zijn geïsoleerde build-omgeving uit te voeren. Het is niet de lijst met runtime dependencies van je package.
Declareer een build system wanneer het project een distributie oplevert of zijn eigen code als package geïnstalleerd moet worden. Een project zonder package kan nog steeds pyproject.toml gebruiken; PEP 735 staat zelfs een bestand toe dat alleen dependency groups bevat. Zonder build system installeert uv het project niet in zijn eigen omgeving: je eigen code importeren werkt dan alleen vanuit de root van de repository, en uv maakt geen [project.scripts]-commando’s aan. Stel tool.uv.package = true in of voeg een build system toe wanneer je een van beide nodig hebt (uv project packaging).
Een backend kiezen
Kies de backend op basis van wat de build moet doen:
| Backend | Gebruik hem wanneer |
|---|---|
uv_build | Het project is pure Python, wordt met uv beheerd en heeft geen build-plugins nodig. uv init schrijft hem. |
hatchling | Het project is pure Python en heeft build-plugins of een versie uit git nodig (hatch-vcs). |
flit-core | Het project is het eenvoudigste pure-Python-package en heeft geen build-plugins nodig. |
setuptools | Een bestaande setup.py, C-extensies of een eigen buildstap moeten blijven werken. |
poetry-core | Het team beheert het project met Poetry. |
maturin | De extensie is in Rust geschreven. |
scikit-build-core | De extensie is C of C++ en wordt met CMake gebouwd. |
meson-python | Het project bouwt met Meson, zoals NumPy en SciPy. |
De figuur plaatst de backends van builds die alleen Python-bestanden kopiëren tot builds die code compileren. Gebruik voor een versie op basis van Git-tags een backend die Git-versies ondersteunt, zoals hatchling met hatch-vcs of setuptools met setuptools-scm.
Kopieer de actuele aanbevolen tabel van de backend uit zijn documentatie. Voeg wheel niet uit gewoonte toe aan de build-vereisten; de backend declareert zelf wat hij nodig heeft.
[project]: metadata die consumenten ontvangen
De [project]-tabel beschrijft de distributie: naam, versie, Python-compatibiliteit, runtime dependencies, entry points en andere indexmetadata.
De dependency-specifiers, zoals httpx>=0.27 in het voorbeeld, zijn beperkingen voor een resolver, geen momentopname van één omgeving. Ze worden onderdeel van de metadata van wheel en sdist, zodat downstream installers ze kunnen combineren met vereisten van andere packages.
Extras zijn een publieke installatie-interface
[project.optional-dependencies] definieert extras die consumenten kunnen aanvragen. Met de extra cli uit het voorbeeld kan een consument weather-client[cli] installeren. Omdat de namen en vereisten van extras gepubliceerd worden, behandel je ze als productmogelijkheden en geef je ze zo een naam, zoals cli of postgres. Gebruik geen extra met de naam dev om alle contributor-tools in onder te brengen.
Entry points koppelen geïnstalleerde commando’s aan Python
De tabel [project.scripts] in het voorbeeld heeft één regel, weather = "weather_client.cli:main". Nadat de distributie is geïnstalleerd, biedt de omgeving een commando weather dat weather_client.cli importeert en main aanroept. Het commando weather wordt ook zonder de extra cli geïnstalleerd, dus cli.py moet zonder rich werken of de gebruiker vertellen weather-client[cli] te installeren.
Test het commando vanuit een gebouwde wheel, niet alleen vanuit de root van de repository; de wheel is wat gebruikers ontvangen. De migratiesectie toont de commando’s.
[dependency-groups]: lokale, niet-gepubliceerde omgevingen
PEP 735 dependency groups beschrijven ontwikkel- of niet-package-omgevingen zonder ze als packagemetadata 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 plek voor tests, linters, documentatie-builders en vergelijkbare contributor-tools. Het is ook handig voor applicaties of notebooks die geen distributie bouwen. Met include-group hergebruikt een groep een andere groep; extras hebben daar geen equivalent voor.
PEP 735 definieert geen command-line interface, dus elke installer heeft zijn eigen flags. Groups en extras installeer je zo:
# Dependency groups (not published)
pip install --group test # pip 25.1 and later
uv sync --group test
uv sync --all-groups
# Extras (published)
pip install "weather-client[cli]"
uv sync --extra cli
pyproject.toml declareert dependencies; het maakt geen omgeving aan. De commando’s die een omgeving aanmaken en het project met een groep installeren, verschillen per tool:
| Workflow | De omgeving aanmaken | Het project en de groep test installeren |
|---|---|---|
| pip | python -m venv .venv, daarna activeren | pip install -e . --group test |
| uv | uv sync maakt .venv aan als die ontbreekt | uv sync --group test (installeert ook het project en dev) |
[tool.*]: configuratie van één tool
Tooltabellen delen geen schema. In het voorbeeld gebruiken [tool.pytest.ini_options] en [tool.ruff] elk keys die alleen hun eigen tool leest. Gebruik een [tool.*]-tabel alleen als de tool die documenteert. Deze veelgebruikte tools lezen hun instellingen uit pyproject.toml:
| Taak | Tool | Tabel |
|---|---|---|
| Formatteren en linten | Ruff | [tool.ruff] |
| Tests | pytest | [tool.pytest.ini_options] of [tool.pytest] |
| Typecontrole | mypy | [tool.mypy] |
| Coverage | coverage.py | [tool.coverage.run], [tool.coverage.report] |
pytest 9.0 voegde [tool.pytest] toe, dat native TOML-types gebruikt, zoals een lijst voor addopts; oudere versies lezen alleen [tool.pytest.ini_options].
Sommige instellingen horen nog in hun eigen bestanden, omdat een ander programma ze leest of het bestand een ander formaat nodig heeft. flake8 leest pyproject.toml niet; het leest .flake8, setup.cfg of tox.ini. pre-commit leest .pre-commit-config.yaml.
Declaraties, locks en requirements-bestanden lossen verschillende problemen op
De figuur volgt de packages uit het voorbeeldbestand. Elke lijst bereikt een andere omgeving en gebruikers krijgen alleen de twee [project]-lijsten.
Vijf plekken bevatten dependency-informatie. Elke heeft één taak:
| Artefact | Hoofddoel | Typische inhoud |
|---|---|---|
[project.dependencies] | Gepubliceerd runtimecontract | Directe vereisten en compatibele bereiken |
[project.optional-dependencies] | Gepubliceerde opt-in-mogelijkheden | Extras voor consumenten |
[dependency-groups] | Niet-gepubliceerde lokale omgevingen | Test-, lint-, docs- of applicatiegroepen |
| Lockfile | Een opgeloste omgeving reproduceren | Exacte versies, bronnen en resolutiemetadata |
requirements.txt | Invoer voor pip-compatibele installatie | Vereisten plus pip-specifieke opties, constraints, URL’s of hashes |
Een library publiceert normaal gesproken compatibele constraints en test tegen een bereik. Een applicatie commit meestal de lockfile van de omgevingsmanager. Exacte pins horen in het opgeloste deployment-artefact, niet blind in de publieke metadata van een library. Een library declareert bijvoorbeeld httpx>=0.27 en de resolver van de gebruiker kiest de versie. Een applicatie commit uv.lock en installeert die met uv sync --locked.
Bewaar een requirements-bestand wanneer een integratie het formaat of de functies van pip vereist. Heeft een door uv beheerd project er een nodig, exporteer het dan uit de lock in plaats van twee onafhankelijke dependency-sets te onderhouden:
uv export --format requirements.txt --output-file requirements.txt
De export is afgeleide compatibiliteitsuitvoer. De lock blijft de opgeloste bron voor die workflow.
Een migratie die het gedrag behoudt
Begin niet met het verwijderen van setup.py of requirements.txt. Bepaal eerst wat elk bestaand bestand doet. De figuur toont waar elk deel van een oude setup in pyproject.toml naartoe gaat en welke bestanden blijven.
- Maak een inventaris van buildlogica, metadata, runtime-vereisten, extras, ontwikkelomgevingen, toolinstellingen, package data en entry points.
- Kies een backend die de huidige bestandsopname, gecompileerde artefacten en editable installs kan reproduceren.
- Verplaats statische, gepubliceerde metadata naar
[project]; houd echt dynamische velden expliciet. - Verplaats vereisten die alleen voor contributors zijn naar
[dependency-groups], niet naar publieke extras. - Verplaats toolinstellingen alleen waar de tool gelijkwaardige semantiek ondersteunt.
- Bouw zowel een sdist als een wheel, bekijk hun inhoud en installeer de wheel in een schone omgeving.
- Voer entry points, tests, importcontroles en het echte deployment-pad uit.
- Verwijder oude configuratie pas nadat artefacten en gedrag overeenkomen.
Stap 6 en 7 zien er voor de voorbeeldlibrary zo uit:
uv build # or: python -m build
unzip -l dist/*.whl # files in the wheel
tar -tzf dist/*.tar.gz # files in the sdist
uv venv /tmp/check
uv pip install --python /tmp/check dist/*.whl
/tmp/check/bin/weather --help
Vergelijk beide bestandslijsten met de oude build voordat je iets verwijdert. Een ontbrekend databestand of een ontbrekende module valt hier op, niet in de checkout van de repository.
MANIFEST.in kan bij sommige setuptools-indelingen nog nodig zijn, en een kleine setup.py kan geldig blijven voor programmatisch buildgedrag.
Een actuele uv-workflow
uv init maakt voor elke flag een andere indeling aan. Kies een van deze alternatieven:
# Packaged application: src/weather_app/, uv_build, and a weather-app command
uv init weather-app
# Single main.py and no build system
uv init --no-package weather-app
# Library: src layout, uv_build, and a py.typed marker
uv init --lib weather-client
uv 0.12.0 maakte de indeling met package de standaard. Eerdere versies maakten standaard de indeling met main.py aan.
uv init maakt projectbestanden aan. uv lock maakt uv.lock aan of werkt het bij zonder .venv aan te maken. uv sync of uv run werkt de lockfile bij en maakt en synchroniseert .venv waar nodig.
Ga verder met de library die uv init --lib weather-client heeft aangemaakt. Die bevat een gegenereerde functie hello(), dus voeg een kleine test toe voordat je pytest uitvoert:
cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
mkdir -p tests
cat > tests/test_weather_client.py <<'PY'
from weather_client import hello
def test_hello():
assert hello() == "Hello from weather-client!"
PY
uv run --group test pytest
uv build
De test moet 1 passed melden. --group test selecteert de testdependencies ook nadat een standaard uv sync ze heeft verwijderd.
Bekijk de archieven en installeer de wheel in een schone omgeving voordat je publiceert, zoals beschreven in de migratiesectie. Deze gegenereerde library heeft geen commando weather, dus controleer in plaats daarvan import weather_client. Upload de bestanden in dist/ naar PyPI zodra alles klaar is:
uv publish
Buiten uv doet python -m build gevolgd door twine upload dist/* hetzelfde.
uv plaatst lokale ontwikkelvereisten in gestandaardiseerde dependency groups. De eigen backend uv_build ondersteunt alleen pure-Python-projecten; gecompileerde extensies hebben een andere backend uit de tabel hierboven nodig, zoals maturin of scikit-build-core. Zie de projectconfiguratiedocumentatie van uv voor de actuele backendinstellingen.
Meerdere packages in één repository
Een repository met meerdere packages kan ze samenvoegen tot één uv-workspace. De pyproject.toml in de root somt de leden op:
[tool.uv.workspace]
members = ["packages/*"]
De leden delen één uv.lock en één .venv. uv run --package <name> voert een commando uit in de context van één lid.