Прямой ответ
Современный 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.
Рекомендации:
- Начинайте с src-layout, даже если пакет внутренний. Перенос кода в
src/позже требует правки импортов и конфигурации. - Проверяйте метаданные после сборки. Инструмент проверки дистрибутива показывает ошибки описания до публикации.
- Не смешивайте setup.py и pyproject.toml. Двойное описание приводит к расхождению метаданных.
- Версионируйте пакет с первого релиза. Отсутствие версии в метаданных делает пакет неустанавливаемым из реестра.
- Тестируйте установку, а не только импорты. Тесты, запущенные из каталога проекта, не проверяют установленный пакет.
Практический сценарий: неработающий импорт у пользователя при рабочих тестах
Контекст: внутренний пакет устанавливался из реестра, и часть пользователей получала ошибку импорта. В репозитории все тесты проходили.
- Диагностика. Пакет использовал flat-layout: код лежал в корне репозитория. При запуске тестов текущий каталог попадал в путь поиска, поэтому импорт работал без установки. У пользователя устанавливался пакет, в который часть модулей не попала.
- Причина. Конфигурация сборки не описывала вложенные подпакеты явно, а автоматическое обнаружение сработало только для верхнего уровня. Проверка установки в чистом окружении не выполнялась.
- Действия. (а) Код перенесли в
src/, чтобы импорт работал только из установленного пакета; (б) конфигурацию сборки перевели на автоматическое обнаружение пакетов вsrc/; (в) добавили шаг конвейера: установка собранного wheel в чистое окружение и прогон импортов; (г) проверили содержимое wheel перечислением файлов; (д) добавили проверку метаданных перед публикацией. - Результат. Ошибка импорта исчезла, а проверка установки в чистом окружении выявляет пропущенные файлы до публикации, а не после жалобы пользователя.
Вывод: src-layout и проверка установки в чистом окружении устраняют основной класс ошибок распространения. Тесты, запущенные из каталога проекта, не проверяют то, что получит пользователь, поскольку импорт в них работает без установки.
FAQ
Обязателен ли src-layout? Нет, но он исключает ситуацию, когда код работает без установки. Для публикуемого пакета это существенное преимущество.
Нужен ли setup.py?
Для большинства пакетов нет: конфигурация описывается в pyproject.toml. Файл setup.py остаётся для сложных сценариев сборки.
Как указать версии зависимостей в библиотеке? Диапазоном с нижней границей. Точная фиксация в библиотеке создаёт конфликты у пользователей, устанавливающих несколько пакетов.
Что проверяется перед публикацией? Содержимое wheel, метаданные и установка в чистое окружение. Проверка в репозитории не покрывает установку.
Источники: Python Packaging User Guide, setuptools docs, PEP 621 — ссылки в шапке.