Пользователь
8,0
рейтинг
27 января 2014 в 22:47

Разработка → Python на колёсах перевод tutorial

Инфраструктура системы пакетов для Python долго подвергалась критике как от разработчиков, так и от системных администраторов. Долгое время даже само комьюнити не могло прийти к соглашению, какие именно инструменты использовать в каждом конкретном случае. Уже существуют distutils, setuptools, distribute, distutils2 в качестве базовых механизмов распространения и virtualenv, buildout, easy_install и pip в качестве высокоуровневых инструментов управления всем этим беспорядком.

До setuptools основным форматом распространения были исходные файлы или некоторые бинарные MSI-дистрибутивы для Windows. Под Linux были изначально сломанный bdist_dumb и bdist_rpm, который работал только на системах, основанных на Red Hat. Но даже bdist_rpm работал недостаточно хорошо для того, чтобы люди начали его использовать.

Несколько лет назад PJE попытался исправить эту проблему, предоставив смесь из setuptools и pkg_resources для улучшения distutils и добавления метаданных в Python-пакеты. В дополнение к этому он написал утилиту easy_install для их установки. По причине отсутствия формата распространения, поддерживающего метаданные, был предоставлен формат 'яиц' [egg].

Python eggs – обычные zip-архивы, содержащие python-пакет и необходимые метаданные. Хотя многие люди, вероятно, никогда намеренно не собирали egg'и, их формат метаданных до сих пор жив-здоров. И все разворачивают свои проекты с использованием setuptools.

К сожалению, некоторое время спустя сообщество разделилось, и часть его провозгласила смерть бинарных форматов и 'яиц' в частности. После этого pip, замена easy_install, перестал принимать egg-формат.

Потом прошло еще немного времени, и отказ от бинарных пакетов стал доставлять неудобства. Люди всё больше и больше стали деплоить на облачные сервера, а необходимость перекомпиляции C-шных библиотек на каждой машине не слишком радует. Так как 'яйца' на тот момент были малопонятны (я так полагаю), их переделали в новых PEP-ах, и назвали 'колёсами' [wheels].

В дальнейшем предполагается, что все действия происходят в virtualenv-окружении.

Что за колесо?


Начнём с простого. Что представляют собой 'колёса' и чем они отличаются от 'яиц'? Оба формата являются zip-файлами. Главная разница в том, что egg можно импортировать без распаковки, wheel же придётся распаковать. Хотя нет никаких технических причин, делающих 'колёса' неимпортируемыми, поддержка их прямого импорта никогда даже не планировалась.

Другое различие в том, что 'яйца' содержат скомпилированные байткод, а 'колёса' – нет. Главное преимущество этого в том, что нет необходимости создавать отдельные wheel'ы для каждой версии Python до тех пор, пока не придётся распространять слинкованные через libpython модули. Хотя в новых версиях Python 3 при использовании стабильного ABI даже это уже можно провернуть.

Однако wheel-формат тоже не лишен проблем, некоторые из которых он наследует от 'яиц'. Например, бинарные дистрибутивы под Linux до сих пор неприемлемы для большинства из-за двух недостатков: Python сам по себе компилируется под Linux в разных формах, и модули линкуются с разными системными библиотеками. Первая проблема вызвана сосуществованием несовместимых версий Python 2: USC2 и USC4. В зависимости от режима компиляции меняется ABI. В настоящее время wheel (насколько я могу судить) не содержит информации о том, с каким режимом Unicode связана библиотека. Отдельная проблема в том, что дистрибутивы Linux меньше совместимы между собой, чем хотелось бы, и обстоятельства могут сложиться так, что сборка, скомпилированная под один дистрибутив, не будет работать на остальных.

Всё это выливается в то, что, вообще говоря, на данный момент бинарные 'колёса' нельзя загружать на PyPI как несовместимые с различными системами.

В дополнение ко всему этому wheel сейчас знает только две крайности: бинарные пакеты и пакеты, содержащие чистый python-код. Бинарные пакеты специфичны для Python ветки 2.x. Сейчас это не кажется большой проблемой, потому что цикл 2.x подходит к концу, и пакетов, собранных только для 2.7, хватит надолго. Но если бы вдруг речь пошла про Python 2.8, была бы интересна возможность заявить, что этот пакет не зависит от версии Python, но он содержит бинарники, поэтому он не может не зависеть от архитектуры.

Единственный случай, оправдывающий существование такого пакета – это когда он содержит распределенные библиотеки, загружаемые с ctypes из CFFI. Такие библиотеки не связаны через libpython и не зависимы от реализации языка (их можно использовать даже с pypy).

