Источник: Nuances of Programming
Для меня это оказалось довольно трудно, и я подумал, что неплохо было бы поделиться своим опытом в статье.
Структура проекта
Вот файловая структура, которую мы будем использовать для этого проекта:
Скрипт установки
setup.py — это скрипт установки пакетов Python. Он указывает инструментам для работы с пакетами, что делать с кодом.
Первым делом давайте импортируем необходимые функции из инструментов установки setuptools.
from setuptools import find_packages, setup
Думаю, вы знаете, что делает setup, раз уж имеете дело с пакетом Cython. Если же вы не работали раньше с find_packages, знайте: делает он именно то, о чём можно догадаться по его названию — выполняет поиск пакетов. Зачем он нам нужен, станет ясно, когда увидим вызов функции setup.
Но прежде чем вызывать setup, нужно импортировать кое-что ещё:
from Cython.Build import cythonize
import numpy as np
cythonize нужен, чтобы преобразовать код Cython в C, а numpy — потому что код на С, генерирующий Cython, зависит от него.
Кроме того, нам нужно загрузить README, который будет отображаться на странице пакета в PyPI. Это можно сделать с помощью, например, такого кода:
with open("README.md", 'r') as f:
long_description = f.read()
После добавления вызова setup этот файл будет выглядеть вот так:
Обратите внимание на последние три аргумента вызова setup: ext_modules указывает расширения C для пакета; include_dirs необходимо указать, чтобы можно было скомпилировать зависимые от numpy расширения C; а install_requires указывает пакеты, от которых зависит classification_library.
Вместо Cython.Build.cythonize можно использовать setuptools.Extension, чтобы генерировать и компилировать C-расширения, но Cython установить всё равно придётся.
Указание зависимостей компоновки
Возможно, вас уже озадачило импортирование numpy и Cython в setup.py. Если вы ещё не успели об этом подумать, давайте разберёмся, почему это может быть проблемой.
Когда мы устанавливаем что-нибудь с помощью pip, временно загружается setup.py, в котором запускаются определённые команды (например, python3 setup.py <command>), чтобы нужные файлы оказались в нужном месте sys.path, доступные для импортирования. Но операторы импорта будут выдавать ошибки, если только у человека, который устанавливает пакет с помощью pip, случайно не окажутся Cython и numpy. Поэтому нужно указать их в pyproject.toml. Таким образом pip временно устанавливает эти пакеты, которые, в свою очередь, необходимы для установки нашего пакета. Вот как выглядит мой pyproject.toml:
[build-system]
requires = ["setuptools", "wheel", "numpy>=1.19.0", "Cython>=0.29.21"]
build-backend = "setuptools.build_meta"
Последняя строка фактически указывает диспетчеру пакетов на setuptools.build_meta, давая знать, что делать с другой информацией, которую мы здесь видим.
Возможности pyproject.toml не ограничиваются простым указанием зависимостей компоновки, но это уже тема другой статьи.
Включение файлов, не относящихся к Python
Наличие файла MANIFEST.in очень важно, ведь он указывает файлы, не относящиеся к Python. Если они не будут включены, то инструменты для работы с пакетами их проигнорируют. Не нужно включать файлы .pyx, потому что они компилируются в файлы .so, которые распознаются как файлы Python. А вот файлы .pxd будут проигнорированы, если их не включить. С учётом всего этого мой MANIFEST.in будет выглядеть примерно так:
include classification_library/__init__.pxd
include classification_library/data
Генерирование файлов Source Archives и Wheel
Если у вас ещё не установлен wheel, установите его с помощью:
pip install wheel
Теперь можно запустить команду:
python3 setup.py sdist bdist_wheel
Вы должны увидеть каталог dist с файлами, оканчивающимися на .tar.gz и .whl. Если вы на Linux, и файлы wheel оканчиваются на cp38-linux_x86_64.whl, придётся проделать немного дополнительной работы.
По техническим причинам PyPI не поддерживает загрузку такого рода файлов wheel. Чтобы поменять формат на правильный, надо установить auditwheel:
pip install auditwheel
Затем можно запустить такую команду:
auditwheel repair dist/<your_package_name>-cp38-cp38-linux_86_64.whl
А дальше перемещаем файлы, сгенерированные auditwheel, в каталог dist и удаляем старые wheels:
mv wheelhouse/* dist
rm dist/*-cp38-cp38-linux_x86_64.whl
Загрузка на PyPI
Загрузка файлов на PyPI осуществляется с помощью пакета twine.
Уверен, что вы уже запустили эту команду, но на всякий случай подскажу, как установить twine:
pip install twine
Создаём учётную запись, если у вас её ещё нет.
Вы можете указывать имя пользователя и пароль при каждой загрузке пакета, а можете поместить их в файл ~/.pypirc вот так:
[pypi]
username = <your_username>
password = <your_password>
Хотите обезопасить себя ещё больше? Тогда можете получить токен API из настроек своей учётной записи. Инструкции можно найти там же на странице настроек.
Теперь с помощью команды:
python3 -m twine upload --repository pypi dist/*
можно загружать файлы на PyPI. Если всё прошло гладко, вы сможете ввести в терминал:
pip install <your_cython_package>
и пакет будет установлен. Дальше надо будет увеличить номер версии в setup.py и повторно генерировать файлы исходного кода в составе комплекта поставки, а также файлы wheel, и перезагружать с помощью twine при каждом обновлении пакета.
Спасибо вам за внимание и удачи в приключениях с Cython!
Читайте также:
Перевод статьи Arin Khare: How to Deploy a Cython Package to PyPI