FastAPI+SQLAlchemy 2正确保留数据库信息的实现方式
FastAPI + SQLAlchemy 2 异步创建接口标准实现
问题背景
我们近期将Python API技术栈切换为FastAPI和SQLAlchemy 2,尝试构建一个简易Post接口:接收请求体、处理后存入数据库,再返回插入的数据库实体。编写的代码如下:
async def create_new_shipment_with_full_data( session: AsyncSession, data: FullShipmentCreationRequestBody ) -> Shipment: async with session: shipment_data = data.model_dump(exclude_none=True) cursor = await session.execute(insert(Shipment).values(**shipment_data).returning(Shipment)) inserted_shipment = cursor.scalar() await session.commit() return inserted_shipment
运行后报错:MissingGreenlet("greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?"),原因是会话提交后实体数据丢失。尝试过将实例从会话中移除但无效,也曾通过将实体转为字典解决类似问题,但不确定这是否是最优方案,想请教这类接口的标准实现方式。
原因分析
会话提交后,SQLAlchemy异步会话会分离返回的ORM实体对象。此时如果实体存在延迟加载的字段,访问这些字段时会尝试触发数据库查询,但会话已关闭,没有活跃的Greenlet上下文,因此抛出MissingGreenlet错误。
标准实现方案
方案1:提交后刷新实体(适合简单场景)
在提交会话后,调用session.refresh()刷新实体,确保实体数据完全加载且不依赖会话上下文。同时注意不要手动嵌套async with session(FastAPI注入的会话通常由依赖管理生命周期,手动包裹会提前关闭会话):
async def create_new_shipment_with_full_data( session: AsyncSession, data: FullShipmentCreationRequestBody ) -> Shipment: shipment_data = data.model_dump(exclude_none=True) # 使用scalar直接获取实体 inserted_shipment = await session.scalar( insert(Shipment).values(**shipment_data).returning(Shipment) ) await session.commit() # 刷新实体,确保所有数据已加载且脱离会话后可访问 await session.refresh(inserted_shipment) return inserted_shipment
方案2:返回DTO(数据传输对象)(生产环境推荐)
定义Pydantic DTO类解耦API层与数据层,避免ORM实体脱离会话后的懒加载问题,同时更灵活地控制返回字段:
- 定义响应DTO:
from pydantic import BaseModel class ShipmentResponse(BaseModel): id: int # 按需添加其他需要返回的字段,与Shipment ORM模型对应 class Config: orm_mode = True
- 接口实现:
async def create_new_shipment_with_full_data( session: AsyncSession, data: FullShipmentCreationRequestBody ) -> ShipmentResponse: shipment_data = data.model_dump(exclude_none=True) inserted_shipment = await session.scalar( insert(Shipment).values(**shipment_data).returning(Shipment) ) await session.commit() # 将ORM实体转为DTO返回,自动处理数据转换 return ShipmentResponse.from_orm(inserted_shipment)
常见误区说明
- 手动调用
session.expunge(inserted_shipment)无法解决问题:分离实体只是解除与会话的关联,但实体中未加载的字段仍会尝试触发懒加载,导致报错。 - 直接返回字典是临时方案:虽然能解决报错,但缺乏类型校验和字段控制,不如DTO方案规范易维护。
内容的提问来源于stack exchange,提问作者Johan Kuylenstierna
相关产品推荐
相关产品推荐

