Python package guidelines (Русский)

From ArchWiki
Состояние перевода: На этой странице представлен перевод статьи Python package guidelines. Дата последней синхронизации: 6 декабря 2023. Вы можете помочь синхронизировать перевод, если в английской версии произошли изменения.
Указания по созданию пакетов

32-bitCLRCMakeCrossDKMSEclipseElectronFontFree PascalGNOMEGoHaskellJavaKDEKernelLispMesonMinGWNode.jsNonfreeOCamlPerlPHPPythonRRubyRustShellVCSWebWine

Этот документ охватывает стандарты и рекомендации по написанию файлов PKGBUILD для программ, работающих на Python.

Именование пакета

Для библиотек Python 3 используйте python-имямодуля. Также используйте префикс, если пакет предоставляет программу, тесно связанную с экосистемой Python (например, pip или tox). Для других приложений используйте только имя программы.

Примечание: Название пакета должно быть полностью в нижнем регистре.

Архитектура

Смотрите PKGBUILD (Русский)#arch.

Пакет Python, содержащий расширения C, является архитектурно-зависимым. В противном случае он, скорее всего, является архитектурно-независимым.

Пакеты, собираемые с помощью setuptools, определяют свои расширения C с помощью ключевого слова ext_modules в setup.py.

Исходники

Примечание: Согласно RFC0020, исходники стоит по возможности получать непосредственно у апстрима, а не из sdist-архивов на PyPI.

URL загрузки, связанные с сайтом PyPI, содержат непредсказуемый хэш, который необходимо получать с сайта PyPI каждый раз, когда пакет должен быть обновлён. Это делает их непригодными для использования в PKGBUILD. PyPI предоставляет альтернативную стабильную схему: массив source должен использовать следующие шаблоны URL:

Архив с исходным кодом
https://files.pythonhosted.org/packages/source/${_name::1}/$_name/$_name-$pkgver.tar.gz
Пакет wheel, содержащий только Python-код
https://files.pythonhosted.org/packages/py2.py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py2.py3-none-any.whl (для пакета, совместимого с Python 2 и Python 3)
https://files.pythonhosted.org/packages/py3/${_name::1}/$_name/${_name//-/_}-$pkgver-py3-none-any.whl (для пакета, совместимого только с Python 3)
Обратите внимание, что имя дистрибутива может содержать тире, а его представление в имени файла wheel — нет (они преобразуются в символы подчёркивания).
Архитектурно-зависимый пакет wheel
Дополнительные массивы, специфичные для архитектуры, могут быть добавлены путём добавления подчёркивания и имени архитектуры, например, source_x86_64=('...'). Также можно использовать _py=cp310, чтобы не повторять версию Python:
https://files.pythonhosted.org/packages/$_py/${_name::1}/$_name/${_name//-/_}-$pkgver-$_py-${_py}m-manylinux1_x86_64.whl

Обратите внимание, что используется переменная _name вместо pkgname, поскольку пакеты Python обычно имеют префикс python-. Эта переменная может быть определена следующим образом:

