You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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实体脱离会话后的懒加载问题,同时更灵活地控制返回字段:

  1. 定义响应DTO:
from pydantic import BaseModel

class ShipmentResponse(BaseModel):
    id: int
    # 按需添加其他需要返回的字段,与Shipment ORM模型对应
    class Config:
        orm_mode = True
  1. 接口实现:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 13:12:51