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

FastAPI+SQLAlchemy返回查询结果触发ValueError问题求助

问题:FastAPI返回SQLAlchemy子查询结果时序列化失败

使用FastAPI+SQLAlchemy开发时,查询逻辑能正常输出数据,但返回给前端时触发序列化错误。

核心查询代码

def get_events_by_month(db: Session, month: int):    
    sq = db.query(models.Events.title, models.Events.start, over(func.row_number(), partition_by=models.Events.start).label('rn')).subquery()
    print(sq)
    q = db.query(sq.c.title, sq.c.start).where(sq.c.rn <= 3).all()
    print(q)
    return q

注:print(sq)输出正确的查询语句,print(q)能打印出期望的元组列表,但return q时触发报错。

报错信息

ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/encoders.py", line 322, in jsonable_encoder
    data = dict(obj)
ValueError: dictionary update sequence element #0 has length 38; 2 is required

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/encoders.py", line 327, in jsonable_encoder
    data = vars(obj)
TypeError: vars() argument must have __dict__ attribute

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/uvicorn/protocols/http/httptools_impl.py", line 411, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__
    return await self.app(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/middleware/cors.py", line 83, in __call__
    await self.app(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/routing.py", line 758, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/routing.py", line 778, in app
    await route.handle(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/routing.py", line 299, in handle
    await self.app(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/routing.py", line 79, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/starlette/routing.py", line 74, in app
    response = await func(request)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/routing.py", line 296, in app
    content = await serialize_response(
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/routing.py", line 180, in serialize_response
    return jsonable_encoder(response_content)
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/encoders.py", line 301, in jsonable_encoder
    jsonable_encoder(
  File "/home/mortyar/Desktop/coding projects/calendar-next14-fastapi/backend/lib/python3.10/site-packages/fastapi/encoders.py", line 330, in jsonable_encoder
    raise ValueError(errors) from e
ValueError: [ValueError('dictionary update sequence element #0 has length 38; 2 is required'), TypeError('vars() argument must have __dict__ attribute')]

已尝试的解决方法

  • 手动将元组转换为字典,未成功
  • 定义响应模型但未生效:
class EventsBase(BaseModel):
    title: Optional[str]
    start: Optional[date]

关联代码

端点定义

@app.get("/events/{month}")
def get_events_by_month(month: int, db: Session = Depends(get_db)):
    return crud.get_events_by_month(db, month)

数据库模型

class Events(Base):
    __tablename__ = "events"
    __table_args__ = (UniqueConstraint("start", "url", name="event"),)

    id = Column(Integer, primary_key=True, index=True, autoincrement=True)
    title = Column((VARCHAR(300)))
    start = Column(DATE, index=True, nullable=False)
    end = Column(DATE)
    registry = Column(VARCHAR(50), index=True)
    city = Column(VARCHAR(50))
    location = Column(VARCHAR(10), index=True)
    discipline = Column(VARCHAR(50), index=True)
    url = Column(VARCHAR(500), nullable=False)

解决方案

问题本质是SQLAlchemy子查询返回的Row对象无法被FastAPI的jsonable_encoder正确序列化,以下是三种可行的解决方式:

方式一:手动转换为字典列表

修改查询函数,将结果逐个转换为字典:

def get_events_by_month(db: Session, month: int):    
    sq = db.query(models.Events.title, models.Events.start, over(func.row_number(), partition_by=models.Events.start).label('rn')).subquery()
    q = db.query(sq.c.title, sq.c.start).where(sq.c.rn <= 3).all()
    return [{"title": item.title, "start": item.start} for item in q]

方式二:正确配置响应模型

在端点中明确指定响应模型,并给模型添加ORM兼容配置:

from pydantic import BaseModel, Optional
from datetime import date

class EventsBase(BaseModel):
    title: Optional[str]
    start: Optional[date]

    class Config:
        orm_mode = True  # SQLAlchemy 2.0+也可使用from_attributes=True

# 修改端点,指定响应模型为列表类型
@app.get("/events/{month}", response_model=list[EventsBase])
def get_events_by_month(month: int, db: Session = Depends(get_db)):
    return crud.get_events_by_month(db, month)

方式三:使用SQLAlchemy内置方法转换

利用Row对象的_asdict()方法直接转换为字典:

def get_events_by_month(db: Session, month: int):    
    sq = db.query(models.Events.title, models.Events.start, over(func.row_number(), partition_by=models.Events.start).label('rn')).subquery()
    q = db.query(sq.c.title, sq.c.start).where(sq.c.rn <= 3).all()
    return [item._asdict() for item in q]

内容的提问来源于stack exchange,提问作者corgipower

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 21:34:58