Skip to content

@inject decorator on a FastAPI dependency with yield #671

Open
@dabljues

Description

@dabljues

Hi.

Docs inlcude the FastAPI example, python-dependency-injector itself works with FastAPI, but I found a case when it doesn't. Let's consider a simple FastAPI application with two dependencies:

from fastapi import Depends, FastAPI


async def service_process():
    return "foo"


async def dep1():
    await service_process()
    print("dep1 - A")
    yield
    print("dep1 - B")


async def dep2():
    print("dep2 - A")
    yield
    print("dep2 - B")


app = FastAPI(dependencies=[Depends(dep1), Depends(dep2)])


@app.api_route("/")
async def index():
    return

The result of calling / would be:

dep1 - A
dep2 - A
dep2 - B
dep1 - B

It's because of how yield works in FastAPI dependencies - everything that happens before yield happens at the beginning of an endpoint, everything after -> happens after a response is returned.

Okay, let's throw python-dependency-injector into the mix. I took this example from examples/miniapps/fastapi-simple/fastapi_di_example.py and added dependencies - one with @inject. The example itself contains meaningless prints, but in the real app that I'm working on, the app configuration is injected in the dependencies.

from fastapi import FastAPI, Depends
from dependency_injector import containers, providers
from dependency_injector.wiring import Provide, inject


class Service:
    async def process(self) -> str:
        return "OK"


class Container(containers.DeclarativeContainer):

    service = providers.Factory(Service)


@inject
async def dep1(service: Service = Depends(Provide[Container.service])):
    await service.process()
    print("dep1 - A")
    yield
    print("dep1 - B")


async def dep2():
    print("dep2 - A")
    yield
    print("dep2 - B")


app = FastAPI(dependencies=[Depends(dep1), Depends(dep2)])


@app.api_route("/")
async def index():
    return


container = Container()
container.wire(modules=[__name__])

The output is:

dep2 - A
dep2 - B

However, if I remove yield from dep1:

@inject
async def dep1(service: Service = Depends(Provide[Container.service])):
    await service.process()
    print("dep1 - A")
    print("dep1 - B")

, the output is:

dep1 - A
dep1 - B
dep2 - A
dep2 - B

The different order here is because without yield, the prints in dep1 happen at the beginning of def index(), in case of dep2 one happens right after the prints from dep1, the other after the response is returned.

But this doesn't matter. What I'm curious about is why does it happen? Why is yield an issue here. It looks like the dependency is not applied at all (no code from dep1 gets executed). I'm not aware of the internals of @inject, so I'm not really sure what happens here. Before you ask - I got to have this yield for various reasons - for example I set a logger context at the beginning of the request and reset it and the end (after the response is returned).

So my question would be: is it possible to use @inject with FastAPI dependencies that with yield?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions