Перейти к содержанию

Автоматический перевод

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

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-проект, вот как его привести к современному виду:

Поток миграции

  1. Добавьте [build-system]. Если не уверены, какой backend использовать, начните с requires = ["setuptools>=61", "wheel"].
  2. Перенесите метаданные в [project]. Перенесите имя, версию и зависимости из setup.py или setup.cfg.
  3. Перенесите dev-зависимости. Поместите их в [project.optional-dependencies].dev.
  4. Настройте инструменты. Добавьте секции [tool.*] для Black, pytest, mypy и т. д.
  5. Решите, что делать с 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

Практические советы

  1. Не фиксируйте точные версии в библиотеках. Используйте диапазоны вроде requests>=2.30, чтобы ваша библиотека не конфликтовала с остальным, что установлено в чьём-то окружении.
  2. Фиксируйте версии в приложениях. Используйте lockfile (uv.lock, poetry.lock) для воспроизводимых сборок.
  3. Группируйте dev-зависимости. Держите зависимости для тестов, линтинга и документации в отдельных optional groups (dev, test, docs).
  4. Не сваливайте все опции в pyproject.toml. Оставляйте там project-wide настройки по умолчанию, а всё остальное отдавайте tool-specific конфигам, если становится слишком громоздко.

Главный выигрыш от pyproject.toml — в том, что он убирает мелкое ежедневное трение. Больше не нужно искать, какой файл отвечает за сборку. Конфиг линтера лежит рядом с зависимостями. Pip и ваша IDE наконец-то согласованы в том, что установлено. Выберите build backend, положите зависимости в [project] и позвольте инструментам читать всё из одного места. Если начинаете с нуля, uv init за одну команду сделает большую часть работы.