Pipenv: Руководство по средству упаковки Python

Оглавление

Pipenv - это инструмент упаковки для Python, который решает некоторые общие проблемы, связанные с типичным рабочим процессом с использованием pip, virtualenv и старого доброго requirements.txt.

Помимо решения некоторых общих проблем, он консолидирует и упрощает процесс разработки до единого инструмента командной строки.

В этом руководстве мы рассмотрим, какие проблемы решает Pipenv и как управлять зависимостями Python с помощью Pipenv. Кроме того, будет рассказано о том, как Pipenv сочетается с предыдущими методами распределения пакетов.

Проблемы, которые решает Pipenv

Чтобы понять преимущества Pipenv, важно пройтись по существующим методам упаковки и управления зависимостями в Python.

Начнем с типичной ситуации работы с пакетами сторонних разработчиков. Затем мы пройдем путь к развертыванию полноценного Python-приложения.

Управление зависимостями с помощью requirements.txt

Представьте, что вы работаете над проектом Python, в котором используется сторонний пакет, например flask. Вам нужно указать это требование, чтобы другие разработчики и автоматизированные системы могли запускать ваше приложение.

Итак, вы решили включить flask зависимость в requirements.txt файл:

flask

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

В приведенном выше файле requirements.txt не указано, какую версию flask следует использовать. В этом случае pip install -r requirements.txt по умолчанию установит последнюю версию. Это нормально, если только в новейшей версии нет изменений интерфейса или поведения, которые сломают наше приложение.

Для примера предположим, что вышла новая версия flask. Однако она не имеет обратной совместимости с версией, которую вы использовали во время разработки.

Теперь, допустим, вы развертываете свое приложение на производстве и выполняете pip install -r requirements.txt. Pip получает последнюю, не совместимую с обратным ходом версию flask, и точно так же ваше приложение ломается... в производстве.

"Но, эй, это работало на моей машине!" Я сам был там, и это не самое приятное чувство.

На данный момент вы знаете, что версия flask, которую вы использовали во время разработки, работала нормально. Поэтому, чтобы исправить ситуацию, вы пытаетесь быть немного более конкретным в своем requirements.txt. Вы добавляете спецификатор версии в зависимость flask. Это также называется пиннингом зависимости:

flask==0.12.1

Прикрепление зависимости flask к определенной версии гарантирует, что pip install -r requirements.txt установит именно ту версию flask, которую вы использовали во время разработки. Но так ли это на самом деле?

Помните, что сам flask также имеет зависимости (которые pip устанавливает автоматически). Однако сам flask не указывает точные версии для своих зависимостей. Например, он допускает любую версию Werkzeug>=0.14.

Опять же, для примера, предположим, что вышла новая версия Werkzeug, но она вносит в ваше приложение ошибку, которая останавливает его работу.

Когда вы сделаете pip install -r requirements.txt в производстве в этот раз, вы получите flask==0.12.1, поскольку вы закрепили это требование. Однако, к сожалению, вы получите последнюю, глючную версию Werkzeug. И снова продукт ломается в производстве.

Настоящая проблема здесь в том, что сборка не является детерминированной. Я имею в виду, что при одинаковых входных данных (файл requirements.txt) pip не всегда создает одно и то же окружение. В настоящее время вы не можете легко воспроизвести точное окружение, которое у вас есть на машине разработки, в производстве.

Типичным решением этой проблемы является использование pip freeze. Эта команда позволяет получить точные версии всех сторонних библиотек, установленных в данный момент, включая подзависимости, которые pip установил автоматически. Таким образом, вы можете заморозить все в разработке, чтобы убедиться, что в продакшене у вас такое же окружение.

В результате выполнения pip freeze появляются прикрепленные зависимости, которые можно добавить в requirements.txt:

click==6.7
Flask==0.12.1
itsdangerous==0.24
Jinja2==2.10
MarkupSafe==1.0
Werkzeug==0.14.1

С помощью этих прикрепленных зависимостей вы можете гарантировать, что пакеты, установленные в вашей производственной среде, будут точно соответствовать пакетам в среде разработки, чтобы ваш продукт неожиданно не сломался. Это "решение", к сожалению, приводит к целому ряду новых проблем.

