Формирование колесных артефактов для Python проектов

Раздел: Разработка на Python -> Упаковка проектов

Сборка wheel-пакета превращает исходный код Python-проекта в готовый к установке бинарный или чистый Python-артефакт с расширением .whl. Wheel-формат ускоряет установку, так как не требует повторной компиляции и сокращает время развёртывания. В статье рассматриваются основные и альтернативные методы создания wheel-дистрибутивов, даются примеры команд и кода, а также разбираются типичные трудности.

Основной способ: сборка с помощью python -m build

Как собрать wheel-пакет современным способом, используя утилиту build?

Инструмент build является рекомендованным в Python-сообществе. Он абстрагируется от конкретной системы сборки (setuptools, flit, poetry и др.), читая pyproject.toml. Для начала следует убедиться, что в корне проекта присутствует файл pyproject.toml или setup.py с необходимыми метаданными.

# Установка build в изолированном окружении (рекомендуется)
pip install build

# Запуск сборки из директории проекта
python -m build

Python build wheel (сборка wheel-пакета python)

После выполнения команды в папке dist/ появляются два файла: исходный архив (.tar.gz) и wheel-пакет (.whl). Параметр --wheel позволяет собрать только колесо:

python -m build --wheel

Проблема: Ошибка 'python -m build' не является внутренней или внешней командой - значит, не установлен пакет build.

Решение: Установить build: pip install build.

Другая ошибка: Сборка падает с сообщением о неправильном формате pyproject.toml - например, отсутствует секция [build-system] или неверно указан backend.

Решение: Добавить минимальную конфигурацию:

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

Альтернативные методы сборки wheel

Как собрать wheel с помощью pip wheel?

Команда pip wheel позволяет сформировать wheel-файл из любого источника, поддерживаемого pip (PyPI, локальный путь, git-репозиторий). Этот метод удобен, когда нужно быстро получить колесо для последующей установки на других машинах без доступа к сети.

# Сборка wheel из текущей директории (наличие setup.py или pyproject.toml обязательно)
pip wheel --no-deps .

# Сборка колеса из git-репозитория (клонирование и сборка)
pip wheel --no-deps git+https://github.com/example/project.git@main

Флаг --no-deps отключает скачивание зависимостей, чтобы они не попали в итоговый wheel. Результат помещается в текущую папку, если не указана опция -w (directory).

Проблема: При сборке wheel из локальной папки возникает ошибка ERROR: Could not find a version that satisfies the requirement ... - возможно, pip пытается разрешить зависимости и не находит их.

Решение: Использовать --no-deps или указать --find-links для поиска локальных колёс.

Как использовать setuptools напрямую для создания wheel?

До появления утилиты build стандартной практикой был вызов setup.py напрямую. Этот метод всё ещё работает, хотя считается устаревшим. Для сборки wheel требуется установленный пакет wheel.

pip install wheel
python setup.py bdist_wheel

Результат появится в папке dist/. При необходимости можно указать свой каталог через опцию --dist-dir.

Проблема: Ошибка error: invalid command 'bdist_wheel' - модуль wheel не установлен.

Решение: Выполнить pip install wheel.

Дополнительно: В больших проектах прямой вызов setup.py может приводить к непредсказуемому поведению в изолированных окружениях, поэтому рекомендуется использовать python -m build.

Как собрать wheel во Flit?

Flit - это минималистичная система сборки, которая использует pyproject.toml без setup.py. Установка flit и сборка колеса выполняются одной командой:

pip install flit
flit build

Если нужно получить только wheel, можно добавить флаг --format wheel. Файл окажется в папке dist/.

Проблема: Flit не находит модуль, указанный в pyproject.toml, и выдаёт ошибку Could not find module 'mypackage'.

Решение: Проверить, что директория с модулем существует и в pyproject.toml правильно задано поле module или packages.

Как собрать wheel через Poetry?

Poetry - это менеджер зависимостей и сборщик проектов. Он работает с собственным форматом pyproject.toml. Для сборки wheel используется команда:

poetry build

После выполнения в папке dist/ появляются .tar.gz и .whl. Можно получить только колесо с помощью poetry build --format wheel.

Проблема: При сборке Poetry жалуется на несоответствие версии Python в pyproject.toml и текущем окружении.

Решение: Указать корректную версию Python в секции [tool.poetry.dependencies] или использовать pyenv для переключения интерпретатора.

Расширенные примеры сборки wheel-пакетов