Но есть и светлая сторона: ничто не запрещает использовать бинарные wheel’ы в своих собственных однородных инфраструктурах.

Сборка колеса


Итак, теперь мы знаем, что такое wheel. Как сделать своё собственное 'колесо'? Сборка из собственных библиотек – простейший процесс. Всё, что нужно – свежая версия setuptools и библиотека wheel. Как только они оба установлены, 'колесо' собирается следующей командой:
$ python setup.py bdist_wheel

Wheel будет создан в директории пакета. Однако есть одна вещь, которой следует опасаться: распространение бинарников. По умолчанию собираемое 'колесо' (при условии, что в setup.py не используется никаких бинарных шагов) состоит из pure-python кода. Это значит, что даже если распространять .so, .dylib или .dll как часть своего пакета, полученное 'колесо' будет выглядеть платформо-независимым.

Решение этой проблемы – вручную реализовать Distribution из setuptools, скинув флаг чистоты в false:
import os
from setuptools import setup
from setuptools.dist import Distribution

class BinaryDistribution(Distribution):
    def is_pure(self):
        return False

setup(
    ...,
    include_package_data=True,
    distclass=BinaryDistribution,
)

Установка колеса


С использованием свежей версии pip 'колесо' ставится следующим образом:
$ pip install package-1.0-cp27-none-macosx_10_7_intel.whl

Но что с зависимостями? Тут появляются некоторые сложности. Обычно одним из требований к пакету является возможность его установки даже без подключения к интернету. К счастью, pip позволяет отключать загрузку из индекса и устанавливать директорию, содержащую всё необходимое для установки. Если у нас есть wheel’ы для всех зависимостей необходимых версий, можно сделать следующее:
$ pip install --no-index --find-links=path/to/wheels package==1.0

Таким образом будет установлена версия 1.0 пакета package в наше виртуальное окружение.

Колёса для зависимостей


Окей, но что, если у нас нет .whl для всех наших зависимостей? Pip в теории позволяет решить эту проблему использованием команды wheel. Это должно работать как-то так:
pip wheel --wheel-dir=path/to/wheels package==1.0

Эта команда выгрузит все пакеты, от которых зависит наш пакет, в указанную папку. Но есть пара проблем.
Первая состоит в том, что в команде в настоящий момент есть баг, который не выгружает зависимости, которые уже являются 'колёсами'. Так что если зависимость уже доступна на PyPI в wheel-формате, она не будет загружена.

Это временно решается shell-скриптом, который вручную перемещает из кэша скачанные wheel’ы.
#!/bin/sh
WHEEL_DIR=path/to/wheels
DOWNLOAD_CACHE_DIR=path/to/cache
rm -rf $DOWNLOAD_CACHE_DIR
mkdir -p $DOWNLOAD_CACHE_DIR

pip wheel --use-wheel -w "$WHEEL_DIR" -f "$WHEEL_DIR" \
  --download-cache "$DOWNLOAD_CACHE_DIR" package==1.0
for x in "$DOWNLOAD_CACHE_DIR/"*.whl; do
  mv "$x" "$WHEEL_DIR/${x##*%2F}"
done

Вторая проблема чуть серьёзней: как pip найдет наш собственный пакет, если его нет на PyPI? Правильно, никак. Документация в таком случае рекомендует использовать не pip wheel package, а pip wheel -r requirements.txt, где requirements.txt содержит все необходимые зависимости.

Сборка пакетов c использованием DevPI


Такое временное решение проблемы зависимостей вполне применимо в простых ситуациях, но что делать, если есть множество внутренних python-пакетов, зависящих друг от друга? Такая конструкция быстро разваливается.

К счастью, в прошлом году Holker Krekel создал решение этой беды под названием DevPI, который по существу является хаком, эмулирующим работу pip с PyPI. После установки на компьютер DevPI работает как прозрачный прокси перед PyPI и позволяет pip-у устанавливать пакеты из локального репозитория. К тому же все пакеты, скачанные с PyPI, автоматически кэшируются, так что даже если отключить сеть, эти пакеты будут доступны для установки. И, в конце концов, появляется возможность загрузки своих собственных пакетов на локальный сервер, чтобы ссылаться на них так же, как и на хранящиеся в публичном индексе.

Я рекомендую установить DevPI в локальный virtualenv, после чего добавить ссылки на devpi-server и devpi в PATH.
$ virtualenv devpi-venv
$ devpi-venv/bin/pip install --ugprade pip wheel setuptools devpi
$ ln -s `pwd`/devpi-venv/bin/devpi ~/.local/bin
$ ln -s `pwd`/devpi-venv/bin/devpi-server ~/.local/bin

После этого остаётся просто запустить devpi-server, и он будет работать до ручной остановки.
$ devpi-server --start

После запуска его необходимо единожды проинициализировать:
$ devpi use http://localhost:3141
$ devpi user -c $USER password=
$ devpi login $USER --password=
$ devpi index -c yourproject

Так как я использую DevPI 'для себя', имена пользователя DevPI и системного пользователя совпадают. На последнем шаге создаётся индекс по имени проекта (при необходимости можно создать несколько).

Для перенаправления pip на локальный репозиторий можно сделать экспорт переменной окружения:
$ export PIP_INDEX_URL=http://localhost:3141/$USER/yourproject/+simple/

Я размешаю эту команду в скрипт postactivate моего virtualenv для предотвращения случайной загрузки из неверного индекса.

Для размещения собственных wheel’ов в локальном DevPI используется утилита devpi:
$ devpi use yourproject
$ devpi upload --no-vcs --formats=bdist_wheel

Флаг --no-vcs отключает магию, которая пытается определить систему контроля версий и перемещает некоторые файлы в первую очередь. Мне это не нужно, так как в моих проектах распространяются файлы, которые я не включаю в VCS (бинарники, например).

Напоследок я настоятельно рекомендую разбить файлы setup.py таким образом, что PyPI их отвергнет, а DevPI примет, чтобы случайно не зарелизить свой код с помощью setup.py resease. Самый простой способ это сделать – добавить неверный классификатор PyPI:
setup(
    ...
    classifier=['Private :: Do Not Upload'],
)

Заворачиваем


Теперь всё готово для начала использования внутренних зависимостей и сборки собственных 'колёс'. Как только они появятся, их можно заархивировать, загрузить на другой сервер и установить в отдельный virtualenv.
Весь процесс станет чуть проще, когда pip wheel перестанет игнорировать существующие wheel-пакеты. А пока приведенный выше shell-скрипт – не худшее решение.

В сравнении с 'яйцами'


Сейчас wheel-формат более притягателен, чем egg. Его разработка активнее, PyPI начал добавлять его поддержку и, так как с ним начинают работать утилиты, он похож на лучшее решение. 'Яйца' пока что поддерживаются только easy_install, хотя большинство давно перешло на pip.

Я считаю, что сообщество Zope до сих пор крупнейшее из базирующихся на egg-формате и buildout. И я считаю, что если решение на основе 'яиц' в вашем случае применимо, его и стоит применять. Я знаю, что многие не используют eggs вовсе, предпочитая создавать virtualenv-ы, архивировать их и рассылать по разным серверам. Как раз для такого развёртывания wheels – лучшее решение, так как у разных серверов могут быть разные пути к библиотекам. Встречалась проблема, связанная с тем, что .pyc-файлы создавались на билд-сервере для virtualenv, а эти файлы содержат конкретные пути к файлам. С использованием wheel .pyc создаются после установки в виртуальное окружение и автоматически будут иметь корректные пути.

Итак, теперь он у вас есть. Python на колёсах. И это вроде даже работает, и, возможно, стоит потраченного времени.
Перевод: Armin Ronacher
Egor Komissarov @komissarex
карма
50,0
рейтинг 8,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Спецпроект

Самое читаемое Разработка

Комментарии (32)

  • +7
    В питоне есть несколько родовых травм, которые до сих пор не могут преодолеть.

    Первое: строки
    Второе: дистрибуция пакетов
    Третье: медленный старт приложений из-за import'ов всего и вся (речь про сотни милисекунд, и это много для некоторых применений).

    Четвёртая травма не родовая, но уж очень глубокая: это фактически мёртвый python3, и декларативно мёртвый python2.
    • +6
      Зря это ты
      • +2
        Зря что? Правду пишу?

        Остаётся объяснить каждому начинающему на питоне, как сделать print «Привет мир» без странных звёздочек с дефисами, как запакетировать смешанную бинарно-питоновую библиотеку и как запустить приложение на питоне за 1-2 милисекунды, и вопросов к питону больше не будет.
        • +17
          Зря пишешь не по теме. Описан конкретный вопрос, а ты уходишь в демагогию о недостатках языка и платформы в целом. Разжигание холивара это называется. Может, даже неумышленное, но все же
          • –8
            При чём тут холивар? Это реальные проблемы, и если их решить, язык станет лучше. Для меня это второй язык (уже, наверное, даже первый) по частоте использования.

            Это не мешает мне плеваться каждый раз, когда нужно что-то серьёзное собрать на нём.
            • +4
              Это проблемы, не спорю. Но почему бы эти проблемы не обсуждать в посте под названием: " Родовые травмы Питона"? Здесь народ ждёт каких-то важных моментов относительно колёс
              З. Ы: ну как всегда — первая десятка комментов никакого конструктива не содержит
        • 0
          #coding utf8
          
        • +2
          “Странные звёздочки с дефисами” — это, кажется, аналог modelines для какого‐то редактора (emacs?). Можно с тем же успехом использовать ́# vim: fileencoding=utf-8. С python3 не нужно и того (вы либо пишете в UTF-8, либо вас посылают с SyntaxError).

          Хотя «привет мир» без звёздочек с дефисами и вообще без не‐ASCII можно очень легко сделать: просто сразу скажите новичку, как использовать gettext и писать многоязычные приложения :) Разумеется, все сообщения в коде будут на английском.

          Проблемы есть, но они решаются. И их меньше, чем во многих других языках. Есть отличная практика, которая убережёт вас от минусов за разжигание священных войн: как только вам захочется написать подобное не соответствующее теме сообщение (не важно, насколько оно правдиво), возьмите какой‐нибудь shell скрипт длиною хотя бы в десяток строк и перепишите его для работы на tcsh. Уверяю, после данного действия все прочие скриптовые языки станут казаться верхом совершенства.
    • +2
      Первое: строки
      Второе: дистрибуция пакетов
      Третье: медленный старт приложений из-за import'ов всего и вся (речь про сотни милисекунд, и это много для некоторых применений).

      Честно говоря, не ощущаю проблем при работе со строками. И с пакетами беды возникают только под Windows (но это общая проблема OpenSource проектов, когда разработчики не заботятся о поддержке Windows).
      А если вам не нравится медленный старт, то может не стоит скрипт останавливать? И по поводу долгой инициализации, вы ещё не видели, как стартует JBoss сервер, поднимая EJB контейнеры, инициализируя Beans, Hibernate, RESTExpress и всё, всё, всё.

      Четвёртая травма не родовая, но уж очень глубокая: это фактически мёртвый python3, и декларативно мёртвый python2.

      Странно, уже вышел Python 3.4.0 beta 3, а вы говорите, что язык мёртвый.
      • +7
        Недавно был топик, что число используемых в продакшене 3их питонов стремится к нулю, а все сидят на 2.4-2.7, и даже началось шебуршение на тему «сделать 2.8 с фичами из 3 и варнингами про депрекейтеды 2->3». И, вроде, на lwn была статья на тему, как пытаются прикрутить к bytearray оператор % назад, чтобы вернуть обратную совместимость с кодом на питоне.

        Собственно, вот она: lwn.net/SubscriberLink/581475/e17810225a457e9c/
        • +6
          Давайте называть вещи своими именами: мёртвый язык — некогда использовавшийся язык, вышедший из употребления. Потому следует говорить о Python 3, скорее, как о языке не получившем [пока] широкого распространения. Python 2, кстати, тоже ещё долго не станет мёртвым (бьюсь об заклад, что даже после окончания периода поддержки).

          Через этот этап, когда язык не имеет широкого распространения, прошли все языки, так давайте просто посидим тихо и понаблюдаем (кто-то с опаской пассивно, кто-то продвигая и развивая язык), что будет дальше: умрёт он в итоге, или наберёт форму. А просто раскачивать лодку смысла мало — разве только из праздности.

          Впрочем, как бы ни были неудобны слова Гейнора, Ронахера и др. о Py3, всё же они во многом справледливы. В этом их ценность — они являются обратной связью для создателей Py3, качественной обратной связью с указанием на конкретные промахи и даже вариантами выхода из сложившейся ситуации. Увидим, в общем ;)
        • 0
          Сплетни-сплетни-сплетни.
          А практика такова что python 3.3 даёт уже больше плюшек чем проблем.
          • 0
            Я как более сисадмин, чем программист, могу сказать, что python3 я в продакшене просто не вижу. Не используют-с.
            • 0
              у вас нету, у нас есть. Вывод?
              • 0
                Вы про самописный код? Я про то, что в репозиториях. (Для исключения разногласий, уберём библиотеки).
                • 0
                  В Gentoo в репозиториях очень даже есть python3.

                  Точнее все проекты, поддерживающие запуск на python3 можно установить для python3, python2 или (за исключением некоторых) вообще для всех поддерживаемых версий python сразу, включая pypy и jython (правда, поддержку последних часто не указывают, даже если на них всё работает). У меня в /usr/lib/python-exec/python3.3 находятся 55 скриптов. Таже 56 у python3.2, 57 у python2.6 и 59 у python2.7, что говорит о хорошей поддержке третьего python среди используемых мною пакетов (их 19, 20, 21 и 22 соответственно для 3.3, 3.2, 2.6 и 2.7). И всё, что есть в /usr/lib/python-exec, установлено emerge.

                  Правда, я не вижу Gentoo в продакшене. Но в её репозиториях всё есть — факт :)
                  • 0
                    Хе, да в генте у меня python3 это уже дефолт, и emerge с ним. И никаких проблем.
                • 0
                  А какая разница что там в репозиториях? В арче я уверен py3 тоже есть.
                  В убунте репозиторий всегда остаёт от реалий, по понятным причинам.

                  Изначально же я говорил про наш продакшн, ибо думал вы о нём.
  • –9
    Для меня это выглядит слишком мудрено. Мы считаем что единственно правильный
    путь распространения пакетов для Linux это deb/rpm.
    FPM делает создание пакетов тривиальным.
    Мы даже не используем virtualenv — вполне достаточно установить PYTHONPATH.

    • +2
      «мы» это кто?
      • –3
        Мы, dmitriko
      • +1
        Как я понял, идет обсуждение способа распространения Python приложений.
        Я, в составе небольшой группы единомышленников, занимаюсь этим много лет.
        И с высоты нашего опыта использование любых способов кроме
        rpm/deb выглядит крайне недальновидно. По множеству причин.
        Я говорю «мы» потому что сам когда то предлагал использовать eggs, virtualenv, etc.
        Но старшие товарищи поправили ;)
        Ну а wheels _лично_ мне показались излишне сложным решением.
        Остались ли еще не ясные моменты в моих комментариях?
        • 0
          virtualenv удобен, если на одной машине у вас несколько независимых проектов. К примеру это staging сервер со всеми выпущенными проектами. Суть в изоляции пакетов одного проекта от другого.

          setuptools/pkg_resources это не только пакеты egg/wheel, это еще и entry points, и многое-многое другое. Через entry points удобно расширять другие пакеты/приложения (группа console_scripts), публиковать WSGI приложения/фильтры, так продолжать можно долго. За примером можно к Pyramid. Да и публиковать приложение лучше как egg/wheel, а не архивом файлов или целого virtualenv.

          Впрочем, в питоне никто не обязывает следовать одному рецепту, лишь бы выбор не мешал работать.
          • +1
            Согласен практически со всем. В разработке без virtualenv не обойтись.

            Может дело личного вкуса, но для меня Pyramid и entry points
            привносят слишком много магии. И получается что установка и
            настройка OpenStack, который использует Pyramid, превращается
            в нетривиальную задачу.

            Но, наверное, главная причина по которой мы не используем eggs
            заключается в том, что наши приложения часто устанавливают третьи лица,
            которые и знать не хотят что там внутри Python. А так как речь
            идет о множестве машин, то установка и обновление идет через Puppet, Chef
            etc которые работают c RPM из коробки.
  • +5
    Мы даже не используем virtualenv — вполне достаточно установить PYTHONPATH.
    — Можно тогда уж импорты не использовать, а все необходимые объявления копировать в ваш Файл.
    • –3
      Мне не понятен Ваш сарказм. Вот как это работает у нас:
      RPM кладет приложение в /var/lib/appname
      При этом в эту же директорию помещаются наши модули,
      /var/lib/appname/foo, /var/lib/appname/bar,…
      Init script устанавливает переменную окружения PYTHONPATH=/var/lib/appname
      После этого import foo, import bar просто работают.
      И зачем здесь virtualenv?

      • 0
        пока вы не понимаете нафик вам virtualenv — не используйте его. Это ок =)
        я не уверен что от virtualenv много толку в продакшне. Но уверен что в нём дофига толку во время разработки. Имхо.
        • 0
          Может Вы найдете полезным для себя в разработке
          использовать вот это orchardup.github.io/fig/
          • –1
            Я Гентушник, брат.
            В своей работе я использую всёёёё :-D

            Иногда удобен virtualenv, иногда удобен крохотный шелловский самописный скрипт, иногда ты придумываешть нечто своё чуть более громоздкое, чтобы тестовое окружение было ближе к реалиям продакшна…

            Однако у virtualenv отнять не одного — это популярен, а потому я крайне редко натыкаюсь на pip install SOMETHING, при SOMETHING не работающим нормально под virtualenv и это определённо плюс.
  • +2
    >Но если вдруг речь пойдет о Python 2.8
    А такой вариант развития событий в принципе рассматривается?
    • 0
      Хм, в оригинале скорее подразумевалось «если бы вдруг речь пошла про Python 2.8». Поправил.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.