Теперь, когда вы указали точные версии каждого стороннего пакета, вы несете ответственность за поддержание этих версий в актуальном состоянии, даже если они являются подзависимостями flask. Что, если в Werkzeug==0.14.1 обнаружена дыра в безопасности, которую сопровождающие пакета немедленно залатали в Werkzeug==0.14.2? Вам действительно нужно обновиться до Werkzeug==0.14.2, чтобы избежать проблем с безопасностью, возникающих из-за более ранней, непропатченной версии Werkzeug.

Во-первых, вы должны знать, что существует проблема с версией, которая у вас есть. Затем необходимо внедрить новую версию в производственную среду до того, как кто-то воспользуется брешью в безопасности. Поэтому вам придется вручную изменить requirements.txt, чтобы указать новую версию Werkzeug==0.14.2. Как видите, в этой ситуации ответственность за своевременное получение необходимых обновлений ложится на вас.

Правда в том, что вам совершенно все равно, какая версия Werkzeug будет установлена, лишь бы она не ломала ваш код. На самом деле вам, скорее всего, нужна последняя версия, чтобы убедиться, что вы получаете исправления ошибок, исправления безопасности, новые возможности, большую оптимизацию и так далее.

Настоящий вопрос заключается в следующем: "Как обеспечить детерминированные сборки для вашего Python-проекта, не возлагая на себя ответственность за обновление версий подзависимостей?"

Предупреждение под спойлером: простой ответ - использование Pipenv.

Разработка проектов с различными зависимостями

Давайте немного переключимся и поговорим о другой распространенной проблеме, которая возникает, когда вы работаете над несколькими проектами. Представьте, что ProjectA нуждается в django==1.9, а ProjectB нуждается в django==1.10.

По умолчанию Python пытается хранить все ваши сторонние пакеты в общесистемном месте. Это означает, что каждый раз, когда вы хотите переключиться между ProjectA и ProjectB, вы должны убедиться, что установлена нужная версия django. Это делает переключение между проектами болезненным, поскольку вам придется удалять и переустанавливать пакеты, чтобы удовлетворить требования каждого проекта.

Стандартным решением является использование виртуальной среды, которая имеет собственный исполняемый файл Python и хранилище пакетов сторонних разработчиков. Таким образом, ProjectA и ProjectB адекватно разделяются. Теперь вы можете легко переключаться между проектами, поскольку они не используют одно и то же место хранения пакетов. PackageA может иметь любую версию django, которая ему нужна, в своем собственном окружении, а PackageB может иметь то, что ему нужно, совершенно отдельно. Очень распространенным инструментом для этого является virtualenv (или venv в Python 3).

В Pipenv встроено управление виртуальным окружением, так что у вас будет единый инструмент для управления пакетами.

Разрешение зависимостей

Что я имею в виду под разрешением зависимостей? Допустим, у вас есть файл requirements.txt, который выглядит примерно так:

package_a
package_b

Допустим, package_a имеет подзависимость package_c, и package_a требует определенной версии этого пакета: package_c>=1.0. В свою очередь, package_b имеет такую же подзависимость, но требует package_c<=2.0.

Идеально, когда вы пытаетесь установить package_a и package_b, инструмент установки смотрит на требования для package_c (являющиеся >=1.0 и <=2.0) и выбирает версию, удовлетворяющую этим требованиям. Вы надеетесь, что инструмент разрешит зависимости так, что ваша программа в итоге будет работать. Это то, что я подразумеваю под "разрешением зависимостей"

К сожалению, сам pip на данный момент не имеет реального разрешения зависимостей, но есть открытый вопрос для его поддержки.

В случае с вышеописанным сценарием pip поступил бы следующим образом:

  1. Он устанавливает package_a и ищет версию package_c, которая удовлетворяет первому требованию (package_c>=1.0).

  2. Pip затем устанавливает последнюю версию package_c, чтобы выполнить это требование. Допустим, последняя версия package_c - 3.1.

