Автоматический перевод
Эта статья была автоматически переведена с оригинальной английской версии.
pyproject.toml Guide: упаковка Python, зависимости и конфигурация инструментов
Если вы когда-либо открывали Python-проект и пытались понять, где на самом деле лежат зависимости, настройки сборки и конфиги инструментов, вы знаете эту боль. setup.py, setup.cfg, requirements.txt, MANIFEST.in, плюс горсть dotfiles для каждого линтера и форматтера — и все они читают настройки из разных мест.
pyproject.toml сводит большую часть этого в один файл.
Кратко
pyproject.toml — это примерно package.json для Python. Один файл хранит метаданные проекта, зависимости и настройки инструментов. Независимо от того, используете ли вы .venv, pyenv или uv, размещение всего здесь упрощает настройку и совместную работу.
Что такое pyproject.toml?
Это конфигурационный файл в корне вашего Python-проекта, написанный в TOML (представьте INI-файлы, но с нормальной спецификацией). То, как он работает сегодня, сформировали два PEP:
- PEP 518 (2016) ввёл таблицу
[build-system], чтобы инструменты сборки могли стандартным образом объявлять свои требования. - PEP 621 (2020) добавил таблицу
[project]для базовых метаданных пакета: имя, версия, зависимости и тому подобное.
Сегодня большинство Python-инструментов (Black, isort, pytest, Ruff, mypy) читает свою конфигурацию из секций [tool.*] в этом файле.
Почему это важно
Меньше файлов, за которыми нужно следить
Типичный legacy-проект жонглировал setup.py, setup.cfg, requirements.txt, MANIFEST.in и целой стаей dotfiles (.flake8, .coveragerc и подобными). Большую часть этого можно свести в одно место.
Backend-agnostic сборки
Когда вы запускаете pip install ., pip читает pyproject.toml и устанавливает все инструменты сборки, которые нужны вашему проекту: setuptools, flit, hatchling — что угодно. Вы больше не привязаны к setuptools.
Одно место для конфигурации инструментов
Линтеры, форматтеры, test runners и type checkers знают, что нужно смотреть сюда. Ваша IDE, CI-пайплайн и коллеги читают один и тот же файл.
Анатомия pyproject.toml
Обычно файл состоит из трёх основных секций:
# 1. Build system - tells pip/build how to package your project
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
# 2. Project metadata and dependencies
[project]
name = "awesome-app"
version = "0.1.0"
description = "Short demo of pyproject.toml"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.111",
"uvicorn[standard]>=0.30",
]
# Expose CLI commands
[project.scripts]
awesome-cli = "awesome_app.cli:main"
# Optional dependencies (e.g., for development)
[project.optional-dependencies]
dev = ["pytest", "ruff", "mypy"]
# 3. Tool configuration
[tool.ruff]
line-length = 100
target-version = "py312"
[tool.pytest.ini_options]
addopts = "-ra -q"
testpaths = ["tests"]
Разберём по частям
[build-system]
Обязательна, если вы хотите упаковывать или распространять проект. Сообщает pip и инструментам сборки (например, python -m build), какой backend использовать.
[project]
Метаданные вашего пакета. Здесь теперь живут зависимости вместо requirements.txt.
dependencies: runtime-зависимости вашего пакета.optional-dependencies: группы дополнительных зависимостей (dev,test,docs).scripts: создаёт исполняемые команды. В примере выше установка пакета даёт командуawesome-cli, которая запускает функциюmainизawesome_app/cli.py.
[tool.*]
Конфигурация для любого инструмента, который это поддерживает. У каждого инструмента свой namespace: [tool.pytest.ini_options], [tool.mypy], [tool.ruff] и остальные.
Заменяет ли это requirements.txt?
Для современных workflow — да. Инструменты вроде Poetry, PDM, Hatch и uv хранят зависимости напрямую в секции [project] и генерируют lockfiles для воспроизводимости.
requirements.txt вам, вероятно, всё ещё нужен, если:
- Вы работаете с legacy-системой деплоя, которая его ожидает.
- У вас есть CI-скрипт, который ещё не обновили.
Большинство современных инструментов умеет при необходимости экспортировать его из вашего pyproject.toml:
uv export > requirements.txt
Выбор build backend
Build backend — одно из самых запутанных решений. Вот краткое сравнение:
| Backend | Лучше всего подходит для | Плюсы | Минусы |
|---|---|---|---|
| Hatchling | Современный стандарт | Быстрый, расширяемый, поддерживает плагины | Более новый, меньше поддержки legacy |
| Flit | Простых пакетов | Предельно простой, почти без конфигурации | Не подходит для сложных сборок (C extensions) |
| Setuptools | Legacy, сложных случаев | Поддерживает всё (C extensions и т. д.) | Медленнее, больше конфигурации |
| Poetry | Пользователей Poetry | Интегрирован с экосистемой Poetry | Привязывает к workflow Poetry |
Для новых pure-Python проектов Hatchling — разумный вариант по умолчанию. Именно его uv init записывает за вас.
Миграция существующего проекта
Если у вас legacy Python-проект, вот как его привести к современному виду:
- Добавьте
[build-system]. Если не уверены, какой backend использовать, начните сrequires = ["setuptools>=61", "wheel"]. - Перенесите метаданные в
[project]. Перенесите имя, версию и зависимости изsetup.pyилиsetup.cfg. - Перенесите dev-зависимости. Поместите их в
[project.optional-dependencies].dev. - Настройте инструменты. Добавьте секции
[tool.*]для Black, pytest, mypy и т. д. - Решите, что делать с
requirements.txt. Либо уберите его, либо генерируйте из lockfile для legacy-систем, которым он нужен.
После миграции обычно можно удалить setup.py, setup.cfg и большую часть этих dotfiles с конфигами.
Несколько полезных возможностей
CLI entry points
Вместо старого блока console_scripts в setup.py используйте [project.scripts]:
[project.scripts]
my-tool = "my_package.main:run"
Когда кто-то установит ваш пакет, он сможет набрать my-tool в терминале.
Workspaces (monorepos)
Инструменты вроде uv и hatch поддерживают workspaces, что позволяет управлять несколькими пакетами в одном репозитории:
[tool.uv.workspace]
members = ["packages/*"]
Вы можете разрабатывать несколько взаимозависимых пакетов и устанавливать их все в одно виртуальное окружение для тестирования.
Типичные workflow с uv
Старт нового проекта
uv init my_app # creates folder with pyproject.toml and .venv
cd my_app
uv add requests fastapi # adds to [project.dependencies] and installs
uv run pytest # runs tests in the venv
Запуск скриптов
Вы можете либо определить скрипты в pyproject.toml (используя task runner вроде poe или hatch), либо просто вызвать uv run:
uv run python main.py
Практические советы
- Не фиксируйте точные версии в библиотеках. Используйте диапазоны вроде
requests>=2.30, чтобы ваша библиотека не конфликтовала с остальным, что установлено в чьём-то окружении. - Фиксируйте версии в приложениях. Используйте lockfile (
uv.lock,poetry.lock) для воспроизводимых сборок. - Группируйте dev-зависимости. Держите зависимости для тестов, линтинга и документации в отдельных optional groups (
dev,test,docs). - Не сваливайте все опции в
pyproject.toml. Оставляйте там project-wide настройки по умолчанию, а всё остальное отдавайте tool-specific конфигам, если становится слишком громоздко.
Главный выигрыш от pyproject.toml — в том, что он убирает мелкое ежедневное трение. Больше не нужно искать, какой файл отвечает за сборку. Конфиг линтера лежит рядом с зависимостями. Pip и ваша IDE наконец-то согласованы в том, что установлено. Выберите build backend, положите зависимости в [project] и позвольте инструментам читать всё из одного места. Если начинаете с нуля, uv init за одну команду сделает большую часть работы.