fastapi 如何让依赖支持 yield(异步资源管理)

7次阅读

fastapi依赖中不能直接使用async def + yield,必须用@asynccontextmanager包装异步生成器;正确做法是定义异步上下文管理器,通过Depends注入,确保请求开始时初始化、响应后清理资源。

fastapi 如何让依赖支持 yield(异步资源管理)

FastAPI 依赖中用 yield 会报错:TypeError: async generator Object is not awaitable

直接在 FastAPI 依赖函数里写 async def + yield 是不行的。FastAPI 默认只识别同步生成器(def + yield)或纯异步协程async def + return),不支持异步生成器(async def + yield)。这是底层 Starlette 的限制,不是 FastAPI 的 bug

正确写法:用 Depends 包裹 contextlib.asynccontextmanager

要让异步资源(比如 aiohttp.Clientsessionaiomysql.PoolAsyncSession)安全地生命周期绑定到请求,必须走异步上下文管理器路线。FastAPI 本身不原生解析 async with,但能识别被 asynccontextmanager 装饰后的函数。

  • 定义依赖时,用 @asynccontextmanager 包装一个 async def 函数,里面 yield 资源实例
  • 路由中用 Depends(your_async_context_manager) 注入
  • FastAPI 会在请求开始时 await 进入上下文,在响应返回后 await 退出上下文(包括异常路径)
from contextlib import asynccontextmanager from fastapi import Depends 

@asynccontextmanager async def get_db_session(): session = AsyncSessionLocal() try: yield session finally: await session.close()

@app.get("/items/") async def read_items(db: AsyncSession = Depends(get_db_session)): return await db.execute("SELECT * FROM items")

别用 async def + yield 直接当依赖,也别手动 await 启动

以下写法全是错的:

  • async def bad_dep(): yield ... → FastAPI 不识别,抛出 TypeError
  • 试图在依赖里 await 一个 async with 块再 return → 生命周期失控,资源可能提前释放或泄漏
  • asynccontextmanager 返回的对象直接 await → 它不是协程,是上下文管理器实例

关键点在于:asynccontextmanager 把异步生成器转成了符合 ASGI 生命周期预期的上下文管理协议对象,FastAPI 内部会按需调用 __aenter____aexit__

注意嵌套依赖和作用域scope="app" 不适用异步资源

异步资源(如数据库连接池、HTTP 客户端)通常不能设为 scope="app",因为它们需要按请求隔离或至少按任务隔离。FastAPI 的依赖作用域目前只支持 "app""request"(默认),没有 "task" 级别。

  • 如果多个请求共用一个 aiohttp.ClientSession,没问题 —— 它本就是线程/协程安全的共享对象
  • 但如果共用一个 AsyncSession,就可能引发状态污染或事务冲突
  • 所以大多数情况应保持默认 scope="request",让每个请求拿到独立的 yield 实例

真正容易被忽略的是异常传播:asynccontextmanageryield 后的代码(finally 块)一定会执行,但若 yield 期间抛出未捕获异常,__aexit__ 仍会被调用,此时你得确保清理逻辑能应对“资源处于半初始化/半失效”状态。

text=ZqhQzanResources