Здесь-то и начинаются проблемы (потенциально).

Если версия package_c, выбранная pip, не соответствует будущим требованиям (например, package_b нуждается в package_c<=2.0), установка будет неудачной.

"Решение" этой проблемы заключается в том, чтобы указать диапазон, необходимый для подзависимости (package_c), в файле requirements.txt. Таким образом, pip сможет разрешить этот конфликт и установить пакет, отвечающий этим требованиям:

package_c>=1.0,<=2.0
package_a
package_b

Как и раньше, теперь вы напрямую обращаетесь к подзависимостям (package_c). Проблема заключается в том, что если package_a изменит свои требования без вашего ведома, то указанные вами требования (package_c>=1.0,<=2.0) могут оказаться неприемлемыми, и установка может оказаться неудачной... снова. Настоящая проблема заключается в том, что вы снова несете ответственность за то, чтобы быть в курсе требований к подзависимостям.

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

Pipenv — Введение

Теперь, когда мы рассмотрели проблемы, давайте посмотрим, как Pipenv их решает.

Сначала установим его:

$ pip install pipenv

После этого вы можете забыть о pip, поскольку Pipenv, по сути, выступает в качестве замены. Он также представляет два новых файла, Pipfile (который призван заменить requirements.txt) и Pipfile.lock (который обеспечивает детерминированные сборки).

Pipenv использует pip и virtualenv под капотом, но упрощает их использование с помощью единого интерфейса командной строки.

Пример использования

Давайте начнем с создания вашего потрясающего приложения на Python. Сначала создайте оболочку в виртуальной среде, чтобы изолировать разработку этого приложения:

$ pipenv shell

Это создаст виртуальную среду, если таковой еще не существует. Pipenv создает все ваши виртуальные среды в месте по умолчанию. Если вы хотите изменить поведение Pipenv по умолчанию, есть несколько переменных окружения для настройки.

Вы можете принудительно создать окружение Python 2 или 3 с помощью аргументов --two и --three соответственно. В противном случае Pipenv будет использовать любое окружение по умолчанию, которое найдет virtualenv.

Примечание: Если вам нужна более конкретная версия Python, вы можете указать в аргументе --python требуемую версию. Например: --python 3.6

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

$ pipenv install flask==0.12.1

В вашем терминале должно появиться что-то вроде следующего:

Adding flask==0.12.1 to Pipfile's [packages]...
Pipfile.lock not found, creating...

Вы заметите, что создаются два файла, Pipfile и Pipfile.lock. Мы рассмотрим их подробнее через секунду. Давайте установим еще один пакет стороннего производителя, numpy, для подсчета чисел. Вам не нужна конкретная версия, поэтому не указывайте ее:

$ pipenv install numpy

Если вы хотите установить что-то непосредственно из системы управления версиями (VCS), вы можете это сделать! Вы указываете местоположение аналогично тому, как это делается при использовании pip. Например, чтобы установить библиотеку requests из системы контроля версий, сделайте следующее:

$ pipenv install -e git+https://github.com/requests/requests.git#egg=requests

Обратите внимание на аргумент -e выше, чтобы сделать установку редактируемой. В настоящее время это необходимо для того, чтобы Pipenv выполнял разрешение подзависимостей.

Допустим, у вас также есть несколько модульных тестов для этого замечательного приложения, и вы хотите использовать pytest для их запуска. Вам не нужно pytest в производстве, поэтому вы можете указать, что эта зависимость предназначена только для разработки, с помощью аргумента --dev:

$ pipenv install pytest --dev

Предоставление аргумента --dev поместит зависимость в специальное [dev-packages] место в Pipfile. Эти пакеты разработки будут установлены только в том случае, если вы укажете аргумент --dev вместе с pipenv install.

В разных секциях отделены зависимости, необходимые только для разработки, от зависимостей, необходимых для реальной работы базового кода. Обычно для этого используются дополнительные файлы требований, такие как dev-requirements.txt или test-requirements.txt. Теперь же все сведено в один Pipfile под разными секциями.

