Przewodnik po pyproject.toml: pakowanie, zależności i narzędzia
Tłumaczenie automatyczne Ten artykuł został automatycznie przetłumaczony z angielskiego oryginału.
Starsze projekty Python często rozpraszały konfigurację między setup.py, setup.cfg, plikami requirements, MANIFEST.in oraz osobnymi plikami narzędzi deweloperskich. pyproject.toml zapewnia tym zagadnieniom wspólne miejsce, ale nie zastępuje każdego pliku projektu ani nie sprawia, że każda sekcja staje się częścią jednego standardu.
Ten przewodnik omawia plik od konfiguracji procesu budowania po metadane projektu i ustawienia narzędzi. Sekcje mają czterech różnych właścicieli: to, co buduje projekt; to, co projekt publikuje; to, czego lokalnie potrzebują współtwórcy; oraz to, co konfigurują poszczególne narzędzia.
TL;DR. Używaj
[build-system]dla backendu budującego dystrybucję,[project]dla publikowanych metadanych i wymagań uruchomieniowych,[dependency-groups]dla niepublikowanych środowisk deweloperskich, a[tool.*]wyłącznie tam, gdzie zaleca to dokumentacja danego narzędzia. Deklaracja zależności nie jest lockfile’em.
Jeden plik, czterech właścicieli
Trzy standardy dotyczące pakowania zdefiniowały podstawową strukturę:
- PEP 518 definiuje wymagania systemu budowania.
- PEP 621 definiuje metadane projektu.
- PEP 735 definiuje niepublikowane grupy zależności.
Autorzy narzędzi mogą również zająć przestrzeń nazw poniżej [tool]. Nie ma standardu określającego, co powinno się tam znaleźć: każde narzędzie definiuje własne klucze i zachowanie.
Oto niewielka biblioteka pakowana jako dystrybucja:
[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"
Każda lista zależności odpowiada na inne pytanie.
[build-system]: jak kod źródłowy staje się dystrybucją
Frontend budowania, taki jak python -m build, pip lub uv, wywołuje backend budowania. Backend decyduje, jak drzewo źródłowe zostanie przekształcone w sdist lub wheel oraz które pliki trafią do tych artefaktów.
[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"
requires zawiera zależności potrzebne do uruchomienia backendu w izolowanym środowisku budowania. Nie jest to lista zależności uruchomieniowych pakietu.
Zadeklaruj system budowania, gdy projekt tworzy dystrybucję lub wymaga zainstalowania własnego kodu jako pakietu. Projekt niebędący pakietem również może używać pyproject.toml; PEP 735 dopuszcza nawet plik zawierający wyłącznie grupy zależności. Menedżery środowisk różnią się sposobem obsługi projektu bez systemu budowania, dlatego tę decyzję należy podjąć świadomie.
Wybierz backend na podstawie wymagań dotyczących budowania:
- układu pure-Python i wyboru plików
- rozszerzeń kompilowanych lub zewnętrznych systemów budowania
- dynamicznej wersji lub generowanych plików
- zachowania przy instalacji editable
- dojrzałości backendu w pipeline’ie wydawniczym
Skopiuj aktualnie zalecaną tabelę backendu z jego dokumentacji. Nie dodawaj wheel do wymagań budowania z przyzwyczajenia; backend deklaruje, czego potrzebuje.
[project]: metadane otrzymywane przez konsumentów
Tabela [project] opisuje dystrybucję: jej nazwę, wersję, zgodność z Pythonem, zależności uruchomieniowe, punkty wejścia i inne metadane indeksu.
[project]
name = "weather-client"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27"]
Specyfikatory zależności są ograniczeniami dla resolvera, a nie migawką jednego środowiska. Stają się częścią metadanych wheel i sdist, dzięki czemu instalatory po stronie użytkownika mogą łączyć je z wymaganiami innych pakietów.
Extras to publiczny interfejs instalacji
[project.optional-dependencies] definiuje extras, o które mogą poprosić konsumenty:
[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]
Konsument może zainstalować weather-client[cli]. Ponieważ nazwy extras i wymagania są publikowane, traktuj je jako możliwości produktu. Nie używaj extra o nazwie dev wyłącznie do przechowywania wszystkich narzędzi współtwórców.
Punkty wejścia łączą zainstalowane polecenia z Pythonem
[project.scripts]
weather = "weather_client.cli:main"
Po zainstalowaniu dystrybucji środowisko udostępnia weather, które importuje i wywołuje weather_client.cli:main. Testuj polecenie z zbudowanego wheel, a nie tylko z katalogu głównego repozytorium; wheel jest tym, co otrzymują użytkownicy.
[dependency-groups]: lokalne, niepublikowane środowiska
Grupy zależności PEP 735 opisują środowiska deweloperskie lub niepakietowe bez publikowania ich jako metadanych pakietu.
[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
{ include-group = "test" },
{ include-group = "lint" },
]
To właściwa granica dla testów, linterów, generatorów dokumentacji i podobnych narzędzi współtwórców. Grupy są również przydatne w przypadku aplikacji lub notebooków, które nie budują dystrybucji.
Grupy zależności są ustandaryzowanymi danymi, ale interfejsy instalatorów nadal się różnią. Sprawdź, jak wybrany menedżer środowiska instaluje, blokuje i rozwiązuje te grupy. PEP 735 nie definiuje uniwersalnego interfejsu wiersza poleceń.
[tool.*]: konfiguracja należąca do jednego narzędzia
Tabele narzędzi nie współdzielą schematu:
[tool.pytest.ini_options]
addopts = "-ra"
testpaths = ["tests"]
[tool.ruff]
line-length = 100
Używaj tabeli [tool.*] tylko wtedy, gdy narzędzie opisuje ją w swojej dokumentacji. Część ustawień nadal powinna znajdować się we własnych plikach, ponieważ korzysta z nich inny ekosystem, plik wymaga innego formatu albo konfiguracja jest wtedy czytelniejsza. pyproject.toml jest punktem koordynacji. Nie wymaga centralizowania wszystkich ustawień.
Deklaracje, lockfile’e i pliki requirements rozwiązują różne problemy
Częstym źródłem nieporozumień jest traktowanie każdego artefaktu zależności jako konkurencyjnego źródła prawdy.
| Artefakt | Główny cel | Typowa zawartość |
|---|---|---|
[project.dependencies] | Publiczny kontrakt zależności uruchomieniowych | Bezpośrednie wymagania i kompatybilne zakresy |
[project.optional-dependencies] | Publikowane możliwości opcjonalne | Extras przeznaczone dla konsumentów |
[dependency-groups] | Niepublikowane środowiska lokalne | Grupy testowe, lintujące, dokumentacyjne lub aplikacyjne |
| Lockfile | Odtworzenie rozwiązanego środowiska | Dokładne wersje, źródła i metadane rozwiązywania |
requirements.txt | Dane wejściowe instalacji zgodne z pip | Wymagania oraz opcje specyficzne dla pip, ograniczenia, URL-e lub hashe |
Biblioteka zazwyczaj publikuje kompatybilne ograniczenia i testuje się w określonym zakresie wersji. Aplikacja zwykle zatwierdza lockfile menedżera środowiska. Dokładne przypięcia wersji powinny znajdować się w artefakcie wdrożeniowym zawierającym rozwiązane zależności, a nie bezrefleksyjnie w publicznych metadanych biblioteki.
Zachowaj plik requirements, gdy integracja wymaga formatu lub funkcji pip. Jeśli projekt zarządzany przez uv go potrzebuje, wyeksportuj go z lockfile’a zamiast utrzymywać dwa niezależne zestawy zależności:
uv export --format requirements.txt --output-file requirements.txt
Eksport jest pochodnym wynikiem kompatybilności. W tym procesie lockfile pozostaje źródłem rozwiązanego stanu.
Migracja z zachowaniem działania
Nie zaczynaj od usuwania setup.py ani requirements.txt. Najpierw sklasyfikuj funkcję każdego istniejącego pliku.
- Zidentyfikuj logikę budowania, metadane, wymagania uruchomieniowe, extras, środowiska deweloperskie, ustawienia narzędzi, dane pakietu i punkty wejścia.
- Wybierz backend, który potrafi odtworzyć bieżący wybór plików, artefakty kompilowane i instalacje editable.
- Przenieś statyczne publikowane metadane do
[project]; rzeczywiście dynamiczne pola pozostaw jawnie określone. - Przenieś wymagania przeznaczone wyłącznie dla współtwórców do
[dependency-groups], a nie do publicznych extras. - Przenoś ustawienia narzędzi tylko wtedy, gdy narzędzie obsługuje równoważną semantykę.
- Zbuduj zarówno sdist, jak i wheel, sprawdź ich zawartość oraz zainstaluj wheel w czystym środowisku.
- Uruchom punkty wejścia, testy, kontrole importów i rzeczywistą ścieżkę wdrożenia.
- Usuń starą konfigurację dopiero wtedy, gdy artefakty i działanie będą zgodne.
MANIFEST.in może być nadal potrzebne w niektórych układach setuptools, a niewielki setup.py może pozostać poprawny w przypadku programowego sterowania budowaniem. Modernizacja przenosi odpowiedzialność między plikami. Usuwanie plików nie jest celem.
Aktualny workflow z uv
uv rozróżnia aplikacje i biblioteki podczas tworzenia projektu:
# Non-library application template
uv init weather-app
# Packaged library with a src layout and build system
uv init --lib weather-client
uv init tworzy pliki projektu. Pierwsza operacja na projekcie, taka jak uv run, uv sync lub uv lock, w razie potrzeby tworzy lockfile i trwały .venv.
cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build
uv obecnie umieszcza lokalne wymagania deweloperskie w ustandaryzowanych grupach zależności. Jego natywny backend uv_build jest jedną z opcji dla projektów pure-Python; rozszerzenia kompilowane wymagają odpowiedniej alternatywy, takiej jak maturin lub scikit-build-core. Aktualne ustawienia backendu opisano w dokumentacji konfiguracji projektów uv.
Podsumowanie
pyproject.toml jest przejrzysty, gdy każda tabela ma jednego odbiorcę. Izolacja budowania należy do [build-system], a publikowane zachowanie do [project]. Środowiska współtwórców należą do [dependency-groups]. Przełączniki narzędzi należą do narzędzia, które je definiuje.
Gdy te granice są stabilne, plik łatwiej przeglądać, a migracje przestają mylić metadane pakietu z rozwiązanym środowiskiem jednej maszyny.