Dependency Injector 4.0 — упрощенная интеграция с другими Python фреймворками
Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті
Привет,
Я выпустил новую мажорную версию 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() # <-- dependency is injected automatically with container.api_client.override(mock.Mock()): main() # <-- overridden dependency is injected automatically
Когда вызывается функция main() зависимость Service собирается и передается автоматически.
При тестировании вызывается container.api_client.override() чтобы заменить API клиент на мок. При вызове main() зависимость Service будет собираться с моком.
Новая фича упрощает использование Dependency Injector’а с другими Python фреймворками.
Как связывание помогает интеграции с другими фреймворками?
Связывание дает возможность делать точные инъекции независимо от структуры приложения. В отличии от
Пример с 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() # <--- Argument "bar" is injected # Same as: foo(bar=container.bar())
Больше про связывание можно узнать тут.
Совместимость?
Версия 4.0 совместима с версиями 3.х.
Интеграционные модули ext.flask и ext.aiohttp задеприкечены в пользу связывания.
При использовании фреймворк будет выводить предупреждение и рекомендовать перейти на связывание.
Полный список изменений можно найти тут.
Что дальше?
- Загляни на Github проекта
- Загляни в документацию
- Попробуй использовать Dependency Injector на проекте
- Хочешь применить в своем проекте и нужна помощь? Пиши в личку — помогу советом и код ревью.
PS: Прошлый пост про Dependency Injector тут.
4 коментарі
Додати коментар Підписатись на коментаріВідписатись від коментарів