Skip to content

FastAPI async resouce not being closed #595

@John98Zakaria

Description

@John98Zakaria

I was trying out the async resource provider to verify that it is actually closing the resource.
However, I noticed that it is not closing the resource.

After deep investigations I found out that the @Inject decorator on the API
makes python think that the function is not an async generator.

FastAPI decides whether the dependency is a generator on this line https://github.com/tiangolo/fastapi/blob/master/fastapi/dependencies/utils.py#L522

Which then tries to inspect the code using the builtin inspect function

def is_gen_callable(call: Callable[..., Any]) -> bool:
    if inspect.isgeneratorfunction(call):
        return True
    call = getattr(call, "__call__", None)
    return inspect.isgeneratorfunction(call)

def is_async_gen_callable(call: Callable[..., Any]) -> bool:
    if inspect.isasyncgenfunction(call):
        return True
    call = getattr(call, "__call__", None)
    return inspect.isasyncgenfunction(call)

Which is returning false for async generators decorated with @Inject

I have attached the test code.

I'll try investigating the @Inject decorator
To see whether it could be fixed

import contextlib
import os
from functools import partial

from dependency_injector import containers, providers, resources
from dependency_injector.wiring import Provide, inject, Closing
from fastapi import FastAPI, Depends
from pydantic import BaseSettings, Field
from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine, AsyncSession
from sqlalchemy.orm import sessionmaker

os.environ["sql_alchemy_str"] = "sqlite+aiosqlite:///database.db"


class DatabaseSettings(BaseSettings):
    sql_alchemy_str: str = Field(env="sql_alchemy_str")


async def make(engine=None):
    print(f"Make {engine}")
    session = sessionmaker(
        engine, expire_on_commit=False, class_=AsyncSession
    )
    yield session()
    with open("kill.txt", "w") as f:
        f.write("killed")
    print("kill")


class DatabaseContainer(containers.DeclarativeContainer):
    config = providers.Configuration[DatabaseSettings]("DatabaseConfig")
    alchemy_engine: AsyncEngine = providers.Singleton(create_async_engine, config.sql_alchemy_str, echo=True)
    async_session = providers.Resource(make, alchemy_engine)


app = FastAPI()


@app.get("/Hello")
@inject
async def say_hi(session: AsyncSession = Depends(Closing[Provide[DatabaseContainer.async_session]], use_cache=False)):
    print(f"Got {session}")


container = DatabaseContainer()
container.config.from_pydantic(DatabaseSettings())
container.wire(modules=[__name__])


@app.on_event("startup")
async def startup_event():
    print(container.alchemy_engine())

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