FastAPI + Dependency Injector



    Привет,

    Я выпустил новую версию Dependency Injector 4.4. Она позволяет использовать Dependency Injector вместе с FastAPI. В этом посте покажу как это работает.

    Основная задача интеграции: подружить директиву Depends FastAPI c маркерами Provide и Provider Dependency Injector'a.

    Из коробки до версии DI 4.4 это не работало. FastAPI использует типизацию и Pydantic для валидации входных параметров и ответа. Маркеры Dependency Injector'а приводили его в недоумение.

    Решение пришло после изучения внутренностей FastAPI. Пришлось сделать нескольких изменений в модуле связывания (wiring) Dependency Injector'а. Директива Depends теперь работает вместе с маркерами Provide и Provider.

    Пример


    Создайте файл fastapi_di_example.py и поместите в него следующие строки:

    import sys
    
    from fastapi import FastAPI, Depends
    from dependency_injector import containers, providers
    from dependency_injector.wiring import inject, Provide
    
    
    class Service:
        async def process(self) -> str:
            return 'Ok'
    
    
    class Container(containers.DeclarativeContainer):
    
        service = providers.Factory(Service)
    
    
    app = FastAPI()
    
    
    @app.api_route('/')
    @inject
    async def index(service: Service = Depends(Provide[Container.service])):
        result = await service.process()
        return {'result': result}
    
    
    container = Container()
    container.wire(modules=[sys.modules[__name__]])
    

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

    pip install fastapi dependency-injector uvicorn
    

    и запустите uvicorn:

    uvicorn fastapi_di_example:app --reload
    

    В терминале должно будет появится что-то вроде:

    INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
    INFO:     Started reloader process [11910] using watchgod
    INFO:     Started server process [11912]
    INFO:     Waiting for application startup.
    INFO:     Application startup complete.
    

    а http://127.0.0.1:8000 должен возвращать:

    {
        "result": "Ok"
    }
    


    Как тестировать?


    Создайте рядом файл tests.py и поместите в него следующие строки:

    from unittest import mock
    
    import pytest
    from httpx import AsyncClient
    
    from fastapi_di_example import app, container, Service
    
    
    @pytest.fixture
    def client(event_loop):
        client = AsyncClient(app=app, base_url='http://test')
        yield client
        event_loop.run_until_complete(client.aclose())
    
    
    @pytest.mark.asyncio
    async def test_index(client):
        service_mock = mock.AsyncMock(spec=Service)
        service_mock.process.return_value = 'Foo'
    
        with container.service.override(service_mock):
            response = await client.get('/')
    
        assert response.status_code == 200
        assert response.json() == {'result': 'Foo'}
    

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

    pip install pytest pytest-asyncio httpx
    

    и запустите pytest:

    pytest tests.py
    

    В терминале должно будет появится:

    ======= test session starts =======
    platform darwin -- Python 3.8.3, pytest-5.4.3, py-1.9.0, pluggy-0.13.1
    rootdir: ...
    plugins: asyncio-0.14.0
    collected 1 item
    
    tests.py .                                                      [100%]
    
    ======= 1 passed in 0.17s =======
    

    Что дает интеграция?


    FastAPI — классный фреймворк для построения API. В него встроен базовый механизм dependency injection.

    Эта интеграция улучшает работу dependency injection в FastAPI. Она позволяет использовать в нём провайдеры, переопредение, конфиг и ресурсы Dependency Injector'а.

    Что дальше?


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

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

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