Сборка дистрибутива (пакетирование)

Альтернативные PyPI системы распространения Python-модулей — Conda (и подобные ей, но поменьше) — здесь не рассматриваются.

Выделенное сообщество — Python Packaging Authority: спецификации, проекты, планирование, движ ☺

Выделенный информационный проект — Python Packaging User Guide. В принципе, его достаточно ☺

Сборка дистрибутива

Что умеет pip install:

  1. Скачать исходники, собрать и установить их
    • Нужны сборочные зависимости
    • В случае модулей на компилируемых ЯП это может быть очень непросто

      • ⇒ часто встречаются прихаканные в исходниках бинарники (например, скомпилированные переводы, а то и прямо исполняемые файлы, как в cmake)

  2. Скачать готовый пакет
    • Под разные архитектуры и разные версии Python
    • В случае модулей на компилируемых ЯП — ещё и под разные ОС
    • ⇒ универсальный формат Wheel (pep-0427)

Публиковать нужно и то, и то (лицензия, вопросы доработки и т. п.), а вот рассчитывать на сборку «на месте» не стоит. Две задачи:

  1. Сформировать дистрибутив (wheel)

    • ⇒ Предварительно собрать все целевые генераты
  2. Сформировать дистрибутив исходников (sdist) — полный комплект исходных текстов, достаточный для сборки в окружении только сборочных зависимостей

    • ⇒ Предварительно убрать все генераты и мусор

Setuptools и pyproject.toml

Наиболее низкоуровневая система сборки — Setuptools

Build — простейшая система сборки дистрибутива

В действительности, build — это Reference implementation сборки при помощи Setuptools.

В репозитории с примером

Что должно входить в wheel / sdist?

  1. Пакет(ы) c python-файлами
  2. Сценарии запуска (см. далее про точки входа)
  3. Непрограммные файлы (картинки, переводы, встроенная документация и т. п.)

Для того, чтобы упаковать какие-то дополнительные файлы в дистрибутив, необходимо, чтобы после сборки эти файлы (неважно, генераты или нет) оказались в каталоге с пакетом

Для того, чтобы упаковать в исходный дистрибутив дополнительные файлы (переводы, тесты и т. п.), их надо перечислить в заготовке файла-манифеста MANIFEST.in. Некоторые файлы включаются автоматически.

Более простой пример, в котором:

Пакетные зависимости

Эксплуатационные зависимости
Набор модулей, необходимых для эксплуатации приложения
  • Обязательные / предполагаемые
  • Включённые в python-инфраструктуру / все остальные (с этим проблема)
Сборочные зависимости

Набор модулей и ПО, необходимый для разработки

  • + Средства сборки

  • + Средства тестирования

  • - Возможно, часть эксплуатационных зависимостей замещена квазиобъектами

Отслеживание зависимостей

Эксплуатационные зависимости

Сборочные зависимости

Комбайны (poetry, uv и т. п.) имеют свои инструменты хранения и установки зависимостей.

Как определить список зависимостей?

Неудобные варианты:

  1. Сделать pip freeze в рабочем окружении и угадать, что из этого всего понадобится при сборке и при запуске

    • pip freeze не умеет отличать «мусор» (левые пакеты) и пакеты установленные только по зависимостям от собственно зависимостей

    • сборочные зависимсоти от эксплуатационных он тоже не отличает
    • Приходится всё это отслеживать вручную
  2. Скопировать в чистое окружение и запускать сборку / само приложение, добавляя в соответствующий класс зависимостей всё, из-за отсутствия чего падает
    • установить в сборочное окружение все эксплуатационные пакеты не всегда возможно / хочется / получится запустить

  3. Строго соблюдать дисциплину установки и удаления пакетов в окружении и тщательно обновлять requirements.txt и project.dependency-groups.devel.

Эксплуатационные зависимости отслеживаются по import-ам в исходных текстах с помощью Py3DepHell:

py3req --inspect_env --only_top_module пакет > requirements.txt

Сборочные зависимости: инструмента, кажется, нет!

<!> Возможны ситуации, когда для сборки требуется инструмент, который был поставлен автоматом по зависимости, а то, что его требовало, для сборки не нужно.

Строгие VS нестрогие зависимости на версии

Точки входа

Точки входа — это некоторые символы (как правило, функции), опубликованные в специальном разделе метаинформациии пакета. Их имена могут быть заранее не известны, но их можно определить по группе, загрузить и вызвать.

Как запустить `doit` при сборке

<!> Этот раздел немного пижонский, особого смысла запускать doit (или любой другой инструмент сборки проекта при сборке дистрибутива) особого смысла не имеет. Возможно, его стоит заменить на что-то более полезное.

  1. перебить сам setuptools.build_meta (конкретно — функцию build_wheel(), в которой и запустить doit)

       1 backend.py:from setuptools import build_meta as _orig
       2 backend.py:from setuptools.build_meta import *
       3 backend.py:from pathlib import Path
       4 backend.py:
       5 backend.py:def build_wheel(*args, **kwargs):
       6 backend.py:    …запуск doit
       7 backend.py:    return _orig.build_wheel(*args, **kwargs)
    
    • Не забыть добавить этот файл (в примере он называется backend.py) в MANIFEST.in

  2. Написать свой setup.py, в котором переопределить команду build_py примерно так

       1 from setuptools import setup
       2 from setuptools.command.build_py import build_py
       3 from pathlib import Path
       4 
       5 class build_py(build_py):
       6     def run(self):
       7 запуск doit
       8         super().run()
       9 
      10 setup(cmdclass={"build_py": build_py})
    
  3. Написать свой плагин к setuptools, в котором добавить дополнительную команду к уже имеющейся команде build

    • Это уже более сложное дело, зато полученные пакет-плагин можно будет применять к любому проекту!
    • Пример в проекте setuptools-gettext:

      • Точки входа в pyproject.toml (сами подкоманды и функция pyprojecttoml_config())

      • Определение новой команды (класса) build_mo в __init__.py

      • Регистрация её в качестве подкоманды в функции pyprojecttoml_config() из __init__.py

    • <!> Никто этого ещё не сделал!

Д/З

Обеспечить в семестровом проекте:

Это весь семестровый проект, готовый к сдаче!

LecturesCMC/PythonDevelopment2026/12_Packaging (последним исправлял пользователь FrBrGeorge 2026-04-29 21:09:20)