Файл requirements.txt в Python и как его создать
requirements.txt — это простой текстовый файл, который содержит перечень всех модулей и пакетов, необходимых для корректной работы вашей программы. Создавая файл Python requirements.txt , вы избавляете себя от необходимости искать и устанавливать все необходимые модули вручную.
Из статьи вы узнаете о том, как создать файл requirements.txt , о его преимуществах и особенностях использования.
Преимущества использования файла зависимостей
- Возможность отслеживать актуальный список всех модулей и пакетов Python, используемых в вашем проекте.
- Облегчение процесса установки недостающих компонентов.
- Удобство совместной работы. Если на ПК другого пользователя отсутствуют нужные модули, они будут быстро загружены из файла requirements.txt , обеспечив беспроблемный запуск программы.
- Если вы захотите удалить, добавить или обновить модуль, изменения будет достаточно внести только в файл requirements.txt .
- При загрузке requirements.txt , GitHub проверяет зависимости на наличие конфликтов, и в некоторых случаях устраняет уязвимости.
Как создать файл зависимостей
Для этого вам достаточно перейти в корневой каталог проекта, где хранятся ваши .py -файлы, и создать текстовый документ requirements.txt . Важно убедиться, чтобы название было именно таким.
Также этот файл может быть сгенерирован автоматически с помощью следующей команды:
pip freeze > requirements.txt
Она возвращает список всех установленных модулей с указанием версий и помещает их в текстовый файл. Обратите внимание, что pip freeze подразумевает использование виртуальной среды для текущего проекта. В противном случае, список зависимостей может включать в себя и те пакеты, которые установлены в другие виртуальные среды.
Дополнительный вариант использования этой команды, который возвращает только локальные установленные пакеты:
pip freeze —local
Добавление модулей в файл
После создания файла его необходимо заполнить названиями модулей и их версиями. Самый простой способ — сделать это вручную. Вот пример содержимого requirements.txt :
matplotlib==3.2.1 numpy==1.18.5 pandas==1.0.4 tensorflow==2.3.1
Перечислив все зависимости, сохраняем файл и закрываем его.
Второй способ — команда pip freeze > requirements.txt , которая работает, даже если файл уже существует. Его пустое содержимое будет заполнено списком пакетов так же, как и при генерации нового файла.
Установка модулей из файла
Для того чтобы установить пакеты из requirements.txt , необходимо открыть командную строку, перейти в каталог проекта и ввести следующую команду:
pip install -r requirements.txt
Если вы хотите обновить компоненты вместо их повторной установки, используйте команду pip install -U -r requirements.txt .
Как поддерживать requirements.txt в актуальном состоянии
Если вы уже создали файл с зависимостями ранее, но по какой-то причине не обновляли его содержимое, волноваться не стоит. Выполните следующие шаги:
- Выведите список устаревших модулей с помощью pip list —outdated .
- Обновите выведенные пакеты вручную с помощью pip install -U PackageName или автоматически, используя pip install -U -r requirements.txt .
- Убедитесь, что ваша программа работает корректно.
- Используйте pip freeze > requirements.txt , чтобы актуализировать содержимое файла с необходимыми внешними зависимостями.
Таким образом вы сможете без проблем обновить информацию об используемых установленных пакетах, даже если в течение определенного времени не занимались управлением зависимостями.
Помните, что постоянное обновление файла requirements.txt помогает избежать многих проблем, связанных с устаревшими или отсутствующими модулями или пакетами. Как следствие, вы обеспечите корректную работу всех ваших сборок на любых ПК.
Как еще можно создать файл зависимостей?
Можно воспользоваться библиотекой pipreqs , которая сделает все за нас. Её запуск в командной строке сгенерирует файл с зависимостями:
$ pipreqs /home/project/location Successfully saved requirements file in /home/project/location/requirements.txt
При этом никто не запрещает вновь обратиться к pip freeze или заполнению документа вручную.
Советы по использованию файла требований
- Всегда используйте pip freeze , чтобы поддерживать список внешних зависимостей в актуальном состоянии.
- Храните в requirements.txt только необходимые модули и пакеты. В противном случае файл может получиться слишком большим и нечитаемым, а неиспользуемые компоненты будут лишь впустую тратить ресурсы.
- Сохраняйте файл с зависимостями в репозитории проекта, чтобы им могли пользоваться другие люди.
- Используйте pip install -r requirements.txt , чтобы автоматически установить все модули, необходимые для работы программы.
- Поддерживайте список зависимостей в актуальном состоянии, чтобы обеспечить полную работоспособность проекта на различных машинах.
Заключение
Мы рассказали вам о том, что представляет собой файл requirements.txt в Python и как его создать. Более того, в материале были разобраны преимущества его использования и практические рекомендации.
Ведение файла requirements.txt является неотъемлемой частью управления зависимостями проекта, которая в конечном итоге избавляет от ряда возможных проблем как программистов, так и конечных пользователей.
Введение в Virtual Environments
Если вы работали с несколькими проектами, в которых использовался Python, то наверняка вы встречались с проблемой поломки одного из проекта, потому что обновленная версия библиотеки для другого проекта, не применима для текущего. Т.е. если вы работаете с Python и не используете miniconda или anaconda, то установка и обновление библиотек python постоянно ломает ваши проекты. Эта проблема называется «Ад зависимостей».
Поэтому лучшим подходом будет создавать для каждого отдельного проекта свою среду. В этой статье будет рассмотрена библиотека venv для настройки Virtual Environment для Windows.
Виртуальная среда — это способ Python для разделения зависимостей между проектами.
Создание виртуальной среды — venv в Windows
venv -это пакет, поставляемый с Python 3.
venv (для Python 3) позволяет управлять отдельными установками пакетов для разных проектов. По сути, venv позволяет вам создавать «виртуальную» изолированную установку Python и устанавливать пакеты в эту виртуальную установку. При переключении проектов вы можете просто создать новую виртуальную среду и не беспокоиться о нарушении работы пакетов, установленных в других средах. При разработке приложений Python всегда рекомендуется использовать виртуальную среду.
Чтобы создать виртуальную среду, перейдите в каталог вашего проекта и запустите venv.
python3 -m venv venv
python -m venv venv
venv создаст виртуальную установку Python в директории venv .

Примечание: Вы должны исключить каталог виртуальной среды из своей системы управления версиями с помощью .gitignore .
Активация и деактивация виртуальной среды Python
Далее необходимо активировать виртуальную среду.
Для этого необходимо в консоли cmd запустить .\venv\Scripts\activate или файл .\venv\Scripts\activate.bat , или .\venv\Scripts\Activate.ps1 .

Префикс вашего рабочего каталога изменится (выделил желтым — venv)
Пока ваша виртуальная среда активирована, pip будет устанавливать пакеты в эту конкретную среду, и вы сможете импортировать и использовать пакеты в своем приложении Python.
Установка пакетов в виртуальную среду
Пример:
pip install requests
pip позволяет вам указать, какую версию пакета установить, используя спецификаторы версии . Например, чтобы установить определенную версию requests :
pip install requests==2.18.4
Как сохранить пакеты в файл requirements.txt
Pip может экспортировать список всех установленных пакетов и их версий с помощью freeze команды: pip freeze > requirements.txt .
Будет выведен список спецификаторов пакетов, таких как:
backports.entry-points-selectable==1.1.0 certifi==2021.5.30 charset-normalizer==2.0.3 distlib==0.3.2 filelock==3.0.12 idna==3.2 platformdirs==2.0.2 requests==2.26.0 six==1.16.0 urllib3==1.26.6 virtualenv==20.6.0
Имейте в виду, что в этом случае в файле requirements.txt будут перечислены все пакеты, которые были установлены в виртуальной среде, независимо от того, откуда они пришли.
Установить пакеты из файла requirements.txt
pip install -r requirements.txt
Как запустить скрипт Python в виртуальной среде. Пример автоматизации с помощью cmd
Для того, чтобы запустить скрипт, достаточно внутри директории с проектом (со средой) запустить команду:
"D:\#python#\#env\flask-app\venv\Scripts\python.exe" "D:\#python#\#env\flask-app\app.py"
Либо создать файл cmd с содержимым и запустить его:
@echo off For /f "tokens=1-4 delims=/ " %%a in ('date /t') do (set mydate=%%c-%%a-%%b) For /f "tokens=1-2 delims=/:" %%a in ('time /t') do (set mytime=%%a%%b) rem %mydate%_%mytime% "D:\#python#\#env\flask-app\venv\Scripts\python.exe" "D:\#python#\#env\flask-app\app.py" 2>"D:\#python#\#env\flask-app\log_get_data_log_%mydate%_%mytime%.log"
Создание виртуальной среды с помощью PyCharm
Для более легкой работы с виртуальными средами на Windows рекомендую установить PyCharm (Community Edition бесплатная). При создании проекта он сам создаст виртуальную среду и будет запускать файл в этой виртуальной среде:

Новую виртуальную среду можно создать с помощью разных инструментов в Pycharm:

Создание виртуальной среды в Ubuntu 20.14
С помощью пакета venv
# Создаем директорию проекта mkdir flask-app # Переходим внутрь директории cd flask-app # Создаем среду myenv python3 -m venv myenv # Активируем среду source myenv/bin/activate
Внедрение зависимостей проще простого – на Python

В качестве иллюстрации для этой статьи рассмотрим проект-пример. Предположим, вы пишете код приложения-чатбота. Вы хотите, чтобы некоторые классы можно было переиспользовать от бота к боту, чтобы не переделывать всякий раз всю работу заново. Для начала пишете главные классы:
from typing import List, Optional class UserMessageSource: def get_user_message(self) -> str: raise NotImplementedError class OutputWriter: def write_bot_messages(self, bot_messages: List[str]) -> None: raise NotImplementedError class AnswerGenerator: def __init__(self): self.end_conversation = False def get_answers(self, user_message: str) -> List[str]: bot_messages = [] if user_message in ["hello", "hi"]: bot_messages.append("Hello there!") elif user_message in ["bye", "good bye"]: bot_messages.append("See you!") self.end_conversation = True else: bot_messages.append("I'm sorry, I didn't understand that :(") return bot_messages class ConversationLogger: def __init__(self, file_path: str): self.file_path = file_path def append_to_conversation(self, user_message: str, bot_messages: List[str]) -> None: with open(self.file_path, "a") as conversation_file: conversation_file.write(f"Human: \n") for message in bot_messages: conversation_file.write(f"Bot: \n") class Chat: def __init__(self, user_message_source: UserMessageSource, output_writer: OutputWriter, answer_generator: AnswerGenerator, conversation_logger: Optional[ConversationLogger] = None): self.user_message_source = user_message_source self.output_writer = output_writer self.answer_generator = answer_generator self.conversation_logger = conversation_logger def run(self): while not self.answer_generator.end_conversation: user_message = self.user_message_source.get_user_message() bot_messages = self.answer_generator.get_answers(user_message) self.output_writer.write_bot_messages(bot_messages) if self.conversation_logger: self.conversation_logger.append_to_conversation(user_message, bot_messages)
Простое приложение для чата
Класс Chat вполне понятен. В нем требуется источник, из которого предоставляются пользовательские сообщения, затем дается генератор ответов, чтобы получать соответствующие ответы, а затем записыватель вывода, отвечающий за обработку ответа. Как вариант, можно добавить инструмент логирования, который будет записывать беседу в файл.
UserMessageSource и OutputMessageWriter – это абстрактные классы, которые можно реализовать для кастомизации поведения чатбота.
В реалистичном сценарии мы могли бы создать две разные реализации. Версия для работы через интерфейс командной строки (CLI) считывает ввод из консоли и записывает в нее ответы, такой режим полезен для отладки в локальной среде.
from typing import List from .chat import AnswerGenerator, Chat, ConversationLogger, OutputWriter, UserMessageSource class CliUserMessageSource(UserMessageSource): def get_user_message(self) -> str: return input("Human: ").strip().lower() class CliOutputWriter(OutputWriter): def write_bot_messages(self, bot_messages: List[str]) -> None: for message in bot_messages: print(f"Bot: ") if __name__ == "__main__": Chat( CliUserMessageSource(), CliOutputWriter(), AnswerGenerator(), ConversationLogger("logs.txt") ).run()
Простой чат для интерфейса командной строки
Эта простая версия позволяет считывать ввод из CLI и выводить ответы, а также логировать беседу в текстовом файле.
Версия для промышленного использования могла бы подключаться к какому-нибудь решению с очередями сообщений, например, к RabbitMQ, чтобы соотносить поток пользовательского ввода с соответствующими ответами бота. Поскольку в рамках этой статьи детали реализации не слишком важны, рассмотрим простую вымышленную версию:
from dataclasses import dataclass from random import random from time import sleep from typing import List from .chat import AnswerGenerator, Chat, OutputWriter, UserMessageSource @dataclass class MqConfig: host: str port: int username: str password: str class MqUserMessageSource(UserMessageSource): def __init__(self, config: MqConfig): self.config = config def get_user_message(self) -> str: return self.poll_messages() def poll_messages(self) -> str: # Fake method, real implementation would use the MqConfig sleep(1) return "hi" if random() > 0.2 else "bye" class MqOutputWriter(OutputWriter): def __init__(self, config: MqConfig): self.config = config def write_bot_messages(self, bot_messages: List[str]) -> None: for message in bot_messages: self.produce_message(message) def produce_message(self, message: str) -> None: # Fake method, real implementation would use the MqConfig pass if __name__ == "__main__": mq_config = MqConfig( "localhost", 1234, "mq_user", "my_password", ) Chat( MqUserMessageSource(mq_config), MqOutputWriter(mq_config), AnswerGenerator(), ).run()
Сымитированный чат, подключенный через очередь сообщений
В этой версии нам понадобится экземпляр MqConfig, который бы совместно использовался между MqUserMessageSource и MqOutputWriter. ConversationLogger здесь не используется, его можно было бы добавить как независимый потребитель очереди сообщений.
Код работает, но в нем остались некоторые болевые точки:
- Создание новой конфигурации: подразумевается, что при этом будет создаваться новый экземпляр Chat и все его зависимости. Если это решение не кажется вам работоспособным – помните, это всего лишь пример, вряд ли вы стали бы его придерживаться, если бы вам потребовалось работать более чем со 100 классами зависимостей.
- Добавление/удаление/замена возможности: изменение группы логически связанных зависимостей может быть изнурительным (вам придется удалить 2 класса и добавить еще 3, чтобы у вас был чатбот, подключенный к очереди сообщений).
- беседы требует соблюдать формат лога, то вам пришлось бы проверить все конфигурации, в которых он используется, и отредактировать их вручную.
- Здесь слишком много шаблонного кода, в нем легко допустить ошибку и ценность его невелика.
Одним из решений, которое позволит это сгладить, является внедрение зависимости, автоматически связывающей каждый класс с его зависимостями.
Как это работает?
Вот несколько примеров, демонстрирующих, как при помощи библиотеки opyoid упростить ваше приложение.
from opyoid import ClassBinding, Injector, InstanceBinding from .chat import AnswerGenerator, Chat, ConversationLogger, OutputWriter, UserMessageSource from .cli import CliOutputWriter, CliUserMessageSource if __name__ == "__main__": injector = Injector(bindings=[ ClassBinding(Chat), ClassBinding(AnswerGenerator), InstanceBinding(ConversationLogger, ConversationLogger("file.txt")), ClassBinding(UserMessageSource, bound_type=CliUserMessageSource), ClassBinding(OutputWriter, bound_type=CliOutputWriter), ]) chat = injector.inject(Chat) chat.run()
Версия для интерфейса командной строки, использующая библиотеку opyoid
Как видите, тут создаются связки, конфигурирующие, экземпляры каких классов следует создавать – а все остальное делает opyoid.
- Здесь для Chat требуется экземпляр UserMessageSource, а CliUserMessageSource связан с этим типом, поэтому его экземпляр создается, когда это нужно. То же касается OutputWriter, который связан с версией CLI. Когда вы хотите привязать класс к нему же самому, например, Chat или AnswerGenerator, то вам не приходится объявлять их дважды.
- ConversationLogger связан с экземпляром самого себя, он будет использоваться напрямую, когда это станет необходимо.
- Все связки даются Injector, который затем может использовать их для создания экземпляров новых объектов.
- Обратите внимание, что единственное требование для привязки класса заключается в том, чтобы конструктор использовал подсказки типов. В нашем примере мы никоим образом не меняли классы.
- Кроме того, порядок привязок не имеет значения. Класс можно привязать до его зависимостей или после, об этом даже не стоит задумываться.
Как же теперь выглядит версия с очередью сообщений?
from opyoid import ClassBinding, Injector, InstanceBinding from .chat import AnswerGenerator, Chat, OutputWriter, UserMessageSource from .mq import MqConfig, MqOutputWriter, MqUserMessageSource if __name__ == "__main__": injector = Injector(bindings=[ ClassBinding(Chat), ClassBinding(AnswerGenerator), ClassBinding(UserMessageSource, bound_type=MqUserMessageSource), ClassBinding(OutputWriter, bound_type=MqOutputWriter), InstanceBinding(MqConfig, bound_instance=MqConfig( "localhost", 1234, "mq_user", "my_password", )), ]) chat = injector.inject(Chat) chat.run()
Версия с очередью сообщений, использующая opyoid
Обратите внимание, как мы воспользовались InstanceBinding для MqConfig. Это полезно, когда требуется внедрить конфигурационные классы данных, содержащие типы-примитивы – например, строки, целые числа или булевы значения. Этот MqConfig автоматически связан со всеми подключениями очереди сообщений, поэтому при добавлении нового не потребовалось бы никакой дополнительной конфигурации кроме самого класса. Также здесь видно, что при удалении ConversationLogger привязка не представляет проблем, поскольку имеет значение по умолчанию в конструкторе Chat. Нужны только параметры без значений по умолчанию.
Дальнейшие улучшения
А что, если в моем коде отсутствуют подсказки типов? Значит ли это, что мне придется переписать весь код, чтобы воспользоваться возможностями, описанными выше? А что, если я завишу от внешней библиотеки, которую не контролирую?
Можете быть уверены, вы сможете написать собственный провайдер, в который сможете обернуть свои классы, чтобы и их можно было внедрять.
from opyoid import ClassBinding, Injector, InstanceBinding, Provider, ProviderBinding from typing import Optional from .chat import AnswerGenerator, Chat, ConversationLogger, OutputWriter, UserMessageSource from .cli import CliOutputWriter, CliUserMessageSource class ChatProvider(Provider[Chat]): def __init__(self, user_message_source: UserMessageSource, output_writer: OutputWriter, answer_generator: AnswerGenerator, conversation_logger: Optional[ConversationLogger] = None): self.user_message_source = user_message_source self.output_writer = output_writer self.answer_generator = answer_generator self.conversation_logger = conversation_logger def get(self) -> Chat: return Chat( self.user_message_source, self.output_writer, self.answer_generator, self.conversation_logger, ) if __name__ == "__main__": injector = Injector(bindings=[ ProviderBinding(Chat, bound_provider=ChatProvider), ClassBinding(AnswerGenerator), InstanceBinding(ConversationLogger, ConversationLogger("file.txt")), ClassBinding(UserMessageSource, bound_type=CliUserMessageSource), ClassBinding(OutputWriter, bound_type=CliOutputWriter), ]) chat = injector.inject(Chat) chat.run()
Провайдер для класса Chat
Здесь ChatProvider будет использоваться для создания каждого необходимого экземпляра Chat, даже если в его конструкторе не будет подсказок типов. Обратите внимание на ProviderBinding в инициализации Injector.
Бывает, что собственный провайдер пишут и для того, чтобы добавить в нем логику, описывающую, как создаются экземпляры некоторых классов.
Значит ли это, что я должен создать единственный файл со всеми моими связками? Я все равно должен продублировать много связок между моими вариантами конфигурации.
Вот почему можно пользоваться модулями для группирования связок – так их можно будет многократно использовать в разных конфигурациях и просто выбирать те, что вам нужны.
from opyoid import Module from .chat import AnswerGenerator, Chat, ConversationLogger, OutputWriter, UserMessageSource from .cli import CliOutputWriter, CliUserMessageSource from .mq import MqOutputWriter, MqUserMessageSource class ChatModule(Module): def configure(self) -> None: self.bind(Chat) self.bind(AnswerGenerator) class CliModule(Module): def configure(self) -> None: self.bind(ConversationLogger, to_instance=ConversationLogger("file.txt")) self.bind(UserMessageSource, to_class=CliUserMessageSource) self.bind(OutputWriter, to_class=CliOutputWriter) class MqModule(Module): def configure(self) -> None: self.bind(UserMessageSource, to_class=MqUserMessageSource) self.bind(OutputWriter, to_class=MqOutputWriter)
Модуль обычно используется, чтобы сгруппировать все классы, относящиеся к той или иной возможности. Добавляя или удаляя модуль, можно с легкостью включать или выключать данную возможность.
from opyoid import Injector, Module from .chat import Chat, ConversationLogger from .modules import ChatModule, CliModule class CliChatModule(Module): def configure(self) -> None: self.install(ChatModule()) self.install(CliModule()) self.bind(ConversationLogger, to_instance=ConversationLogger("file.txt")) if __name__ == "__main__": injector = Injector(modules=[CliChatModule()]) chat = injector.inject(Chat) chat.run()
Конфигурация значительно упростилась
В модуле вы можете объявить столько связок, сколько хотите, а также установить другие модули по мере необходимости. Так, здесь мы установили ChatModule в CliChatModule.
Обратите внимание: при этом вы по-прежнему можете использовать связки поверх модуля в вашем инъекторе.
from opyoid import Injector, InstanceBinding, Module from .chat import Chat from .modules import ChatModule, MqModule from .mq import MqConfig class MqChatModule(Module): def configure(self) -> None: self.install(ChatModule()) self.install(MqModule()) if __name__ == "__main__": injector = Injector( modules=[MqChatModule()], bindings=[InstanceBinding(MqConfig, bound_instance=MqConfig( "localhost", 1234, "mq_user", "my_password", ))] ) chat = injector.inject(Chat) chat.run()
Версия очереди сообщений с модулями
А вы могли бы сделать лучше? Мы уже говорили о сокращении шаблонного кода, но мне все равно придется писать все эти связки?
from opyoid import Injector, InjectorOptions, InstanceBinding from .chat import Chat, ConversationLogger from .modules import CliModule if __name__ == "__main__": injector = Injector( modules=[CliModule()], bindings=[InstanceBinding(ConversationLogger, ConversationLogger("file.txt"))], options=InjectorOptions(auto_bindings=True)) chat = injector.inject(Chat) chat.run()
При использовании опции auto_bindings классы автоматически привязываются сами к себе, поэтому вам всего лишь потребуется написать другие связки. Конечно же, вы можете и дальше пользоваться модулями и связками, а такие автосвязки будут создаваться только в качестве последнего варианта. В данном примере мы удалили ChatModule, поскольку он содержал только ClassBindings.
В сравнении с кодом, который был у нас в самом начале, теперь имеем:
- Автоматическое связывание между классами и их зависимостями, гораздо меньше серьезных переделок, когда меняются требования, автоматическое обнаружение недостающих зависимостей
- Никакого дублирования между конфигурациями
- Количество шаблонного кода сведено до абсолютного минимума
Почему бы не переиспользовать имеющуюся библиотеку?
Мы обнаружили, что в экосистеме Python отсутствуют серьезные кандидаты для этого. Лучшей из библиотек, которую мы протестировали, была pinject, но мы хотели воспользоваться преимуществами типизации при внедрении наших классов. Другая альтернатива — python-dependency-injector, но для ее начальной настройки требуется достаточно много кода.
Поэтому мы решили написать новую библиотеку для внедрения зависимостей в Python, opyoid, которая помогала бы нам разрешать зависимости между классами и помогала при начальной настройке больших приложений.
Вот основные цели, которые ставились при создании этой библиотеки:
- Автоматически предоставлять зависимости для каждого класса
- Использовать типизацию для их разрешения, поскольку они становятся нормой в Python
- Иметь возможность внедрять сторонние классы
- Иметь возможность работать без обязательных декораторов во всех классах
Мы также добавили некоторые другие продвинутые возможности, которые нужны в данном случае:
- Область видимости каждой связки, чтобы иметь возможность совместно использовать некоторые экземпляры во всем коде, в то же время создавая множество экземпляров многих других классов
- Провайдеры для кастомизации того, как создаются классы, либо как откладывается создание экземпляра
- Аннотации, чтобы можно было иметь множество связок для одного и того же типа
- Многое другое…
Заключение
Вот и все, у нас наконец-то получился чистый удобочитаемый код, разделенный на простые классы, без возни с написанием шаблонного кода. Можно с легкостью привязывать новые классы, нужные для каждой возможности, создавать модули, чтобы их можно было группировать и легко активировать. Проект полностью выложен на Github вместе с дополнительными примерами и документацией.
- python
- внедрение зависимостей
- чистый код
- оптимизация
- программирование
Управление зависимостями в Python: файл pyproject.toml
Процесс управления зависимостями в Python вызывает сложности, а иногда и откровенное раздражение. Новичкам хочется даже в одной виртуальной среде установить любую потенциально полезную зависимость, т.е. пакет. Подобная тенденция увеличивает вероятность появления конфликтующих зависимостей пакетов и в результате приводит к такому явлению, как ад зависимостей.
Файлы setup.py , setup.cfg и requirements.txt позволяют по-разному работать с зависимостями в проектах Python. Однако в Python 3.6 был представлен новый стандартный файл конфигурации pyproject.toml , который упрощает пользователям управление зависимостями и определениями метаданных.
За последние годы файл pyproject.toml обрел популярность и стал востребованным для управления зависимостями в проектах Python. В статье мы рассмотрим его практическое применение и покажем, как установить проект со спецификацией pyproject.toml в режиме редактирования.
Управление зависимостями до pyproject.toml
Сначала в Python для создания дистрибутивов применялся пакет distutils . Затем появился setuptools , который представлял собой надстройку над distutils . Оба инструмента задействовали файл setup.py , в котором пользователи определяли зависимости и метаданные, используемые как часть дистрибутива для сборки пакета.
Однако это породило проблему, поскольку любой проект, выбирающий для работы setuptools , должен импортировать пакет в файл setup.py . Следовательно, setup.py не может выполняться без знания своих зависимостей, но при этом он предназначен для определения этих зависимостей. Вот мы и пришли к так называемой проблеме курицы и яйца в управлении зависимостями Python.
Все выше сказанное хорошо объясняет возникшую потребность в новом подходе. Более подробный разбор сути проблемы курицы и яйца в отношении setuptools и pip содержится в PEP-518.
Новое предложение, которое является частью PEP-518, позволяет проектам Python предварительно указывать свои зависимости. В этом случае такие инструменты, как pip , получают возможность проверить факт их установки до начала сборки проекта.
Файл pyproject.toml
Файл pyproject.tom был представлен как часть предложения по улучшению PEP 518. Он определяет, как именно проекты Python должны указывать зависимости сборки.
Эти зависимости хранятся в файле, который находится в корневой директории проекта и соответствует синтаксису TOML .
Он содержит метаданные, такие как имя проекта, версию, описание, автора, лицензию и другую информацию.
Одна из ключевых характеристик файла pyproject.toml — способность определять зависимости проекта. Разработчики могут указывать пакеты и версии, необходимые для правильного запуска проекта. pyproject.toml обеспечивает согласованность проекта и гарантирует возможность его воспроизведения другими разработчиками.
Файл pyproject.toml также поддерживает концепцию extras , позволяющую разработчикам определять дополнительные зависимости. Пользователи могут устанавливать только нужные зависимости для запуска проекта. Как правило, в разделе extras указываются дополнительные требования, которые используются в рамках тестирования, например pytest .
Помимо стандартных метаданных и зависимостей файл pyproject.toml также поддерживает настраиваемые поля с возможностью их применения сторонними инструментами. Это могут быть статические анализаторы, средства форматирования и проверки, такие как black и mypy . Данное свойство файла pyproject.toml позволяет разработчикам расширять функциональность файла и добавлять настраиваемые поля в соответствии с требованиями.
Управление зависимостями в pyproject.toml
pyprojet.toml можно задействовать с инструментами управления зависимостями пакетов, такими как setuptools и poetry .
В качестве примера рассмотрим файл для проекта, который использует poetry :
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
[tool.poetry]
name = "my-project"
version = "1.0.0"
description = "My Python project"
authors = ["John Doe "]
license = "MIT"
[tool.poetry.dependencies]
python = "^3.6"
[tool.poetry.dev-dependencies]
pytest = "^4.6"
[tool.poetry.extras]
docs = ["sphinx"]
Пример с setuptools :
[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"
[project]
name = "my_package"
description = "My package description"
readme = "README.rst"
requires-python = ">=3.7"
keywords = ["one", "two"]
license =
classifiers = [
"Framework :: Django",
"Programming Language :: Python :: 3",
]
dependencies = [
"requests",
'importlib-metadata; python_version]
dynamic = ["version"]
[project.optional-dependencies]
pdf = ["ReportLab>=1.2", "RXP"]
rest = ["docutils>=0.3", "pack ==1.1, ==1.3"]
[project.scripts]
my-script = "my_package.module:function"
Установка проекта в режиме редактирования из pyproject.toml
Если вы активно разрабатываете проект, то, скорее всего, захотите установить его локально в режиме редактирования. При установке пакета в режиме редактирования из определенного места любые изменения исходного кода сразу же отражаются в среде (без необходимости переустановки “новой” версии).
Предположим, вы управляете зависимостями Python с помощью poetry . Чтобы установить проект Python в режиме редактирования, файл pyproject.toml должен содержать следующее:
[build-system]
requires = ["poetry-core>=1.0.8"]
build-backend = "poetry.core.masonry.api"
Из корневой директории проекта просто выполняем команду:
$ pip install -e .
Как вариант, poetry install также устанавливает режим редактирования.
Заключение
В статье мы рассмотрели практическое применение pyproject.toml в Python, когда дело касается управления зависимостями и распространения проектов между участниками сообщества.
В целом, pyproject.toml предоставляет стандартную и простую конфигурацию для проектов Python. Он упрощает процесс определения метаданных и зависимостей, а также гарантирует возможность воспроизведения проекта другими разработчиками.
- Разработка продвинутого GUI на Python
- Написание консольных скриптов: Bash против Python
- Как автоматизировать удаление ненужных файлов с помощью Python
Читайте нас в Telegram, VK и Дзен
