requirements.txt — что это и зачем?

Опубликовано: 22 апреля 2020, Ср, автор: Андрей Семакин Обновлено: 27 апреля 2020, Пн 6 минут

requirements.txt

В исходниках множества Python-проектов можно встретить этот странный текстовый файл. Например, им пользуются urllib3, numpy, pandas, flake8 и куча других проектов. Давайте разберемся, что это такое, как этим пользоваться и зачем нам это нужно.

Гипотетическая предыстория

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

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

Traceback (most recent call last):
  File "weather.py", line 3, in <module>
    from pyowm import OWM
ModuleNotFoundError: No module named 'pyowm'

Кажется, что скинуть только код недостаточно.

Или, допустим, что вы сами через полгода-год попытаетесь запустить эту же программу. За это время вы успели пару раз переустановить Python, переустановить ОС, отформатировать свой магнитный накопитель (используйте SSD — нет, я серьёзно!) или может быть вообще сменили компьютер. Почти уверен, что при запуске скрипта вы получите ровно ту же самую ошибку.

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

Если ваша программа небольшая (состоит из пары файлов), то можно довольно легко просмотреть её глазами, найти там все инструкции import, отсеять из них импорты из стандартной библиотеки (вы ведь знаете модули стандартной библиотеки наизусть, да?), и таким образом восстановить список внешних зависимостей программы, которые устанавливаются через pip. Чем крупнее проект, тем сложнее это сделать. Бывают ситуации, когда по коду вообще нельзя понять, что ему нужна определенная зависимость.

Я хочу сказать, что намного мудрее составлять этот список зависимостей сразу же и просто поддерживать его в актуальном состоянии по мере развития проекта.

requirements.txt — это список внешних зависимостей

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

Стоит отметить, что requirements.txt не является стандартом, т.е. нет документа, который описывал бы требования к этому файлу. Скорее, это просто распространённая практика в сообществе, которая, наверное, возникла спонтанно и хорошо прижилась.

Не обязательно называть файл именно requirements.txt, можно назвать его как угодно, лишь бы его назначение оставалось понятно. Я встречал такие вариации, как requirements-dev.txt, test-requirements.txt, requirements/docs.txt и другие.

Вот пример самого простого такого файла (кстати, именно этим файлом можно описать зависимости, которые нужны для запуска нашего скрипта с погодой):

pyowm

Если бы было несколько зависимостей, то файл выглядел бы так:

numpy
requests
pytest

Можно указать конкретную версию зависимости. Если версия не указана, то считается, что нужна последняя доступная:

numpy  # нужна последняя версия
requests==2.23.0  # нужна конкретная версия

Можно указывать диапазоны и другие более сложные "спецификаторы версий". В целом, в requirements.txt можно писать любые "запросы", которые понимает команда pip install:

requests>=2.20.0,<2.23.0  # подойдет любая версия между 2.20.0 и 2.23.0
numpy!=1.0  # подойдет любая версия, кроме 1.0

Как пользоваться

Команда pip install умеет читать такие файлы, если передать специальный флаг:

$ pip install --help
...
Install Options:
  -r, --requirement <file>    Install from the given requirements file. This option can be used multiple times.
...

Таким образом, если requirements.txt будет иметь вот такое содержимое:

pytest
flake8
black
isort

То следующие две команды будут иметь одинаковое действие:

$ pip install -r requirements.txt

$ pip install pytest flake8 black isort

Преимущества использования requirements.txt:

  • Краткость.

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

  • Стабильность.

    Как бы ни поменялся файл requirements.txt, команда pip install -r requirements.txt не поменяется.

  • Универсальность.

    Так как это распространённое соглашение, то другим разработчикам будет достаточно увидеть этот файл, чтобы понять, что нужно сделать. Это здорово экономит время на чтении инструкций.

Как создать

Есть два подхода:

  • создавать этот файл вручную;
  • генерировать автоматически.

Главный принцип ручного подхода — если что-то поменялось в списке зависимостей (добавилась или удалилась зависимость, обновилась версия и т.д.), это изменение нужно отразить в requirements.txt.

