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 задеприкечены в пользу связывания.
    При использовании фреймворк будет выводить предупреждение и рекомендовать перейти на связывание.

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

    Что дальше?


    Комментарии 2

      0
      «Explicit is better than implicit.
      Simple is better than complex.»… ¯\_(ツ)_/¯
        +1
        And:

        «Complex is better than complicated.»

        image

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

      Самое читаемое