Ниже приведены нестандартные сценарии, которые могут потребоваться при разработке реальных проектов.

Пример 1: Сборка pure Python wheel с динамической версией через setuptools-scm

Если версия пакета определяется по git-тегам, удобно использовать setuptools-scm. Пример pyproject.toml:

Пример
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=6.0"]
build-backend = "setuptools.build_meta"

[project]
name = "my_pure_package"
dynamic = ["version"]

[tool.setuptools.packages.find]
include = ["my_package", "my_package.*"]

[tool.setuptools_scm]
write_to = "my_package/_version.py"

Сборка:

Пример
python -m build

Результат в dist/:

my_pure_package-1.2.3-py3-none-any.whl

Если git-теги отсутствуют, setuptools-scm сгенерирует версию на основе даты коммита. Ошибка: LookupError: setuptools-scm was unable to detect version - решается созданием хотя бы одного тега или установкой переменной SETUPTOOLS_SCM_PRETEND_VERSION.

Пример 2: Сборка wheel с C-расширением (ускорение на C)

Для проектов, содержащих расширения на C (например, с помощью Cython или raw C), pyproject.toml может выглядеть так:

Пример
[build-system]
requires = ["setuptools>=61.0", "wheel", "numpy"]
build-backend = "setuptools.build_meta"

[project]
name = "my_c_ext"
version = "0.1.0"

[tool.setuptools.packages.find]
include = ["my_c_ext"]

# Поля для C-расширений
[tool.setuptools.ext-modules]
my_c_ext.my_mod = "my_c_ext/_my_mod.c"

Альтернатива - setup.py с Extension:

Пример
from setuptools import setup, Extension

setup(
    name="my_c_ext",
    version="0.1.0",
    ext_modules=[Extension("my_c_ext.my_mod", ["my_c_ext/_my_mod.c"])],
)

Сборка:

Пример
python -m build

Результат:

my_c_ext-0.1.0-cp311-cp311-linux_x86_64.whl

Ошибки компиляции (отсутствие компилятора, libpython) решаются установкой build-essential и python3-dev (на Linux) или MSVC Build Tools (на Windows).

Пример 3: Включение дополнительных файлов через MANIFEST.in

Если проект содержит статические файлы, которые должны быть внутри wheel, их нужно перечислить в MANIFEST.in. Пример MANIFEST.in:

Пример
include my_package/data/*.json
include my_package/templates/*.html
recursive-include my_package/locale *

В pyproject.toml (или setup.cfg) следует указать include_package_data=True:

Пример
[tool.setuptools]
include-package-data = true

Сборка:

Пример
python -m build

Проверить содержимое wheel можно командой:

Пример
unzip -l dist/my_package-0.1.0-py3-none-any.whl | grep -E "(data|templates|locale)"

Если файлы отсутствуют, стоит убедиться, что MANIFEST.in лежит в корне проекта и что include_package_data включен.

Пример 4: Сборка платформозависимого wheel (manylinux) с помощью cibuildwheel

Для автоматической сборки колес под разные платформы и архитектуры используется инструмент cibuildwheel. Пример настройки в pyproject.toml:

Пример
[tool.cibuildwheel]
build = "cp39-* cp310-*"
skip = "*-win32 *-manylinux_i686"
archs = ["x86_64", "aarch64"]

Запуск (обычно в CI):

Пример
cibuildwheel --platform linux

Результат - папка wheelhouse/ с колесами типа my_package-1.0-cp39-cp39-manylinux_2_28_x86_64.whl. Ошибка Failed to build manylinux wheels часто связана с отсутствием Docker (для Linux) или многоплатформенной сборки - решается установкой Docker и включением эмуляции QEMU.

Пример 5: Сборка через flit с указанием зависимостей для разных версий Python

Flit также поддерживает условные зависимости. Пример pyproject.toml:

Пример
[build-system]
requires = ["flit_core>=3.8"]
build-backend = "flit_core.buildapi"

[project]
name = "my_flit_pkg"
version = "0.1.0"
dependencies = [
    "requests",
    "importlib-metadata; python_version < '3.8'",
]

Сборка:

Пример
flit build --format wheel

Результат:

my_flit_pkg-0.1.0-py3-none-any.whl

Чтобы проверить корректность зависимостей, можно извлечь METADATA из wheel:

Пример
unzip -p dist/my_flit_pkg-0.1.0-py3-none-any.whl *.dist-info/METADATA

Сборка wheel-пакета Python - comments

En
Python build wheel (python)