Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Сборка Python-пакета: структура и pyproject.toml

Сборка Python-пакета: структура проекта и pyproject.toml

Сборка Python-пакета: структура src-layout, конфигурация pyproject.toml, метаданные, точка входа, сборка wheel и типичные ошибки импорта.

Прямой ответ

Современный Python-пакет описывается одним файлом pyproject.toml, содержит код в каталоге src/ и собирается в wheel. Три решения определяют, будет ли пакет устанавливаться корректно у других.

1. Структура src-layout. Код лежит в src/, а не в корне репозитория:

my-package/
├── pyproject.toml
├── src/
│   └── my_package/
│       ├── __init__.py
│       └── core.py
├── tests/
└── README.md

Различие с flat-layout принципиально: при расположении пакета в корне репозитория import my_package работает без установки, поскольку текущий каталог входит в путь поиска. Ошибки импорта проявляются только у пользователя после установки, а не при разработке; о компоновке — в статье про структуру проекта.

2. Конфигурация в pyproject.toml. Метаданные, зависимости и настройки инструментов описываются в одном файле:

[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"

[project]
name = "my-package"
version = "1.2.0"
description = "Обработка отчётов"
readme = "README.md"
requires-python = ">=3.10"
license = {text = "MIT"}
dependencies = [
    "httpx>=0.27",
    "pydantic>=2.6",
]

[project.optional-dependencies]
dev = ["pytest>=8", "ruff>=0.4"]

[project.urls]
Homepage = ""

Секция build-system указывает, какой инструмент собирает пакет. Без неё используется устаревший путь через setup.py.

3. Точка входа для команд. Если пакет предоставляет консольную команду, она объявляется явно:

[project.scripts]
my-tool = "my_package.cli:main"

После установки команда my-tool доступна в окружении без указания пути к модулю.

Сборка выполняется одной командой:

python -m build

Результат — два файла в каталоге dist/: архив исходников и wheel; о различии форматов — в статье про wheel и sdist.

Что попадает в дистрибутив

Состав собранного пакета определяется конфигурацией, а не содержимым каталога проекта. Три категории файлов требуют явного решения.

Модули и подпакеты. Обнаруживаются автоматически, если расположены в ожидаемом месте. Вложенные подпакеты без __init__.py могут быть пропущены, что проявляется только после установки.

Данные пакета. Файлы шаблонов, справочники и схемы не включаются автоматически. Они объявляются в конфигурации и размещаются внутри каталога пакета, иначе путь к ним после установки окажется неверным.

Файлы проекта. README и текст лицензии включаются, если указаны в метаданных. Их отсутствие не ломает установку, но описание пакета в реестре становится неполным.

Проверка состава выполняется перечислением содержимого собранного дистрибутива:

unzip -l dist/my_package-1.2.0-py3-none-any.whl

Перечень показывает, что получит пользователь. Сравнение с ожидаемым набором файлов занимает минуту и предотвращает ошибку, которая иначе обнаружится после публикации.

Отдельный случай — файлы, нужные для разработки, но не для установки. Тесты, примеры и конфигурация инструментов не должны попадать в дистрибутив, поскольку увеличивают объём и могут содержать данные, не предназначенные для распространения.

Практика: настройка пакета без типичных ошибок

1. Используйте src-layout. Он исключает ситуацию, когда тесты проходят без установки пакета, а у пользователя импорт не работает.

2. Указывайте минимальную версию Python. Поле requires-python предотвращает установку на неподдерживаемой версии с непонятной ошибкой.

3. Разделяйте обязательные и необязательные зависимости. Инструменты разработки относятся к optional-dependencies, иначе они устанавливаются всем пользователям.

4. Не фиксируйте версии зависимостей в пакете. Библиотека указывает диапазон, а приложение — точные версии. Фиксация в библиотеке создаёт конфликты; о причинах — в статье про конфликты зависимостей.

5. Описывайте пакет в метаданных. Поля description, readme и license используются реестром и инструментами проверки; о лицензиях — в статье про лицензии зависимостей.

6. Проверяйте установку в чистом окружении. Установка собранного wheel в новое окружение выявляет пропущенные файлы и неверные зависимости; об окружениях — в статье про venv и uv.

7. Включайте данные пакета явно. Файлы шаблонов и справочники не попадают в wheel автоматически и требуют объявления.

8. Настраивайте инструменты в том же файле. Конфигурация линтера и тестов в pyproject.toml не требует отдельных файлов; о настройке — в статье про ruff.

Рекомендации:

  1. Начинайте с src-layout, даже если пакет внутренний. Перенос кода в src/ позже требует правки импортов и конфигурации.
  2. Проверяйте метаданные после сборки. Инструмент проверки дистрибутива показывает ошибки описания до публикации.
  3. Не смешивайте setup.py и pyproject.toml. Двойное описание приводит к расхождению метаданных.
  4. Версионируйте пакет с первого релиза. Отсутствие версии в метаданных делает пакет неустанавливаемым из реестра.
  5. Тестируйте установку, а не только импорты. Тесты, запущенные из каталога проекта, не проверяют установленный пакет.

Практический сценарий: неработающий импорт у пользователя при рабочих тестах

Контекст: внутренний пакет устанавливался из реестра, и часть пользователей получала ошибку импорта. В репозитории все тесты проходили.

  1. Диагностика. Пакет использовал flat-layout: код лежал в корне репозитория. При запуске тестов текущий каталог попадал в путь поиска, поэтому импорт работал без установки. У пользователя устанавливался пакет, в который часть модулей не попала.
  2. Причина. Конфигурация сборки не описывала вложенные подпакеты явно, а автоматическое обнаружение сработало только для верхнего уровня. Проверка установки в чистом окружении не выполнялась.
  3. Действия. (а) Код перенесли в src/, чтобы импорт работал только из установленного пакета; (б) конфигурацию сборки перевели на автоматическое обнаружение пакетов в src/; (в) добавили шаг конвейера: установка собранного wheel в чистое окружение и прогон импортов; (г) проверили содержимое wheel перечислением файлов; (д) добавили проверку метаданных перед публикацией.
  4. Результат. Ошибка импорта исчезла, а проверка установки в чистом окружении выявляет пропущенные файлы до публикации, а не после жалобы пользователя.

Вывод: src-layout и проверка установки в чистом окружении устраняют основной класс ошибок распространения. Тесты, запущенные из каталога проекта, не проверяют то, что получит пользователь, поскольку импорт в них работает без установки.

FAQ

Обязателен ли src-layout? Нет, но он исключает ситуацию, когда код работает без установки. Для публикуемого пакета это существенное преимущество.

Нужен ли setup.py? Для большинства пакетов нет: конфигурация описывается в pyproject.toml. Файл setup.py остаётся для сложных сценариев сборки.

Как указать версии зависимостей в библиотеке? Диапазоном с нижней границей. Точная фиксация в библиотеке создаёт конфликты у пользователей, устанавливающих несколько пакетов.

Что проверяется перед публикацией? Содержимое wheel, метаданные и установка в чистое окружение. Проверка в репозитории не покрывает установку.

Источники: Python Packaging User Guide, setuptools docs, PEP 621 — ссылки в шапке.