Pipfile.lock → requirements.txt

Суть

Для requirements.txt:

1
2
3
4
jq -r '.default
        | to_entries[]
        | .key + .value.version' \
    Pipfile.lock > requirements.txt

Для requirements-dev.txt:

1
2
3
4
jq -r '.develop
        | to_entries[]
        | .key + .value.version' \
    Pipfile.lock > requirements-dev.txt

Пояснение

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

1
pip install -r requirements.txt

В качестве примера я буду использовать создание образа для докера с пайтоном внутри. В моём случае, первой проблемой было поведение pipenv install. Интуитивно кажется, что эта команда должна установить зависимости из файла Pipfile.lock — с их конкретными версиями. Однако, на деле оказалось, что под капотом сначала запускается команда lock, которая обновляет версии пакетов в файле Pipfile.lock до наиболее свежих, совместимых с ограничениями версий, прописанными в Pipfile. И уже после этого запускает команду sync, которая устанавливает зависимости из lock-файла. Это может не быть большой проблемой, если ограничения по версиям в Pipfile достаточно строгие. Но это зачастую не так и помимо этого всегда есть риск получить «неудачное» минорное обновление. В этом случае можно получить сломанный образ и долго искать причину — когда локально всё работает.

После выяснения этих деталей я перешёл на использование pipenv sync напрямую.

Однако, позже обнаружилась другая проблема: очередное обновление сломало Pipenv так, что пришлось откатываться на более раннюю версию и фиксировать её в Dockerfile, чтобы избежать такого в дальнейшем.

После этого у меня появилась идея о том, в наличии Pipenv внутри контейнера нет необходимости. Всё, что эта утилита делает в контейнере — это чтение зависимостей из Pipfile.lock и запуск pip для их установки. А для этого достаточно иметь лишь requirements.txt.

Существует официальный способ для генерации requirements.txt с помощью Pipenv:

1
pipenv lock -r > requirements.txt

Это работает, однако, имеет упомянутый выше побочный эффект — обновление версий и возможное несовпадение зависимостей как следствие. И нет альтернативной команды: pipenv sync -r.

Кто-то даже создал утилиту для генерации requirements-файлов из lock-фала, называется pipenv-to-requirements. Я её не пробовал, потому что нашёл способ получше.

Периодически мне приходится работать с json-файлами в терминале. Для этого есть замечательный инструмент jq, рекомендую с ним познакомиться, если вы этого ещё не сделали. Так как Pipfile.lock — это обычный json-файл, логично предположить, что с помощью jq можно трансформировать его в формат requirements.txt.

Вот что получилось:

1
2
3
4
jq -r '.default
        | to_entries[]
        | .key + .value.version' \
    Pipfile.lock > requirements.txt
  • флаг -r нужен для того, чтобы убрать кавычки из выходных данных
  • часть .defailt получает содержимое секции default внутри Pipfile.lock, заменив её на .develop можно сгенерировать requirements-dev.txt
  • to_entries[] трансформирует пары ключ-значение из объекта на предыдущем шаге: package: info трансформируется в {"key": package, "value": info} и пакуется в массив
  • .key + .value.version составляет строку из названия пакета и его точной версии

Загляните в Pipfile.lock для лучшего понимания.