Dependency Injector 4.0 — упрощенная интеграция с другими Python фреймворками

от автора

Привет, Хабр! Я выпустил новую мажорную версию Dependency Injector.

Основная фича этой версии — связывание (wiring). Она позволяет делать инъекции в функции и методы без затягивания их в контейнер.

from dependency_injector import containers, providers from dependency_injector.wiring import Provide   class Container(containers.DeclarativeContainer):      config = providers.Configuration()      api_client = providers.Singleton(         ApiClient,         api_key=config.api_key,         timeout=config.timeout.as_int(),     )      service = providers.Factory(         Service,         api_client=api_client,     )   def main(service: Service = Provide[Container.service]):     ...   if __name__ == '__main__':     container = Container()     container.config.api_key.from_env('API_KEY')     container.config.timeout.from_env('TIMEOUT')     container.wire(modules=[sys.modules[__name__]])      main()  # <-- зависимость внедряется автоматически      with container.api_client.override(mock.Mock()):         main()  # <-- переопределенная зависимость внедряется автоматически 

Когда вызывается функция main() зависимость Service собирается и передается автоматически.

При тестировании вызывается container.api_client.override() чтобы заменить API клиент на мок. При вызове main() зависимость Service будет собираться с моком.

Новая фича упрощает использование Dependency Injector’а с другими Python фреймворками.

Как связывание помогает интеграции с другими фреймворками?

Связывание дает возможность делать точные инъекции независимо от структуры приложения. В отличии от 3-ей версии для внедрения зависимости не нужно затягивать функцию или класс в контейнер.

Пример с Flask:

import sys  from dependency_injector import containers, providers from dependency_injector.wiring import Provide from flask import Flask, json   class Service:     ...   class Container(containers.DeclarativeContainer):      service = providers.Factory(Service)   def index_view(service: Service = Provide[Container.service]) -> str:     return json.dumps({'service_id': id(service)})   if __name__ == '__main__':     container = Container()     container.wire(modules=[sys.modules[__name__]])      app = Flask(__name__)     app.add_url_rule('/', 'index', index_view)     app.run() 

Другие примеры:

Как работает связывание?

Для того чтобы применять связывание нужно:

  • Разместить маркеры в коде. Маркер вида Provide[Container.bar] указывается как дефолтное значение аргумента функции или метода. Маркеры нужны чтобы указать что и куда внедрять.
  • Связать контейнер с маркерами в коде. Для этого нужно вызвать метод container.wire(modules=[…], packages=[…]) и указать модули или пакеты, в которых есть маркеры.
  • Использовать функции и методы как обычно. Фреймворк подготовит и внедрит нужные зависимости автоматически.

Связывание работает на базе интроспекции. При вызове container.wire(modules=[...], packages=[...]) фреймворк пройдется по всем функциям и методам в этих пакетах и модулях и изучит их дефолтные параметры. Если дефолтным параметром будет маркер, то такая функция или метод будут пропатчены декоратором внедрения зависимостей. Этот декоратор при вызове подготавливает и внедряет зависимости вместо маркеров в оригинальную функцию.

def foo(bar: Bar = Provide[Container.bar]):     ...   container = Container() container.wire(modules=[sys.modules[__name__]])  foo()  # <--- Аргумент "bar" будет внедрен  # То же что и: foo(bar=container.bar()) 

Больше про связывание можно узнать тут.

Совместимость?

Версия 4.0 совместима с версиями 3.х.

Интеграционные модули ext.flask и ext.aiohttp задеприкечены в пользу связывания.
При использовании фреймворк будет выводить предупреждение и рекомендовать перейти на связывание.

Полный список изменений можно найти тут.

Что дальше?

ссылка на оригинал статьи https://habr.com/ru/post/522988/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *