Руководство по pyproject.toml: упаковка, зависимости и инструменты

Автоматический перевод Эта статья была автоматически переведена с оригинальной английской версии.

В старых Python-проектах конфигурация часто была распределена между setup.py, setup.cfg, файлами requirements, MANIFEST.in и отдельными файлами для инструментов разработки. pyproject.toml объединяет эти задачи в одном месте, хотя не заменяет каждый файл проекта и не превращает все секции в части единого стандарта.

В этом руководстве мы разберём файл — от конфигурации сборки до метаданных проекта и настроек инструментов. Четыре владельца помогают разделять секции: что собирает проект, что проект публикует, что локально нужно контрибьюторам и какие параметры настраивают отдельные инструменты.

Кратко. Используйте [build-system] для бэкенда, который собирает дистрибутив, [project] для публикуемых метаданных и runtime-требований, [dependency-groups] для непубликуемых окружений разработки, а [tool.*] — только если это предусмотрено документацией соответствующего инструмента. Декларация зависимостей — не lockfile.

Один файл, четыре владельца

Четыре границы ответственности в pyproject.tomlЧетыре границы ответственности в pyproject.toml

Структуру ядра определяют три стандарта упаковки:

  • PEP 518 определяет требования системы сборки.
  • PEP 621 определяет метаданные проекта.
  • PEP 735 определяет непубликуемые группы зависимостей.

Авторы инструментов также могут объявлять namespace внутри [tool]. Содержимое этого namespace не стандартизировано: каждый инструмент сам определяет свои ключи и поведение.

Вот небольшой упакованный пакет-библиотека:

