pyproject.toml-Leitfaden: Packaging, Dependencies und Tools
Automatische Übersetzung Dieser Artikel wurde automatisch aus der englischen Originalversion übersetzt.
Ältere Python-Projekte verteilen ihre Konfiguration häufig auf setup.py, setup.cfg, Requirements-Dateien, MANIFEST.in und separate Dateien für Development-Tools. pyproject.toml bietet diesen Belangen einen gemeinsamen Ort, ersetzt jedoch nicht jede Projektdatei und macht auch nicht jeden Abschnitt zu einem Teil eines einzigen Standards.
Dieser Leitfaden führt von der Build-Konfiguration über Projektmetadaten bis zu Tool-Einstellungen durch die Datei. Vier Verantwortliche halten die Abschnitte getrennt: Was das Projekt baut, was das Projekt veröffentlicht, was Contributors lokal benötigen und welche Konfiguration die einzelnen Tools verwalten.
TL;DR. Verwende
[build-system]für das Backend, das eine Distribution baut,[project]für veröffentlichte Metadaten und Runtime-Anforderungen,[dependency-groups]für unveröffentlichte Development-Environments und[tool.*]nur dort, wo die Dokumentation des jeweiligen Tools dies vorsieht. Eine Dependency-Deklaration ist kein Lockfile.
Eine Datei, vier Verantwortliche
Drei Packaging-Standards bilden die grundlegende Struktur:
- PEP 518 definiert Build-System-Anforderungen.
- PEP 621 definiert Projektmetadaten.
- PEP 735 definiert unveröffentlichte Dependency Groups.
Tool-Autoren können außerdem einen Namespace unter [tool] beanspruchen. Was dort hineingehört, ist nicht standardisiert: Jedes Tool definiert seine eigenen Keys und sein eigenes Verhalten.
Hier ist eine kleine paketierte 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"
Jede Dependency-Liste beantwortet eine andere Frage.
[build-system]: wie aus Quellcode eine Distribution wird
Ein Build-Frontend wie python -m build, pip oder uv ruft ein Build-Backend auf. Das Backend entscheidet, wie aus dem Source Tree ein sdist oder Wheel wird und welche Dateien in diese Artefakte aufgenommen werden.
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
requires enthält Dependencies, die zum Ausführen des Backends in seiner isolierten Build-Environment benötigt werden. Es ist nicht die Runtime-Dependency-Liste deines Packages.
Deklariere ein Build-System, wenn das Projekt eine Distribution erzeugt oder eigenen Code als Package installieren muss. Auch ein Non-Package-Projekt kann pyproject.toml verwenden; PEP 735 erlaubt sogar eine Datei, die ausschließlich Dependency Groups enthält. Environment Manager unterscheiden sich darin, wie sie ein Projekt ohne Build-System behandeln. Triff diese Entscheidung daher bewusst.
Wähle das Backend anhand der Build-Anforderungen:
- Layout und Dateiauswahl für reines Python
- kompilierte Extensions oder externe Build-Systeme
- Anforderungen an dynamische Versionen oder generierte Dateien
- Verhalten bei Editable Installs
- Reifegrad des Backends in der Release-Pipeline
Übernimm die aktuell empfohlene Tabelle des Backends aus dessen Dokumentation. Füge wheel nicht aus Gewohnheit zu den Build-Anforderungen hinzu; das Backend definiert, was es benötigt.
[project]: Metadaten für Consumer
Die Tabelle [project] beschreibt die Distribution: ihren Namen, ihre Version, Python-Kompatibilität, Runtime-Dependencies, Entry Points und weitere Index-Metadaten.
[project]
name = "weather-client"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27"]
Die Dependency-Specifier sind Constraints für einen Resolver, kein Snapshot einer einzelnen Environment. Sie werden Teil der Wheel- und sdist-Metadaten, sodass nachgelagerte Installer sie mit Requirements anderer Packages kombinieren können.
Extras sind eine öffentliche Installationsschnittstelle
[project.optional-dependencies] definiert Extras, die Consumer anfordern können:
[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]
Ein Consumer kann weather-client[cli] installieren. Da Extra-Namen und Requirements veröffentlicht werden, solltest du sie als Produktfähigkeiten behandeln. Verwende kein Extra namens dev lediglich als Container für alle Contributor-Tools.
Entry Points verbinden installierte Commands mit Python
[project.scripts]
weather = "weather_client.cli:main"
Nach der Installation der Distribution stellt die Environment weather bereit. Dieser Command importiert weather_client.cli:main und ruft es auf. Teste den Command mit einem gebauten Wheel und nicht nur aus dem Repository-Root; das Wheel ist das Artefakt, das User erhalten.
[dependency-groups]: lokale, unveröffentlichte Environments
PEP-735-Dependency-Groups beschreiben Development- oder Non-Package-Environments, ohne sie als Package-Metadaten zu veröffentlichen.
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
]
Das ist die richtige Grenze für Tests, Linter, Dokumentations-Builder und ähnliche Contributor-Tools. Dependency Groups sind auch für Applications oder Notebooks nützlich, die keine Distribution bauen.
Dependency Groups sind standardisierte Daten, aber die Installer-Schnittstellen unterscheiden sich weiterhin. Prüfe, wie der ausgewählte Environment Manager sie installiert, lockt und auflöst. PEP 735 definiert keine universelle Command-Line-Schnittstelle.
[tool.*]: Konfiguration im Besitz eines einzelnen Tools
Tool-Tabellen verwenden kein gemeinsames Schema:
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
Verwende eine [tool.*]-Tabelle nur, wenn das Tool sie dokumentiert. Einige Einstellungen gehören weiterhin in eigene Dateien, weil ein anderes Ecosystem sie konsumiert, die Datei ein anderes Format benötigt oder die Konfiguration eigenständig übersichtlicher ist. pyproject.toml ist ein Koordinationspunkt. Daraus folgt nicht, dass jede Einstellung zentralisiert werden muss.
Deklarationen, Locks und Requirements-Dateien lösen unterschiedliche Probleme
Eine häufige Ursache für Verwirrung ist, jedes Dependency-Artefakt als konkurrierende Source of Truth zu betrachten.
| Artefakt | Hauptzweck | Typische Inhalte |
|---|---|---|
[project.dependencies] | Öffentlicher Runtime-Vertrag | Direkte Requirements und kompatible Bereiche |
[project.optional-dependencies] | Veröffentliche Opt-in-Fähigkeiten | Consumer-facing Extras |
[dependency-groups] | Unveröffentlichte lokale Environments | Test-, Lint-, Docs- oder Application-Groups |
| Lockfile | Reproduzierbare aufgelöste Environment | Exakte Versionen, Quellen und Resolution-Metadaten |
requirements.txt | pip-kompatibler Installations-Input | Requirements plus pip-spezifische Optionen, Constraints, URLs oder Hashes |
Eine Library veröffentlicht normalerweise kompatible Constraints und testet gegen einen Versionsbereich. Eine Application committed üblicherweise das Lockfile des Environment Managers. Exakte Pins gehören in das aufgelöste Deployment-Artefakt und nicht blind in die öffentlichen Metadaten einer Library.
Behalte eine Requirements-Datei, wenn eine Integration das pip-Format oder dessen Features benötigt. Wenn ein uv-managed Project eine solche Datei benötigt, exportiere sie aus dem Lockfile, statt zwei unabhängige Dependency-Sets zu pflegen:
uv export --format requirements.txt --output-file requirements.txt
Der Export ist abgeleiteter Kompatibilitäts-Output. Das Lockfile bleibt für diesen Workflow die aufgelöste Source of Truth.
Eine Migration, die das Verhalten bewahrt
Beginne nicht damit, setup.py oder requirements.txt zu löschen. Klassifiziere zuerst, welche Aufgabe jede bestehende Datei erfüllt.
- Erfasse Build-Logik, Metadaten, Runtime-Requirements, Extras, Developer-Environments, Tool-Einstellungen, Package-Daten und Entry Points.
- Wähle ein Backend, das die aktuelle Dateiauswahl, kompilierte Artefakte und Editable Installs reproduzieren kann.
- Verschiebe statische veröffentlichte Metadaten nach
[project]; halte tatsächlich dynamische Felder explizit. - Verschiebe ausschließlich für Contributors bestimmte Requirements nach
[dependency-groups]und nicht in öffentliche Extras. - Verschiebe Tool-Einstellungen nur dann, wenn das Tool gleichwertige Semantik unterstützt.
- Baue sowohl ein sdist als auch ein Wheel, prüfe deren Inhalte und installiere das Wheel in einer sauberen Environment.
- Führe Entry Points, Tests, Import-Checks und den tatsächlichen Deployment-Pfad aus.
- Lösche alte Konfiguration erst, nachdem Artefakte und Verhalten übereinstimmen.
MANIFEST.in kann bei einigen setuptools-Layouts weiterhin erforderlich sein, und ein kleines setup.py kann für programmatisches Build-Verhalten weiterhin sinnvoll sein. Modernisierung verschiebt Verantwortlichkeiten zwischen Dateien. Das Löschen von Dateien ist nicht das Ziel.
Ein aktueller uv-Workflow
uv unterscheidet beim Erstellen eines Projects zwischen Applications und 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 erstellt Projektdateien. Die erste Project-Operation, etwa uv run, uv sync oder uv lock, erstellt bei Bedarf das Lockfile und .venv dauerhaft.
cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build
uv legt lokale Development-Requirements derzeit in standardisierten Dependency Groups ab. Sein natives uv_build-Backend ist eine Option für reine Python-Projekte; kompilierte Extensions benötigen eine geeignete Alternative wie maturin oder scikit-build-core. Die aktuellen Backend-Einstellungen findest du in der Project-Configuration-Dokumentation von uv.
Fazit
pyproject.toml ist übersichtlich, wenn jede Tabelle genau eine Zielgruppe hat. Build-Isolation gehört zu [build-system] und veröffentlichtes Verhalten zu [project]. Contributor-Environments gehören zu [dependency-groups]. Tool-Schalter gehören zu dem Tool, das sie definiert.
Sobald diese Grenzen stabil sind, lässt sich die Datei leichter reviewen, und Migrationen verwechseln Package-Metadaten nicht mehr mit der aufgelösten Environment einer einzelnen Maschine.