Упаковка Python - прошлое, настоящее, будущее
Last updated
Was this helpful?
Last updated
Was this helpful?
Оригинал статьи Bernat Gabor: от 7-02-2019.
Описывает, где упаковка Python находится сегодня, и куда, как надеется, будет двигаться дальше. В этом посте вы узнаете, как работает команда pip install
и как она изменится с PEP-517/518.
Вы когда-нибудь задумывались, что именно происходит, когда вы запускаете pip install
? В этом посте вы найдете подробный обзор шагов, которые выполнялись в прошлом, и того, как все это изменилось с принятием PEP-517 и PEP-518.
В я описал, как можно установить три типа контента: дерево исходных текстов (source tree), распространение исходного кода (source distribution) и wheels. Только два последних типа загружаются в PyPi, центральный репозиторий Python, однако можно также получить доступ к дереву исходных текстов (например, путем подачи протокола git для pip). Преимущество колес перед другими в том, что для него не требуется выполнение каких-либо операций сборки на пользовательской машине; это просто скачать и распаковать.
Теперь независимо от того, где происходит сборка (пользователь или машина разработчика), вам все равно нужно собрать пакет (либо sdist, либо wheel). Для этого вам понадобится какой-нибудь строитель. Исторически потребность в сторонних пакетах проявлялась рано. Следуя принципу, что в 2000 году в Python 1.6 есть батареи, пакет был добавлен в стандартную библиотеку Python. Он представил концепцию файла setup.py
, содержащего логику сборки, и запускается через python setup.py cmd
.
Он позволял пользователям упаковывать код в виде библиотек, но не имел таких функций, как объявление и автоматическая установка зависимостей. Более того, жизненный цикл его улучшения был напрямую привязан к циклу выпуска ядра интерпретатора. В 2004 году был создан setuptools, который построен на основе distutils и расширен другими полезными функциями. Он быстро стал очень популярным до такой степени, что большинство установок python начали предоставлять его вместе с самим интерпретатором ядра.
В те дни все пакеты были исходными. Дистрибутивы wheel появились намного позже, в 2014 году. Distutils был создан еще тогда, когда только несколько высокопрофессиональных людей занимались упаковкой. Таким образом, он очень гибкий и обязательный, вы пишете скрипт на Python, вы можете изменять каждый шаг в процессе создания пакета.
Обратной стороной этого является то, что его нелегко выучить и понять. Это стало становиться все более и более серьезной проблемой, поскольку популярность Python росла, и у нас было все больше и больше пользователей, менее разбирающихся в внутренней работе Python.
Для установки исходного дистрибутива pip в основном делали следующее:
открыть пакет
скачать исходный дистрибутив и распаковать его
запустите python setup.py install
в извлеченную папку (выполняет сборку + установку).
Разработчики использовали python setup.py sdist
для создания дистрибутива и python setup.py upload
для его загрузки в центральный репозиторий (команда загрузки устарела с 2013 года в пользу инструмента , в первую очередь из-за загрузки с использованием небезопасных HTTP-соединение и команда загрузки также выполнили новую сборку, не позволяя конечному пользователю проверять сгенерированный пакет перед фактической загрузкой).
Когда pip запускал python setup.py install
, он делал это с интерпретатором python, для которого он устанавливал пакет. Таким образом, операция сборки имела доступ ко всем сторонним пакетам, уже доступным внутри этого интерпретатора. В частности, он использовал именно ту версию setuptools, которая была установлена на интерпретаторе python хоста. Если в пакете использовалась функция setuptools, доступная в более новой версии, чем установленная в настоящее время, единственный способ завершить установку - сначала обновить установленные инструменты установки.
Это потенциально может вызвать проблемы, если новый выпуск содержит ошибку, нарушающую работу других пакетов. Это особенно проблематично в системах, где пользователи не могут изменять установленные пакеты. Затем возникла проблема того, что происходит, когда сборщик (например, setuptools) хочет использовать другие вспомогательные пакеты, такие как cython.
Если какой-либо из этих помощников отсутствовал, сборка обычно просто прерывалась из-за ошибки неудачного импорта пакета:
Идея состоит в том, что вместо использования хоста python с установленными в данный момент пакетами для сборки, пакет предоставляет возможность явно указывать, что им нужно для их операции сборки. Кроме того, вместо того, чтобы сделать это доступным на хосте python, мы создаем изолированный python (представьте себе своего рода виртуальную среду) для запуска упаковки.
python setup.py install
теперь становится:
создать временную папку
создать изолированную (из пакетов сторонних сайтов) среду Python python -m virtualenv our_build_env
, давайте назовем этот исполняемый файл python python_isolated
установить зависимости сборки
создать wheel, которое мы можем установить через python_isolated setup.py bdist_wheel
извлеките wheel на сайт пакетов Python.
Благодаря этому мы можем устанавливать пакеты, которые зависят от cython, без фактической установки cython внутри среды выполнения python. Файл и метод указания зависимостей сборки - это файл метаданных pyproject.toml
:
Кроме того, это также позволяет тому, кто занимается упаковкой, четко указать, какие минимальные версии требуются для упаковки, и они могут быть легко предоставлены через pip прозрачно на пользовательских машинах.
Тот же механизм можно использовать при генерации дистрибутива исходного кода или wheel на машине разработчиков. Когда кто-то вызывает pip wheel . --no-deps
команда, которая автоматически создаст в фоновом режиме изолированный python, удовлетворяющий зависимостям систем сборки, а затем вызовет внутри этой среды команду python setup.py bdist_wheel
или python setup.py sdist
.
Но здесь есть еще одна проблема. Обратите внимание, что все эти операции по-прежнему выполняются через механизм, представленный двадцать лет назад, также известный как setup.py
. Вся экосистема по-прежнему строится на основе интерфейса distutils и setuptools, который не может сильно измениться из-за попытки сохранить обратную совместимость.
Кроме того, выполнение произвольного кода Python на стороне пользователя во время упаковки опасно, поскольку может привести к незначительным ошибкам, которые трудно отладить менее опытным пользователям. Системы императивной сборки были великолепны для гибкости двадцать лет назад, когда мы еще не знали обо всех вариантах использования, но теперь, когда у нас есть хорошее понимание, мы, вероятно, сможем создать очень надежные и простые компоновщики пакетов для различных вариантов использования.
В идеале вариантом по умолчанию была бы декларативная конфигурация сборки, которая хорошо работает в случае 99%, с возможностью вернуться к императивной системе, когда вам действительно нужна гибкость. На этом этапе для нас вполне реально перейти в мир, где это считается запахом кода, если вы обнаружите, что вам нужно обратиться к императивным параметрам сборки.
Самая большая проблема с
setup.py
заключается в том, что большинство людей используют его декларативно, а когда они используют его императивно, они склонны вносить ошибки в систему сборки. Один пример: если у вас есть зависимость только для Python 2.7, у вас может возникнуть соблазн указать ее условно с помощьюsys.version
в вашемsetup.py
, ноsys.version
относится только к интерпретатору, который выполнил сборку; вместо этого вы должны использовать декларативные маркеры среды для ваших требований к установке.
Приведенный выше код эффективно означает для внешнего интерфейса, что вы можете получить его, запустив указанный выше код внутри изолированной среды Python:
Это зависит от серверной части, где и как они хотят предоставить свой официальный API:
Благодаря этому мы можем начать иметь инструменты упаковки, которые больше не привязаны к устаревшим решениям distutils во внешнем интерфейсе.
Однако, чтобы иметь возможность протестировать пакет, сначала необходимо создать исходный код. Хотя и PEP-518, и PEP-517 должны улучшить ситуацию, их включение может повредить упаковку в некоторых случаях использования. Поэтому, когда tox добавил изолированную сборку в версию 3.3.0
, решил пока не включать ее по умолчанию. Вам нужно включить его вручную (вероятно, он будет включен по умолчанию в версии 4
позже в этом году - 2019).
После того, как вы указали pyproject.toml
с соответствующими requires и build-backend, вам нужно включить флаг isolated_build внутри tox.ini
:
Администрация Python Packaging Authority надеется, что все это имеет смысл и позволит создавать более удобные, устойчивые к ошибкам и стабильные сборки. Спецификации для этих стандартов были написаны и обсуждены в длинных цепочках в период с 2015 по 2017 годы. Предложения были сочтены достаточно хорошими, чтобы принести наибольшую пользу, но некоторые менее распространенные варианты использования могли быть упущены.
У разработчиков не было возможности предоставить такие зависимости сборки. Это также означало, что пользователям необходимо было установить все зависимости сборки пакетов, даже если они не хотели использовать это во время выполнения. Для решения этой проблемы был создан .
Процитирую (сопровождающего setuptools и dateutil по этому поводу):
подтвердил правильность этого предположения, представив его в 2015 году. Он стал любимым упаковочным инструментом для многих новичков в Python, так как позволяет новым пользователям избегать большого количества этих ножных ружей. Однако, чтобы добраться до этого момента, flit пришлось снова создавать поверх distutils/setuptools
, что делает его реализацию нетривиальной, а в базе кода довольно много слоев прокладки (он по-прежнему генерирует файл setup.py
, например, для своего источника раздачи).
Пришло время освободить его от этих оков, а также побудить других людей создавать свои собственные инструменты упаковки, которые упрощают упаковку для их вариантов использования. Пора сделать setup.py
исключением, а не значением по умолчанию. setuptools пользовательский интерфейс только setup.cfg
, чтобы указать путь, и когда система PEP-517 уже установлена, вы должны предпочесть ее использованию setup.py
в большинстве случаев.
Чтобы не связывать все с инструментами настройки и distutils и облегчить создание новых серверных модулей сборки, был создан . Он разделяет сборщиков на серверную часть и фронтенд. Интерфейс предоставляет изолированную среду Python, удовлетворяющую всем заявленным зависимостям сборки; бэкэнд предоставляет хуки, которые интерфейс может вызывать из своей изолированной среды для генерации исходного дистрибутива (source distribution) или wheel.
Кроме того, вместо того, чтобы разговаривать с серверной частью через файл setup.py
и его команды, мы переходим к модулям и функциям python. Все бэкэнды упаковки должны предоставлять API-интерфейс объекта Python, реализующий как минимум два метода и . Точка объекта API указывается в файле pyproject.toml
под ключом build-backend:
делает это через flit.buildapi
предоставляет два варианта: setuptools.build_meta
(о том, зачем читать дальше)
делает это с помощью poetry.masonry.api
- это инструмент тестирования, который используется в большинстве проектов для обеспечения совместимости с несколькими версиями интерпретатора Python данного пакета. Это также позволяет легко создавать среды python, в которых установлен проверяемый пакет, что ускоряет воспроизведение сбоев.
После этого tox на соберет исходный дистрибутив, предоставив зависимости сборки в изолированную среду Python для PEP-518, и вызовет бэкэнд сборки, как указано в PEP-517. В противном случае tox будет использовать старый способ создания исходных дистрибутивов, то есть вызов команды python setup.py sdist
с тем же интерпретатором, в который установлен tox.
Если ваш вариант использования таков, не волнуйтесь, PEP могут быть улучшены в любой момент, если мы сочтем это необходимым. В я расскажу о некоторых болевых точках, с которыми столкнулось сообщество при выпуске этих двух PEP. Они должны послужить извлеченными уроками и показать, что еще предстоит проделать некоторую работу. Еще не все идеально, но мы поправляемся. Присоединяйтесь к сообществу разработчиков упаковки, если можете помочь, и давайте вместе сделаем все лучше!