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