Методы создания распространяемых пакетов для языка Python
Сборка пакета Python: основные подходы
Вопрос: Как создать минимальный Python пакет для распространения?
Современным стандартом является использование pyproject.toml вместе с инструментом setuptools. Такой подход рекомендуется PEP 621 и считается основным.
Пример структуры пакета:
mypackage/\n├── pyproject.toml\n├── src/\n│ └── mypackage/\n│ └── __init__.py\n└── README.mdPython make package (создание пакета python)
Файл pyproject.toml содержит метаданные:
[build-system]\nrequires = ["setuptools>=61.0"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\nname = "mypackage"\nversion = "0.1.0"\ndescription = "Пример пакета"\nreadme = "README.md"\nauthors = [{name = "Автор", email = "author@example.com"}]\nrequires-python = ">=3.8"\ndependencies = []После настройки выполняется сборка:
python -m buildКоманда создаёт дистрибутивы в папке dist/.
Типичная ошибка: пакет не обнаруживается, если исходный код расположен в корне проекта. Решение - явно указать [tool.setuptools.packages.find] или разместить код в src/.
Ошибка: ValueError: "version" must be a string - версия должна быть строкой, а не числом.
Альтернатива 1: использование Poetry
Poetry предлагает интегрированное управление зависимостями и упаковкой.
Пример инициализации:
poetry new mypackageФайл pyproject.toml для Poetry:
[tool.poetry]\nname = "mypackage"\nversion = "0.1.0"\ndescription = ""\nauthors = ["Автор <author@example.com>"]\n\n[tool.poetry.dependencies]\npython = "^3.8"\n\n[build-system]\nrequires = ["poetry-core"]\nbuild-backend = "poetry.core.masonry.api"Сборка выполняется командой:
poetry buildПроблема: Poetry создает виртуальное окружение автоматически. Если оно не нужно, следует отключить через настройки.
Ошибка: конфликт зависимостей при добавлении пакета - решается ручным разрешением или использованием poetry lock.
Альтернатива 2: Flit для быстрой публикации
Flit ориентирован на простые пакеты без сложных зависимостей.
Команда инициализации:
flit initВ результате создаётся pyproject.toml с конфигурацией Flit:
[build-system]\nrequires = ["flit_core>=3.2,<4"]\nbuild-backend = "flit_core.buildapi"\n\n[project]\nname = "mypackage"\nversion = "0.1.0"\ndescription = ""\nauthors = []Для публикации используется:
flit publishПроблема: Flit требует, чтобы код был в одной директории с pyproject.toml, и не поддерживает src-структуру по умолчанию.
Ошибка: отсутствие файла __init__.py - пакет не будет собран.
Расширенные примеры сборки и публикации
Ниже приведены примеры с подробными инструкциями и ожидаемыми выводами команд.
Пример: создание консольного скрипта с помощью entry_points
Для того чтобы после установки пакета можно было запустить команду из терминала, в pyproject.toml добавляется секция [project.scripts].
[project.scripts]\nmycli = "mypackage.cli:main"После сборки и установки пакета:
pip install dist/mypackage-0.1.0-py3-none-any.whlВыполнение команды:
mycli --helpРезультат:
usage: mycli [-h]\noptions:\n -h, --help show this help message and exit
Пример: автоматическое управление версиями с setuptools_scm
Подключается плагин, который извлекает версию из Git тегов.
[build-system]\nrequires = ["setuptools>=61.0", "setuptools_scm>=6.0"]\nbuild-backend = "setuptools.build_meta"\n\n[project]\ndynamic = ["version"]\n\n[tool.setuptools_scm]\nwrite_to = "src/mypackage/_version.py"После создания Git тега v0.1.0 и сборки:
python -m buildВ файле _version.py появится строка:
version = "0.1.0"
Пример: публикация на TestPyPI через twine
После сборки дистрибутивов загружаются на тестовый индекс:
twine upload --repository testpypi dist/*Процесс потребует ввода токена API. Успешная загрузка:
Uploading mypackage-0.1.0-py3-none-any.whl\n100% ━━━━━━━━━━━━━━━━━━━━ 6.3 kB/6.3 kB • 0:00:00 • ?\nUploading mypackage-0.1.0.tar.gz\n100% ━━━━━━━━━━━━━━━━━━━━ 5.2 kB/5.2 kB • 0:00:00 • ?
Пример: Poetry - добавление зависимости и сборка
Добавление пакета requests:
poetry add requestsВ pyproject.toml появится:
[tool.poetry.dependencies]\npython = "^3.8"\nrequests = "^2.31"Сборка:
poetry buildВывод:
Building mypackage (0.1.0)\n - Building sdist\n - Built mypackage-0.1.0.tar.gz\n - Building wheel\n - Built mypackage-0.1.0-py3-none-any.whl
Пример: Flit - публикация с указанием описания из README
В pyproject.toml указывается:
[project]\nreadme = "README.md"И публикация:
flit publishЕсли не указан репозиторий, по умолчанию PyPI:
Found 1 file to publish: mypackage-0.1.0-py2.py3-none-any.whl\nUploading to https://pypi.org/legacy/\nUpload complete
Пример: включение дополнительных файлов с MANIFEST.in
Если в пакет нужно включить файлы, не являющиеся частью Python кода (например, конфигурации), создаётся MANIFEST.in:
include config/*.yaml\nrecursive-include data/ *.jsonПосле сборки проверяется содержимое тарбола:
tar tzf dist/mypackage-0.1.0.tar.gzРезультат показывает включённые файлы.
src/mypackage/__init__.py\nsrc/mypackage/cli.py\nconfig/default.yaml\ndata/settings.json