Но можно использовать и встроенную в pip функциональность:

(env) $ pip freeze
certifi==2020.4.5.1
chardet==3.0.4
geojson==2.5.0
idna==2.9
pyowm==2.10.0
requests==2.23.0
urllib3==1.25.9

Команда pip freeze выводит все установленные в интерпретатор сторонние пакеты. Заметьте, что в список попали не только прямые зависимости (pyowm), но и подзависимости — это даже лучше, потому что вы сможете более точно воссоздать окружение по этому файлу.

Можно перенаправить вывод этой команды в файл при помощи стандартного консольного приема (работает и на Windows), и получить валидный файл requirements.txt:

(env) $ pip freeze > requirements.txt

Обратите внимание, что pip freeze выведет список пакетов в том окружении, в котором он запущен. Не забудьте активировать виртуальное окружение перед запуском этой команды, иначе получите список пакетов, установленных в глобальный интерпретатор. Кстати, у меня есть пост про виртуальные окружения, где объясняется как ими пользоваться.

Подытожим плюсы и минусы ручного и автоматического подходов:

  • Вручную:
    • минимальный файл, содержащий только прямые зависимости;
    • можно указывать сложные спецификаторы версий;
    • человеческий фактор — легко ошибиться или забыть что-нибудь;
  • pip freeze:
    • автоматически, быстро;
    • автоматически добавляет версии установленных пакетов;
    • в файл попадет всё дерево зависимостей, а не только прямые зависимости.

Можно использовать и смешанный подход: сгенерировать начальную версию файла при помощи pip freeze и допилить её затем руками, если у вас какая-то сложная нестандартная ситуация.

⚠️ Файл requirements.txt, конечно же, должен быть добавлен в систему контроля версий (git). Это такая же важная часть проекта, как и код. При этом виртуальное окружение не должно попадать в систему контроля версий. Оно должно воссоздаваться из requirements.txt.

Проблемы requirements.txt

Некоторые пакеты часто меняются, поэтому если вы не указываете конкретные версии, то в следующий раз при установке вы можете получить совсем другую среду. Это бывает особенно обидно, когда локально на машине разработчика всё работает правильно, но при деплое на боевой сервер программа либо работает иначе, либо вообще отказывается запускаться. Поэтому обязательно фиксируйте версии пакетов в requirements.txt — это сделает разные окружения хотя бы примерно похожими.

Почему "хотя бы примерно"? Практика показывает, что зафиксировать версию пакета недостаточно. Иногда случается, что под одной версией пакета в разное время может находиться совершенно разный код. PyPI, конечно, не позволит перезаписать уже опубликованную версию, но, например, ваш приватный корпоративный индекс пакетов может быть не таким строгим.

Чтобы действительно гарантировать, что вы устанавливаете те пакеты, что и ожидали, нужно рассчитывать и сверять их контрольные суммы. requirements.txt может содержать хэши пакетов, но, к сожалению, на данный момент нет простого стандартного способа как их туда положить, кроме как вручную (сложно). В качестве альтернативы опять предлагаю присмотреться к таким проектам, как poetry (хранит хэши в poetry.lock) и pipenv (хранит хэши в Pipfile.lock), где эта проблема решена хорошо, и вам не придётся переживать о воспроизводимости ваших сборок. Если всё-таки хочется добиться такого же эффекта при помощи requirements.txt, то можно посмотреть на такие проекты как pip-tools (пример использования) и hashin.

Заключение

requirements.txt — это очень популярный способ хранения списка внешних зависимостей проекта, поэтому вам определенно нужно уметь работать с такими файлами. Однако этот способ хранения списка зависимостей не лишён недостатков, поэтому если вы начинаете новый проект, то я предлагаю вам лучше использовать для этого poetry или pipenv.

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

Дополнительное чтение

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

Подпишитесь!

Чтобы получить уведомление о новом посте можно:

Обложка: Joe deSousa, Gnarled Tree