Итак, предположим, что у вас все работает в локальной среде разработки, и вы готовы перевести ее в продакшн. Для этого вам нужно заблокировать среду, чтобы убедиться, что в продакшене она такая же:

$ pipenv lock

Это создаст/обновит ваш Pipfile.lock, который вам никогда не понадобится (и не предполагается) редактировать вручную. Вы всегда должны использовать сгенерированный файл.

Теперь, как только вы получите свой код и Pipfile.lock в производственной среде, вы должны установить последнюю успешную запись среды:

$ pipenv install --ignore-pipfile

Это указывает Pipenv игнорировать Pipfile для установки и использовать то, что находится в Pipfile.lock. Учитывая это Pipfile.lock, Pipenv создаст точно такое же окружение, какое было при запуске pipenv lock, с подзависимостями и всем остальным.

Файл блокировки позволяет выполнять детерминированные сборки, делая снимок всех версий пакетов в окружении (аналогично результату pip freeze).

Теперь предположим, что другой разработчик хочет внести некоторые дополнения в ваш код. В этой ситуации они получат код, включая Pipfile, и воспользуются следующей командой:

$ pipenv install --dev

Здесь устанавливаются все зависимости, необходимые для разработки, включая как обычные зависимости, так и те, которые вы указали с помощью аргумента --dev во время install.

Когда точная версия не указана в Pipfile, команда install дает возможность зависимостям (и подзависимостям) обновить свои версии.

Это важное замечание, потому что оно решает некоторые из предыдущих проблем, которые мы обсуждали. Чтобы продемонстрировать это, предположим, что стала доступна новая версия одной из ваших зависимостей. Поскольку вам не нужна конкретная версия этой зависимости, вы не указываете точную версию в Pipfile. Когда вы pipenv install, новая версия зависимости будет установлена в вашу среду разработки.

Теперь внесите изменения в код и проведите несколько тестов, чтобы убедиться, что все работает так, как ожидалось. (У вас ведь есть модульные тесты, верно?) Теперь, как и раньше, вы блокируете свое окружение с помощью pipenv lock, и будет сгенерировано обновленное Pipfile.lock с новой версией зависимости. Как и раньше, вы можете реплицировать это новое окружение в производстве с помощью файла блокировки.

Как видно из этого сценария, вам больше не придется принудительно устанавливать точные версии, которые вам действительно не нужны, чтобы обеспечить одинаковые условия для разработки и производства. Вам также не нужно следить за обновлением подзависимостей, которые вас "не волнуют". Этот рабочий процесс с Pipenv, в сочетании с вашим отличным тестированием, устраняет проблемы, связанные с ручным управлением зависимостями.

Подход Pipenv к разрешению зависимостей

Pipenv попытается установить подзависимости, которые удовлетворяют всем требованиям ваших основных зависимостей. Однако, если есть конфликтующие зависимости (package_a требует package_c>=1.0, а package_b требует package_c<1.0), Pipenv не сможет создать файл блокировки и выдаст ошибку, подобную следующей:

Warning: Your dependencies could not be resolved. You likely have a mismatch in your sub-dependencies.
  You can use $ pipenv install --skip-lock to bypass this mechanism, then run $ pipenv graph to inspect the situation.
Could not find a version that matches package_c>=1.0,package_c<1.0

Как говорится в предупреждении, вы также можете показать график зависимостей, чтобы понять ваши зависимости верхнего уровня и их подзависимости:

$ pipenv graph

Эта команда выведет древовидную структуру, показывающую ваши зависимости. Вот пример:

Flask==0.12.1
  - click [required: >=2.0, installed: 6.7]
  - itsdangerous [required: >=0.21, installed: 0.24]
  - Jinja2 [required: >=2.4, installed: 2.10]
    - MarkupSafe [required: >=0.23, installed: 1.0]
  - Werkzeug [required: >=0.7, installed: 0.14.1]
numpy==1.14.1
pytest==3.4.1
  - attrs [required: >=17.2.0, installed: 17.4.0]
  - funcsigs [required: Any, installed: 1.0.2]
  - pluggy [required: <0.7,>=0.5, installed: 0.6.0]
  - py [required: >=1.5.0, installed: 1.5.2]
  - setuptools [required: Any, installed: 38.5.1]
  - six [required: >=1.10.0, installed: 1.11.0]