[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"

Каждый список зависимостей отвечает на свой вопрос.

[build-system]: как исходный код превращается в дистрибутив

Build frontend, например python -m build, pip или uv, вызывает build backend. Бэкенд определяет, как дерево исходников превращается в sdist или wheel и какие файлы попадут в эти артефакты.

[build-system]
requires = ["hatchling>=1.26"]
build-backend = "hatchling.build"

requires содержит зависимости, необходимые для запуска бэкенда в изолированном build-окружении. Это не список runtime-зависимостей вашего пакета.

Объявляйте систему сборки, если проект выпускает дистрибутив или требует установки собственного кода как пакета. Непакетный проект тоже может использовать pyproject.toml; PEP 735 даже допускает файл, содержащий только группы зависимостей. Менеджеры окружений по-разному работают с проектами без системы сборки, поэтому принимайте это решение осознанно.

Выбирайте бэкенд с учётом требований сборки:

  • layout на чистом Python и требований к выбору файлов
  • компилируемые расширения или внешние системы сборки
  • динамическая версия или требования к генерируемым файлам
  • поведение при editable-установке
  • зрелость бэкенда в release pipeline

Скопируйте актуальную рекомендуемую таблицу бэкенда из его документации. Не добавляйте wheel в требования сборки по привычке: бэкенд сам объявляет, что ему нужно.

[project]: какие метаданные получают потребители

Таблица [project] описывает дистрибутив: его имя, версию, совместимость с Python, runtime-зависимости, entry points и другие метаданные для индексов пакетов.

[project]
name = "weather-client"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = ["httpx>=0.27"]

Спецификаторы зависимостей — это ограничения для резолвера, а не снапшот конкретного окружения. Они становятся частью метаданных wheel и sdist, поэтому downstream-инсталляторы могут объединять их с требованиями других пакетов.

Extras — это публичный интерфейс установки

[project.optional-dependencies] определяет extras, которые могут запросить потребители:

[project.optional-dependencies]
cli = ["rich>=13"]
postgres = ["psycopg[binary]>=3.2"]

Потребитель может установить weather-client[cli]. Поскольку имена extras и требования публикуются, относитесь к ним как к возможностям продукта. Не используйте extra с именем dev просто для хранения всех инструментов контрибьютора.

Entry points связывают установленные команды с Python

[project.scripts]
weather = "weather_client.cli:main"

После установки дистрибутива окружение предоставляет weather, который импортирует и вызывает weather_client.cli:main. Тестируйте команду из собранного wheel, а не только из корня репозитория: именно wheel получают пользователи.

[dependency-groups]: локальные непубликуемые окружения

Группы зависимостей PEP 735 описывают окружения разработки или другие непакетные окружения, не публикуя их как метаданные пакета.

[dependency-groups]
test = ["pytest>=8", "pytest-cov>=5"]
lint = ["ruff>=0.12"]
dev = [
  { include-group = "test" },
  { include-group = "lint" },
]

Это подходящая граница для тестов, линтеров, сборщиков документации и других инструментов контрибьютора. Группы также полезны для приложений или ноутбуков, которые не собирают дистрибутив.

Группы зависимостей — это стандартизированные данные, но интерфейсы инсталляторов всё ещё различаются. Проверьте, как выбранный менеджер окружений устанавливает, лочит и резолвит их. PEP 735 не определяет универсальный CLI-интерфейс.

[tool.*]: конфигурация, принадлежащая одному инструменту

Таблицы инструментов не используют общую схему:

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

[tool.ruff]
line-length = 100

Используйте таблицу [tool.*] только в том случае, если инструмент документирует её поддержку. Некоторые настройки по-прежнему должны находиться в отдельных файлах: например, если их потребляет другая экосистема, файлу нужен другой формат или отдельная конфигурация понятнее. pyproject.toml — это точка координации. Она не требует централизовать все настройки.

Потребители конфигурации вокруг pyproject.tomlПотребители конфигурации вокруг pyproject.toml

Декларации, lockfile и файлы requirements решают разные задачи

Частая причина путаницы — воспринимать каждый артефакт зависимостей как конкурирующий источник истины.

АртефактОсновное назначениеТипичное содержимое
[project.dependencies]Публичный runtime-контрактПрямые требования и совместимые диапазоны
[project.optional-dependencies]Публикуемые возможности по запросуExtras для потребителей
[dependency-groups]Непубликуемые локальные окруженияГруппы для тестов, линтинга, документации или приложения
LockfileВоспроизведение разрешённого окруженияТочные версии, источники и метаданные разрешения
requirements.txtВходные данные для установки через pipТребования и специфичные для pip параметры, constraints, URL или хэши

Библиотека обычно публикует совместимые ограничения и тестируется на диапазоне версий. Приложение обычно коммитит lockfile менеджера окружений. Точные pin-версии должны находиться в разрешённом артефакте деплоя, а не бездумно в публичных метаданных библиотеки.

Сохраняйте файл requirements, если интеграции нужен формат или функциональность pip. Если проект под управлением uv требует такой файл, экспортируйте его из lockfile, а не поддерживайте два независимых набора зависимостей:

uv export --format requirements.txt --output-file requirements.txt

Экспорт — это производный compatibility-артефакт. Lockfile остаётся разрешённым источником для этого workflow.

Миграция с сохранением поведения

Миграция на pyproject.toml с приоритетом верификацииМиграция на pyproject.toml с приоритетом верификации

Не начинайте с удаления setup.py или requirements.txt. Сначала классифицируйте назначение каждого существующего файла.

  1. Проведите инвентаризацию логики сборки, метаданных, runtime-требований, extras, окружений разработчика, настроек инструментов, данных пакета и entry points.
  2. Выберите бэкенд, способный воспроизвести текущий выбор файлов, компилируемые артефакты и editable-установки.
  3. Перенесите статические публикуемые метаданные в [project]; действительно динамические поля оставьте явно обозначенными.
  4. Перенесите требования только для контрибьюторов в [dependency-groups], а не в публичные extras.
  5. Переносите настройки инструментов только в тех случаях, когда инструмент поддерживает эквивалентную семантику.
  6. Соберите и sdist, и wheel, проверьте их содержимое и установите wheel в чистое окружение.
  7. Запустите entry points, тесты, проверки импорта и фактический путь деплоя.
  8. Удаляйте старую конфигурацию только после того, как артефакты и поведение совпадут.

MANIFEST.in всё ещё может понадобиться для некоторых layout setuptools, а небольшой setup.py может оставаться допустимым для программного управления сборкой. Модернизация переносит ответственность между файлами. Удаление файлов — не цель.

Актуальный workflow с uv

При создании проекта uv различает приложения и библиотеки:

# Non-library application template
uv init weather-app

# Packaged library with a src layout and build system
uv init --lib weather-client

uv init создаёт файлы проекта. Первая операция с проектом, например uv run, uv sync или uv lock, при необходимости создаёт lockfile и постоянный .venv.

cd weather-client
uv add httpx
uv add --group test pytest pytest-cov
uv run pytest
uv build

Сейчас uv размещает локальные требования разработки в стандартизированных группах зависимостей. Встроенный бэкенд uv_build — один из вариантов для проектов на чистом Python; для компилируемых расширений нужен подходящий альтернативный бэкенд, например maturin или scikit-build-core. Актуальные настройки бэкенда см. в документации uv по конфигурации проекта.

Заключение

pyproject.toml остаётся понятным, когда у каждой таблицы есть одна аудитория. Изоляция сборки относится к [build-system], а публикуемое поведение — к [project]. Окружения контрибьюторов относятся к [dependency-groups]. Переключатели инструментов принадлежат инструменту, который их определяет.

Когда эти границы зафиксированы, файл становится проще проверять, а миграции перестают смешивать метаданные пакета с разрешённым окружением одной машины.

Ссылки