FastAPI Lifespan关闭会话触发AttributeError问题排查与解决
问题:FastAPI服务器关闭时优雅释放资源失败的类型错误分析与解决
依赖版本
- fastapi(0.95.0)
- fastapi-users(10.4.2)
- fastapi-users-db-sqlalchemy(5.0.0)
- SQLAlchemy(2.0.10)
简化代码片段
engine = create_async_engine(SQLALCHEMY_DATABASE_URL) async_session_maker = async_sessionmaker(engine, expire_on_commit=False) async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async with async_session_maker() as session: yield session async def get_user_db(session: AsyncSession = Depends(get_async_session)): yield SQLAlchemyUserDatabase(session, UserModel, OAuthAccount) @asynccontextmanager async def lifespan(fapp: FastAPI): # establish a connection to the database fapp.state.async_session = await get_user_db().__anext__() yield # close the connection to the database await fapp.state.async_session.close() await fapp.state.async_session.engine.dispose() app = FastAPI(lifespan=lifespan) # Add Routes # ... if __name__ == '__main__': import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
错误现象
第一次错误(直接调用close())
按下Ctrl-C停止服务器时出现以下错误:
INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) ^CINFO: Shutting down INFO: Waiting for application shutdown. <class 'fastapi_users_db_sqlalchemy.SQLAlchemyUserDatabase'> ERROR: Traceback (most recent call last): File "/path/to/proj/env/lib/python3.10/site-packages/starlette/routing.py", line 677, in lifespan async with self.lifespan_context(app) as maybe_state: File "/usr/lib/python3.10/contextlib.py", line 206, in __aexit__ await anext(self.gen) File "/path/to/proj/src/main.py", line 40, in lifespan await fastapi_app.state.async_session.close() AttributeError: 'SQLAlchemyUserDatabase' object has no attribute 'close' ERROR: Application shutdown failed. Exiting. INFO: Finished server process [37752]
第二次错误(调用session.close())
修改为await fapp.state.async_session.session.close()后,再次停止服务器出现:
INFO: Started server process [33125] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) ^CINFO: Shutting down INFO: Waiting for application shutdown. ERROR: Traceback (most recent call last): File "/path/to/proj/env/lib/python3.10/site-packages/starlette/routing.py", line 677, in lifespan async with self.lifespan_context(app) as maybe_state: File "/path/to/proj/env/lib/python3.10/site-packages/starlette/routing.py", line 569, in __aexit__ await self._router.shutdown() File "/path/to/proj/env/lib/python3.10/site-packages/starlette/routing.py", line 664, in shutdown await handler() File "/path/to/proj/src/main.py", line 88, in shutdown await app.state.async_session.session.close() AttributeError: 'Depends' object has no attribute 'close' ERROR: Application shutdown failed. Exiting.
错误原因分析
- 第一个错误根源:
get_user_db()是生成器函数,调用__anext__()获取的是SQLAlchemyUserDatabase实例,而非AsyncSession。这个类仅封装数据库会话,本身没有close()方法,直接调用会报错。 - 第二个错误根源:手动调用
get_user_db()时未传入实际的AsyncSession参数,导致SQLAlchemyUserDatabase内部的session属性被赋值为Depends(get_async_session)对象(而非真实会话实例),因此访问session.close()会触发Depends对象无该属性的错误。get_user_db设计用于FastAPI的依赖注入系统,手动调用无法自动解析Depends参数。
解决方案
修正lifespan函数,直接管理AsyncSession和engine的生命周期,避免通过依赖注入函数手动获取数据库会话:
engine = create_async_engine(SQLALCHEMY_DATABASE_URL) async_session_maker = async_sessionmaker(engine, expire_on_commit=False) async def get_async_session() -> AsyncGenerator[AsyncSession, None]: async with async_session_maker() as session: yield session async def get_user_db(session: AsyncSession = Depends(get_async_session)): yield SQLAlchemyUserDatabase(session, UserModel, OAuthAccount) @asynccontextmanager async def lifespan(fapp: FastAPI): # 直接创建AsyncSession实例 async with async_session_maker() as session: fapp.state.async_session = session # 若需全局可用的user_db实例,可在此创建并存入state fapp.state.user_db = SQLAlchemyUserDatabase(session, UserModel, OAuthAccount) yield # 关闭会话 await session.close() # 释放数据库引擎资源 await engine.dispose() app = FastAPI(lifespan=lifespan) # Add Routes # ... if __name__ == '__main__': import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
关键修正点
- 直接通过
async_session_maker()创建AsyncSession,绕开依赖注入函数的手动调用问题,确保拿到真实的会话实例。 - 关闭资源顺序:先关闭会话,再调用
engine.dispose()释放所有连接池资源,保证优雅 shutdown。 - 原有的
get_user_db函数仍可用于路由的依赖注入,不影响业务逻辑。
内容的提问来源于stack exchange,提问作者Homunculus Reticulli
相关产品推荐
相关产品推荐

