小能豆

为什么我必须迭代 SQLAlchemy 对象才能在 FastAPI 中返回它?

py

GET端点不起作用:

@app.get("/question/", response_model=list[schemas.QuizSchema])
def get_questions(db: Session = Depends(get_db)):
    quiz = db.query(models.Quiz).all()
    return jsonable_encoder(quiz)

它会引发以下错误:

raise ValidationError(errors, field.type_)
pydantic.error_wrappers.ValidationError: 3 validation errors for QuizSchema
response -> 0 -> category
  field required (type=value_error.missing)
response -> 0 -> answer
  field required (type=value_error.missing)
response -> 0 -> question
  field required (type=value_error.missing)

但是这段代码工作得很好:

@app.get("/question/", response_model=list[schemas.QuizSchema])
def get_questions(db: Session = Depends(get_db)):
    quiz = db.query(models.Quiz).all()
    for x in quiz:
        print(x.id, x.category.id, x.answer[0].id, x.question.id)
    return jsonable_encoder(quiz)

我真的不明白这是怎么回事。

我在for-loop 之前和之后使用了一个断点:

@app.get("/question/", response_model=list[schemas.QuizSchema])
def get_questions(db: Session = Depends(get_db)):
    quiz = db.query(models.Quiz).all()
    breakpoint()
    for x in quiz:
        print(x.id, x.category.id, x.answer[0].id, x.question.id)
    breakpoint()
    return jsonable_encoder(quiz)

那是结果:

# FIRST BREAKPOINT
(Pdb) quiz
[<models.Quiz object at 0x7fb81957c5e0>]
(Pdb) jsonable_encoder(quiz)
[{'id': 2}]
(Pdb) continue
2 3 1 1
> /backend/main.py(29)get_questions()
-> return jsonable_encoder(quiz)

#SECOND BREAKPOINT
(Pdb) quiz
[<models.Quiz object at 0x7fb81957c5e0>]
(Pdb) jsonable_encoder(quiz)
[{'id': 2, 'category': {'quiz_category_id': 2, 'id': 3, 'name': 'foo'}, 'answer': [{'name': 'zdecydowanie nie', 'value': -2, 'id': 1}, {'name': 'raczej nie', 'value': -1, 'id': 2}, {'name': 'nie wiem', 'value': 0, 'id': 3}, {'name': 'raczej tak', 'value': 1, 'id': 4}, {'name': 'zdecydowanie tak', 'value': 2, 'id': 5}], 'question': {'name': 'bar', 'quiz_question_id': 2, 'id': 1}}]
(Pdb) 

我的 SQLAlchemy 模型如下所示:

class Quiz(Base):
    __tablename__ = "quiz"

    id: Mapped[int] = mapped_column(Integer, primary_key=True)
    category: Mapped["Category"] = relationship(back_populates="quiz_category")
    answer: Mapped[list[Answer]] = relationship(secondary=quiz_answer)
    question: Mapped["Question"] = relationship(back_populates="quiz_question")

阅读 104

收藏
2023-06-04

共2个答案

小能豆

根据你提供的信息,看起来问题可能与 Pydantic 模型的验证有关。错误消息表明在 QuizSchema 模型中,categoryanswerquestion 字段被标记为必需字段,但在返回的数据中缺少这些字段。

在你的代码中,你使用了 SQLAlchemy 的模型来定义数据库结构,并与 Pydantic 模型一起使用来定义 API 的输入和输出。由于 Pydantic 模型具有验证功能,如果模型中的字段缺失或类型不匹配,将引发 ValidationError

根据你提供的断点信息,当你在第一个断点处查看 jsonable_encoder(quiz) 的结果时,只返回了 {'id': 2},而缺少了其他字段。然而,在第二个断点处,jsonable_encoder(quiz) 返回了完整的包含所有字段的结果。

这可能是由于你的 SQLAlchemy 模型的延迟加载(lazy loading)特性引起的。在第一个断点处,当你访问 quiz 对象时,只有 id 字段被立即加载,而其他关联对象(如 categoryanswerquestion)是延迟加载的。因此,Pydantic 在验证时无法找到这些字段,引发了验证错误。

要解决这个问题,你可以考虑在查询 quiz 对象时,使用 SQLAlchemy 的 options 来显式加载关联对象。例如,使用 optionsjoinedload 方法加载关联对象,以确保在验证期间这些字段也被加载:

from sqlalchemy.orm import joinedload

quiz = db.query(models.Quiz).options(
    joinedload(models.Quiz.category),
    joinedload(models.Quiz.answer),
    joinedload(models.Quiz.question)
).all()

这样,在使用 jsonable_encoder(quiz) 时,所有关联字段都将被加载,并且可以在 Pydantic 模型验证期间找到它们。

另外,请确保你的 SQLAlchemy 模型中的字段与 Pydantic 模型中的字段名称完全匹配,包括大小写。验证错误也可能是由于字段名称不匹配导致的。

2023-06-04
小能豆

模式应该有它应该有的配置类orm_mode = True

class QuizSchema(BaseModel):
  .... field 
  class Config(): 
     orm_mode = True
2023-06-04