requests==2.18.4
  - certifi [required: >=2017.4.17, installed: 2018.1.18]
  - chardet [required: >=3.0.2,<3.1.0, installed: 3.0.4]
  - idna [required: >=2.5,<2.7, installed: 2.6]
  - urllib3 [required: <1.23,>=1.21.1, installed: 1.22]

Из вывода pipenv graph можно увидеть зависимости верхнего уровня, которые мы установили ранее (Flask, numpy, pytest и requests), а под ними - пакеты, от которых они зависят.

Дополнительно вы можете развернуть дерево, чтобы показать подзависимости с родительской зависимостью, которая их требует:

$ pipenv graph --reverse

Это обратное дерево может быть более полезным, когда вы пытаетесь разобраться в конфликтующих подзависимостях.

The Pipfile

Pipfile намерен заменить requirements.txt. В настоящее время Pipenv является эталонной реализацией для использования Pipfile. Кажется весьма вероятным, что pip сам сможет обрабатывать эти файлы. Также стоит отметить, что Pipenv даже является официальным инструментом управления пакетами, рекомендованным самим Python.

Синтаксис для Pipfile - TOML, а сам файл разделен на секции. [dev-packages] для пакетов, предназначенных только для разработки, [packages] для минимально необходимых пакетов, и [requires] для других требований, например, для конкретной версии Python. Смотрите пример файла ниже:

[[source]]
url = "https://pypi.python.org/simple"
verify_ssl = true
name = "pypi"

[dev-packages]
pytest = "*"

[packages]
flask = "==0.12.1"
numpy = "*"
requests = {git = "https://github.com/requests/requests.git", editable = true}

[requires]
python_version = "3.6"

В идеале, у вас не должно быть никаких подзависимостей в вашем Pipfile. Я имею в виду, что вы должны включать только те пакеты, которые вы действительно импортируете и используете. Нет необходимости держать chardet в вашем Pipfile только потому, что он является подзависимостью requests. (Pipenv установит его автоматически). Pipfile должен передавать зависимости верхнего уровня, которые требует ваш пакет.

Файл Pipfile.lock

Этот файл позволяет выполнять детерминированные сборки, задавая точные требования для воспроизведения окружения. Он содержит точные версии пакетов и хэшей для поддержки более безопасной проверки, которую теперь поддерживает и сам pip. Пример файла может выглядеть следующим образом. Обратите внимание, что синтаксис этого файла - JSON и что я исключил части файла с помощью ...:

{
    "_meta": {
        ...
    },
    "default": {
        "flask": {
            "hashes": [
                "sha256:6c3130c8927109a08225993e4e503de4ac4f2678678ae211b33b519c622a7242",
                "sha256:9dce4b6bfbb5b062181d3f7da8f727ff70c1156cbb4024351eafd426deb5fb88"
            ],
            "version": "==0.12.1"
        },
        "requests": {
            "editable": true,
            "git": "https://github.com/requests/requests.git",
            "ref": "4ea09e49f7d518d365e7c6f7ff6ed9ca70d6ec2e"
        },
        "werkzeug": {
            "hashes": [
                "sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b",
                "sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c"
            ],
            "version": "==0.14.1"
        }
        ...
    },
    "develop": {
        "pytest": {
            "hashes": [
                "sha256:8970e25181e15ab14ae895599a0a0e0ade7d1f1c4c8ca1072ce16f25526a184d",
                "sha256:9ddcb879c8cc859d2540204b5399011f842e5e8823674bf429f70ada281b3cc6"
            ],
            "version": "==3.4.1"
        },
        ...
    }
}

Обратите внимание на точную версию, указанную для каждой зависимости. Даже такие подзависимости, как werkzeug, которых нет в нашей Pipfile, появляются в этой Pipfile.lock. Хеши используются для того, чтобы убедиться, что вы получаете тот же пакет, что и в разработке.

