相当于 Pydantic/FastAPI 的 Marshmallow dump_only 字段,没有多个模式

Wil*_*aly 5 python marshmallow pydantic fastapi

问题

是否可以dump_only使用 FastAPI 的 pydantic 复制 Marshmallow 的功能,以便某些字段是“只读”,而不需要为序列化和反序列化定义单独的模式?

语境

有时,给定 API 资源的属性子集(例如idcreated_date)应该是只读的,并且应该在反序列化期间从请求有效负载中忽略(例如,当 POST 到集合或 PUT 到现有资源时),但需要对于这些相同的请求,将在响应正文中与该架构一起返回。

Marshmallow 提供了一个方便的dump_only参数,只需要为序列化和反序列化定义一个模式,并可以选择从任一操作中排除某些字段。

现有解决方案

我见过的在 FastAPI 中复制此功能的大多数尝试(即FastAPI 文档GitHub 问题相关 SO 问题)倾向于为输入(反序列化)和输出(序列化)定义单独的模式,并为之间的共享字段定义一个通用的基本模式。他们俩。

根据我目前对这种方法的理解,由于以下几个原因,它似乎有点不方便:

  1. 它要求 API 开发人员为每个模式保留单独的命名空间,而将公共字段抽象为第三个“基”模式类的做法会加剧这个问题。
  2. 它导致具有嵌套资源的 API 中模式类的激增,因为每个级别的嵌套都需要单独的输入和输出模式。
  3. 当该 API 的使用者只需要知道单个模式时,符合 OAS 的文档将输入/输出模式显示为单独的定义,因为这些只读字段的(反)序列化应由API。

例子

假设我们正在为具有以下模型的调查开发一个简单的 API:

from sqlalchemy.orm import declarative_base, relationship
from sqlalchemy import (
    func,
    Column,
    Integer,
    String,
    DateTime,
    ForeignKey,
)

Base = declarative_base()


class SurveyModel(db.Base):
    """Table that represents a collection of questions"""

    __tablename__ = "survey"

    # columns
    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, nullable=False)
    created_date = Column(DateTime, default=func.now())

    # relationships
    questions = relationship("Question", backref="survey")


class QuestionModel(Base):
    """Table that contains the questions that comprise a given survey"""

    __tablename__ = "question"

    # columns
    id = Column(Integer, primary_key=True, index=True)
    survey_id = Column(Integer, ForeignKey("survey.id"))
    text = Column(String)
    created_date = Column(DateTime, default=func.now())
Run Code Online (Sandbox Code Playgroud)

我们希望POST /surveys端点能够接受请求正文中的以下有效负载:

{
    "name": "First Survey",
    "questions": [
        {"text": "Question 1"},
        {"text": "Question 2"}
    ]
}
Run Code Online (Sandbox Code Playgroud)

并在响应正文中返回以下内容:

{
    "id": 1,
    "name": "First Survey",
    "created_date": "2021-12-12T00:00:30",
    "questions": [
        {
             "id": 1,
             "text": "Question 1",
             "created_date": "2021-12-12T00:00:30"
        },
        {
             "id": 2,
             "text": "Question 2",
             "created_date": "2021-12-12T00:00:30"
        },
    ]
}
Run Code Online (Sandbox Code Playgroud)

除了像这样定义模式之外,还有其他方法可以使两者都id成为created_date只读模式吗?QuestionModelSurveyModel

{
    "name": "First Survey",
    "questions": [
        {"text": "Question 1"},
        {"text": "Question 2"}
    ]
}
Run Code Online (Sandbox Code Playgroud)

为了进行比较,这里是使用棉花糖的等效模式:

from marshmallow import Schema, fields


class Question(Schema):
    id = fields.Integer(dump_only=True)
    created_date = fields.DateTime(dump_only=True)
    text = fields.String(required=True)

class Survey(Schema):
    id = fields.Integer(dump_only=True)
    created_date = fields.DateTime(dump_only=True)
    name = fields.String(required=True)
    questions = fields.List(fields.Nested(Question))
Run Code Online (Sandbox Code Playgroud)

参考

  1. Marshmallow 只读/仅加载字段
  2. 现有的堆栈交换问题
  3. FastAPI 存储库上的只读字段问题
  4. 有关模式的 FastAPI 文档

lig*_*lig 0

根据pydantic.Field文档:

\n
    \n
  • init_var\xc2\xad\xe2\x80\x94 该字段是否应包含在数据类的构造函数中。
  • \n
  • exclude\xe2\x80\x94 是否从模型序列化中排除该字段。
  • \n
\n

因此,要从 Pydantic 中的方法处理的“反序列化”中排除某个字段,设置为BaseModel.__init__就足够了。init_varFalse

\n

在 Pydantic 中重写示例将导致如下结果:

\n
from datetime import datetime\n\nfrom Pydantic import BaseModel, Field\n\n\nclass Question(BaseModel):\n    id: int = Field(init_var=False)\n    created_date: datetime = Field(init_var=False)\n    text: str\n\nclass Survey(BaseModel):\n    id: int = Field(init_var=False)\n    created_date: datetime = Field(init_var=False)\n    name: str\n    questions: list[Question]\n
Run Code Online (Sandbox Code Playgroud)\n