Для меня это оказалось довольно трудно, и я подумал, что неплохо было бы поделиться своим опытом в статье.
Структура проекта
Вот файловая структура, которую мы будем использовать для этого проекта:
.
├── classification_library
│ ├── data
│ ├── __init__.pxd
│ ├── __init__.pyx
│ └── test_classification_library.py
├── MANIFEST.in
├── pyproject.toml
├── README.md
└── setup.py
Скрипт установки
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
этот файл будет выглядеть вот так:
from setuptools import find_packages, setup
import numpy as np
from Cython.Build import cythonize
with open("README.md", 'r') as f:
long_description = f.read()
setup(
name="classification_library",
version="0.0.3",
packages=find_packages(),
author="Arin Khare",
description="A classification library using a novel audio-inspired algorithm.",
long_description=long_description,
long_description_content_type='text/markdown',
url="https://github.com/lol-cubes/classification-library",
ext_modules=cythonize(["classification_library/__init__.pyx"]),
include_dirs=np.get_include(),
install_requires=[
'numpy>=1.19.2',
'PyObjC;platform_system=="Darwin"',
'PyGObject;platform_system=="Linux"',
'playsound==1.2.2'
]
)
Обратите внимание на последние три аргумента вызова 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!
Читайте также:
- Не используйте оператор «+» для объединения строк в Python
- Создаем краткое содержание текста с помощью Python без NLP
- Считаете, что Python лучше всех? Просто вы не имели дело с Rust
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Arin Khare: How to Deploy a Cython Package to PyPI