_name=${pkgname#python-}

Методы установки

Пакеты Python обычно устанавливаются с помощью специального менеджера пакетов, такого как pip, который получает пакеты из онлайн-репозитория (обычно PyPI, Python Package Index) и отслеживает соответствующие файлы.

Однако для управления пакетами Python из PKGBUILD необходимо «установить» пакет Python во временное место $pkgdir/usr/lib/python<версия Python>/site-packages/$pkgname.

Для пакетов Python, использующих стандартные метаданные для указания бэкенда сборки в файле pyproject.toml, это проще всего сделать с помощью python-build и python-installer. Старые пакеты могут не указывать, что они используют setuptools, и предлагать только setup.py, который должен быть вызван вручную.

Примечание: Зависимости, прописанные в метаданных, должны быть добавлены в массив depends, иначе они не будут установлены.

Основанный на стандартах (PEP 517)

Совет: Если сборка производится из исходников апстрима, а апстрим полагается на git для получения строки версии проекта, перед сборкой wheel-пакета необходимо задать значение $pkgver переменным окружения, специфичным для используемого инструментария:

Рабочий процесс, основанный на стандартах, прост: создайте wheel-пакет с помощью python-build и установите его в $pkgdir с помощью python-installer:

makedepends=(python-build python-installer python-wheel)

build() {
    cd "$_name-$pkgver"
    python -m build --wheel --no-isolation
}

package() {
    cd "$_name-$pkgver"
    python -m installer --destdir="$pkgdir" dist/*.whl
}

где

  • --wheel указывает создать только wheel-пакет, без архива с исходным кодом.
  • --no-isolation указывает, что пакет должен быть собран с использованием установленных в системе зависимостей (включая пакеты, указанные вами в depends); по умолчанию без этой опции для сборки создаётся изолированное виртуальное окружение.
  • --destdir="$pkgdir" предотвращает попытку прямой установки в хост-систему, а не внутрь файла пакета, что привело бы к ошибке доступа
  • --compile-bytecode=... или --no-compile-bytecode можно передать в installer, но значение по умолчанию выбрано разумно, поэтому в этом нет необходимости.
Важно: Пропуск build и помещение файла .whl в массив source не рекомендуется и должно использоваться только в тех случаях, когда сборка из исходников невозможна (например, для программ, которые поставляются только в виде wheel-пакетов и потому не могут быть собраны из исходных текстов).
Важно: Если ваш пакет является VCS-пакетом (python-…-git), добавьте в функцию prepare команду git -C "${srcdir}/${pkgname}" clean -dfx. Это удалит устаревшие wheel-пакеты вместе с другими артефактами сборки и поможет избежать проблем в дальнейшем. Смотрите также issues в setuptools и Poetry.

setuptools или distutils

Если pyproject.toml отсутствует или не содержит таблицу [build-system], это означает, что проект использует старый формат, использующий файл setup.py, который вызывает setuptools или distutils. Обратите внимание, что хотя distutils включен в стандартную библиотеку Python, наличие установленного setuptools означает, что вы используете пропатченную версию distutils.

makedepends=('python-setuptools')  # если только он не требует именно distutils

build() {
    cd "$_name-$pkgver"
    python setup.py build
}

package() {
    cd "$_name-$pkgver"
    python setup.py install --root="$pkgdir" --optimize=1
}

где:

  • --root="$pkgdir" работает как --destdir выше
  • --optimize=1 заранее компилирует оптимизированные файлы байткода (.opt-1.pyc), чтобы их мог отслеживать pacman, вместо ленивого их создания при запуске программы.
  • Добавление --skip-build оптимизирует ненужную попытку повторного выполнения шагов сборки, уже запущенных в функции build(), если это имеет место.

Некоторые пакеты пытаются использовать setuptools и возвращаются к distutils, если setuptools не может быть импортирован. В этом случае setuptools должен быть добавлен как makedepends, чтобы результирующие метаданные Python были лучше.

Если пакет требует сборки setuptools из-за включения исполняемых файлов (что не поддерживается distutils), но импортирует только distutils, то при сборке будет выдано предупреждение, а полученный пакет будет повреждён (он не будет содержать исполняемых файлов):

/usr/lib/python3.8/distutils/dist.py:274: UserWarning: Unknown distribution option: 'entry_points'
  warnings.warn(msg)

Необходимо сообщить об ошибке в upstream. Для обхода проблемы можно использовать недокументированную функцию setuptools:

# не работает из-за distutils
python setup.py build

# работает, используя setuptools shim
python -m setuptools.launch setup.py build

Если пакет использует python-setuptools-scm, пакет, скорее всего, не будет собран с ошибкой, такой как:

LookupError: setuptools-scm was unable to detect version for /build/python-jsonschema/src/jsonschema-3.2.0.

Make sure you're either building from a fully intact git repository or PyPI tarballs. Most other sources (such as GitHub's tarballs, a git checkout without the .git folder) don't contain the necessary metadata and will not work.

Чтобы пакет собрался, нужно экспортировать переменную окружения SETUPTOOLS_SCM_PRETEND_VERSION со значением $pkgver:

export SETUPTOOLS_SCM_PRETEND_VERSION=$pkgver

Проверка

Важно: Избегайте использования tox для запуска тестов, поскольку он явно предназначен для проверки воспроизводимых конфигураций, загруженных из PyPI во время работы tox, и не проверяет версию, которая будет установлена пакетом. Это противоречит цели наличия функции check вообще.

Большинство проектов Python, предоставляющих набор тестов, используют nosetests или pytest для запуска тестов с test в имени файла или каталога, содержащего набор тестов. В общем случае для запуска набора тестов достаточно просто запустить nosetests или pytest.

check(){
    cd "$srcdir/foo-$pkgver"

    # Для nosetests
    nosetests

    # Для pytest
    pytest
}

Если есть скомпилированное расширение C, тесты необходимо запускать, используя $PYTHONPATH, отражающий текущую мажорную и минорную версию Python, чтобы найти и загрузить его.

check(){
  cd "$pkgname-$pkgver"
  local python_version=$(python -c 'import sys; print("".join(map(str, sys.version_info[:2])))')
  # Для nosetests
  PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-${python_version}" nosetests

  # Для pytest
  PYTHONPATH="$PWD/build/lib.linux-$CARCH-cpython-${python_version}" pytest
}

Некоторые проекты предоставляют setup.py точки входа для запуска теста. Это работает как для pytest, так и для nosetests.

check(){
    cd "$srcdir/foo-$pkgver"

    # Для nosetests
    python setup.py nosetests

    # Для pytest - нужен python-pytest-runner
    python setup.py pytest
}

Советы и рекомендации

Обнаружение отдельных подписей PGP на PyPI

Важно: В мае 2023 функция предоставления отдельных подписей OpenPGP на PyPI была удалена.

Если для sdist-архива существуют отдельные PGP-подписи — их следует использовать для проверки архива. Однако файлы подписей не отображаются непосредственно в разделе загрузки файлов какого-либо проекта на pypi.org. Чтобы обнаружить sdist и их потенциальные файлы подписей, можно воспользоваться этим сервисом для получения обзора по каждому проекту: https://pypi.debian.net/.

Для python-requests это будет https://pypi.debian.net/requests.

Использование версии python

Иногда во время подготовки, сборки, тестирования или установки требуется указать мажорную и минорную версию Python для системы. Не записывайте это жёстко в коде (например, 3.9 или 3.10), а запустите интерпретатор Python для получения информации и сохранения её в локальной переменной:

check(){
  local python_version=$(python -c 'import sys; print(".".join(map(str, sys.version_info[:2])))')
  ...
}

Использование site-packages

Иногда во время сборки, тестирования или установки требуется обратиться к системному каталогу site-packages. Не следует записывать путь жёстко в коде, вместо этого запустите интерпретатор Python для получения пути и сохранения его в локальной переменной:

check(){
  local site_packages=$(python -c "import site; print(site.getsitepackages()[0])")
  ...
}

Тестовый каталог в site-packages

Убедитесь, что вы не установили каталог с именем tests в site-packages. (т.е. /usr/lib/pythonX.Y/site-packages/tests/). Некоторые Python-проекты, использующие setuptools, иногда неправильно настроены и добавляют каталог с тестами в качестве Python-пакета верхнего уровня. Если вы столкнулись с этим, то можете помочь, отправив проекту сообщение об ошибке с просьбой исправить это, например, вот так.