Стоит еще раз отметить, что вы никогда не должны изменять этот файл вручную. Он должен быть сгенерирован с помощью pipenv lock.

Дополнительные возможности Pipenv

Откройте пакет стороннего разработчика в стандартном редакторе следующей командой:

$ pipenv open flask

Это откроет пакет flask в редакторе по умолчанию, или вы можете указать программу с помощью переменной окружения EDITOR. Например, я использую Sublime Text, поэтому я просто задаю EDITOR=subl. Это позволяет очень просто разобраться во внутреннем устройстве пакета, который вы используете.


Вы можете выполнить команду в виртуальной среде, не запуская оболочку:

$ pipenv run <insert command here>

Проверьте наличие уязвимостей безопасности (и требований PEP 508) в вашей среде:

$ pipenv check

Теперь, допустим, пакет вам больше не нужен. Вы можете удалить его:

$ pipenv uninstall numpy

Кроме того, допустим, вы хотите полностью стереть все установленные пакеты из вашей виртуальной среды:

$ pipenv uninstall --all

Вы можете заменить --all на --all-dev, чтобы просто удалить dev-пакеты.


Pipenv поддерживает автоматическую загрузку переменных окружения, когда .env файл существует в каталоге верхнего уровня. Таким образом, когда вы pipenv shell открываете виртуальную среду, она загружает ваши переменные окружения из этого файла. Файл .env просто содержит пары ключ-значение:

SOME_ENV_CONFIG=some_value
SOME_OTHER_ENV_CONFIG=some_other_value

Наконец, вот несколько быстрых команд, чтобы узнать, где что находится. Как узнать, где находится ваше виртуальное окружение:

$ pipenv --venv

Как узнать, где находится дом вашего проекта:

$ pipenv --where

Распространение пакетов

Вы можете спросить, как все это работает, если вы собираетесь распространять свой код в виде пакета.

Да, мне нужно распространять свой код в виде пакета

Как Pipenv работает с setup.py файлами?

В этом вопросе есть много нюансов. Во-первых, файл setup.py необходим, если вы используете setuptools в качестве системы сборки/распределения. Это было стандартом де-факто в течение некоторого времени, но недавние изменения сделали использование setuptools необязательным.

Это означает, что такие проекты, как flit, могут использовать новую pyproject.toml для указания другой системы сборки, которая не требует setup.py.

При всем при этом в ближайшем будущем setuptools и сопутствующий ему setup.py все еще будут выбором по умолчанию для многих людей.

Вот рекомендуемая схема работы, когда вы используете setup.py в качестве способа распространения вашего пакета:

  • setup.py
  • install_requires ключевое слово должно включать все, что пакету "минимально необходимо для корректной работы".
  • Pipfile
  • Представляет собой конкретные требования к вашему пакету
  • Вытащите минимально необходимые зависимости из setup.py при установке вашего пакета с помощью Pipenv:
    • Используйте pipenv install '-e .'
    • В результате в вашем Pipfile появится строка, которая будет выглядеть примерно так "e1839a8" = {path = ".", editable = true}.
  • Pipfile.lock
  • Детали для воспроизводимой среды, созданной из pipenv lock

Чтобы уточнить, поместите свои минимальные требования в setup.py, а не прямо в pipenv install. Затем используйте команду pipenv install '-e .' для установки вашего пакета как редактируемого. В результате все требования из setup.py попадут в ваше окружение. Затем вы можете использовать pipenv lock для получения воспроизводимого окружения.

Мне не нужно распространять свой код в виде пакета

Отлично! Если вы разрабатываете приложение, которое не предназначено для распространения или установки (персональный сайт, настольное приложение, игра или что-то подобное), вам не нужны setup.py.

В этой ситуации вы можете использовать комбинацию Pipfile/Pipfile.lock для управления зависимостями и описанный ранее поток для развертывания воспроизводимой среды в производстве.

У меня уже есть requirements.txt. Как мне преобразовать его в Pipfile?

Если вы запустите pipenv install, он должен автоматически обнаружить requirements.txt и преобразовать его в Pipfile, выдав что-то вроде следующего:

requirements.txt found, instead of Pipfile! Converting…
Warning: Your Pipfile now contains pinned versions, if your requirements.txt did.
We recommend updating your Pipfile to specify the "*" version, instead.

Примите к сведению вышеуказанное предупреждение.

Если вы закрепили точные версии в файле requirements.txt, вам, вероятно, захочется изменить свой Pipfile, чтобы указывать только те версии, которые вам действительно нужны. Это позволит вам получить реальные преимущества перехода. Например, допустим, у вас есть следующее, но вам не нужна именно эта версия numpy:

[packages]
numpy = "==1.14.1"

Если у вас нет конкретных требований к версии зависимостей, вы можете использовать символ подстановки *, чтобы сообщить Pipenv, что может быть установлена любая версия:

[packages]
numpy = "*"

Если вы боитесь разрешить любую версию с помощью *, обычно лучше указать версию, большую или равную той, на которой вы уже находитесь, чтобы вы могли использовать преимущества новых версий:

[packages]
numpy = ">=1.14.1"

Конечно, оставаться в курсе новых релизов также означает, что вы несете ответственность за то, чтобы ваш код продолжал работать так, как ожидается, при изменении пакетов. Это означает, что набор тестов необходим во всем этом потоке Pipenv, если вы хотите обеспечить работоспособность релизов вашего кода.

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

Например, если numpy==1.15 устанавливается после выполнения pipenv install и ломает ваш код, что вы, надеюсь, заметите либо во время разработки, либо во время тестирования, у вас есть несколько вариантов:

  1. Обновите свой код, чтобы он функционировал с новой версией зависимости.

    Если обратная совместимость с предыдущими версиями зависимостей невозможна, вам также нужно будет увеличить требуемую версию в вашем Pipfile:

    [packages]
    numpy = ">=1.15"
    
  2. Ограничьте версию зависимости в Pipfile, чтобы она была < версией, которая только что сломала ваш код:

    [packages]
    numpy = ">=1.14.1,<1.15"
    

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


Вы также можете установить из файлов требований с тем же -r аргументом pip принимает:

$ pipenv install -r requirements.txt

Если у вас есть dev-requirements.txt или что-то подобное, вы можете добавить их к Pipfile также. Просто добавьте аргумент --dev, чтобы он был помещен в нужную секцию:

$ pipenv install -r dev-requirements.txt --dev

Дополнительно можно пойти другим путем и сгенерировать файлы требований из Pipfile:

$ pipenv lock -r > requirements.txt
$ pipenv lock -r -d > dev-requirements.txt

Что дальше?

Мне кажется, что естественным развитием экосистемы Python будет система сборки, которая использует Pipfile для установки минимально необходимых зависимостей при получении и сборке пакета из индекса пакетов (например, PyPI). Важно еще раз отметить, что спецификация дизайна Pipfile все еще находится в разработке, а Pipenv является лишь эталонной реализацией.

При этом я могу увидеть будущее, в котором раздел install_requires в setup.py не будет существовать, а вместо него будет использоваться раздел Pipfile для минимальных требований. Или же setup.py полностью исчезнет, и вы будете получать метаданные и другую информацию другим способом, по-прежнему используя Pipfile для получения необходимых зависимостей.

Стоит ли обратить внимание на Pipenv?

Неопределенно. Даже если это просто способ объединить уже используемые инструменты (pip & virtualenv) в единый интерфейс. Однако это гораздо больше. С добавлением Pipfile вы указываете только те зависимости, которые вам действительно нужны.

У вас больше не будет головной боли, связанной с самостоятельным управлением версиями всего, чтобы обеспечить копирование среды разработки. С Pipfile.lock вы можете спокойно разрабатывать, зная, что сможете точно воспроизвести свою среду в любом месте.

Кроме того, очень вероятно, что формат Pipfile будет принят и поддерживаться официальными инструментами Python, такими как pip, поэтому было бы полезно быть впереди. О, и убедитесь, что вы также обновляете весь свой код до Python 3: 2020 быстро приближается.

Ссылки, дальнейшее чтение, интересные обсуждения и прочее

Вернуться на верх