首页
/ FastAPI 中使用 dataclasses:用标准库数据类驱动请求体校验与响应模型

FastAPI 中使用 dataclasses:用标准库数据类驱动请求体校验与响应模型

2026-09-06 19:14:38作者:卓艾滢Kingsley

FastAPI 构建在 Pydantic 之上,但官方并不要求你必须定义 Pydantic BaseModel——仓库中的进阶文档(英文原版西班牙语版)说明了如何使用 Python 标准库的 dataclasses 以完全相同的姿势声明请求体、response_model 与嵌套数据结构。阅读本文后,你将掌握把已有 dataclass 代码直接接入 FastAPI 的三种实战写法,理解其底层如何经由 Pydantic 完成校验、序列化与 API 文档生成,并学会在嵌套结构场景下切换到 pydantic.dataclasses

从示例代码出发:dataclass 当请求体用

先看最简单的一种用法:直接把一个标准库 dataclass 用作路径操作函数的参数类型。完整代码见 docs_src/dataclasses_/tutorial001_py310.py

from dataclasses import dataclass

from fastapi import FastAPI


@dataclass
class Item:
    name: str
    price: float
    description: str | None = None
    tax: float | None = None


app = FastAPI()


@app.post("/items/")
async def create_item(item: Item):
    return item

这里没有出现任何 BaseModel,只有 Python 标准库的 @dataclass 装饰器。但 FastAPI 依旧能:

  • 数据校验(validation):把 JSON 请求体按 Item 的字段类型做解析与类型检查;
  • 数据序列化(serialization):把 dataclass 实例转成可传输的 JSON;
  • 数据文档(documentation):在自动生成的 OpenAPI schema 与交互式 API 文档中完整展示该结构。

之所以可行,原因正如文档所述:FastAPI 本身就构建在 Pydantic 之上,而 Pydantic 内置了对标准库 dataclasses 的支持——FastAPI 会把上面的标准 dataclass 悄悄"翻译"成 Pydantic 自己的 dataclass 风味再使用,即使你的业务代码里从未显式引用 Pydantic。

底层:FastAPI 如何识别一个 dataclass

从源码结构看,dataclass 之所以会被 FastAPI 当作"复杂结构"(请求体、模型字段)而非普通标量处理,关键在于兼容层对注解类型的判定逻辑。在 fastapi/_compat/shared.py 中:

def _annotation_is_complex(annotation: type[Any] | None) -> bool:
    return (
        lenient_issubclass(annotation, (BaseModel, Mapping, UploadFile))
        or _annotation_is_complex(annotation)   # 序列类型递归判定
        or is_dataclass(annotation)              # ← 标准库 dataclass 在此被识别
    )

(上面为便于理解做了改写;实际文件中该分支合并于 fastapi/_compat/shared.py 顶部 from dataclasses import is_dataclass 的调用链中,整体逻辑为:BaseModelMapping、序列类型或 is_dataclass(annotation) 均为 true 时即视为复杂注解。)一旦被判定为复杂类型,FastAPI 就会沿着与 Pydantic 模型相同的管线去创建字段与请求体模型,因此 dataclass 与 BaseModel 得到的是一套一致的待遇。

不要忽略的边界

文档用一条醒目提示给出了边界:

dataclasses 无法做到 Pydantic models 能做的一切,某些场景你可能仍然需要 Pydantic models。但如果你手头已经有一堆 dataclass,这是让它们直接为 FastAPI 驱动的 API 服务的好技巧。

换句话说,dataclass 方案适合"复用既有数据类"的场景;当需要 Pydantic 独有的高级能力(如 model_config、字段校验器、复杂的 Schema 定制等)时,仍应回到 BaseModel

dataclasses 放进 response_model

dataclass 不仅能做请求体,还能直接放进 response_model 参数,用于声明响应的结构与输出过滤。参考 docs_src/dataclasses_/tutorial002_py310.py

from dataclasses import dataclass, field

from fastapi import FastAPI


@dataclass
class Item:
    name: str
    price: float
    tags: list[str] = field(default_factory=list)
    description: str | None = None
    tax: float | None = None


app = FastAPI()


@app.get("/items/next", response_model=Item)
async def read_next_item():
    return {
        "name": "Island In The Moon",
        "price": 12.99,
        "description": "A place to be playin' and havin' fun",
        "tags": ["breater"],
    }

要点拆解:

  • 该 dataclass 会被自动转换为 Pydantic dataclass,其字段 schema 会出现在自动生成的 API 文档 UI 中;
  • 可变默认值必须使用 field(default_factory=list) 而非 tags: list[str] = [],否则会触发 Python 的"可变默认参数"陷阱;
  • 这里路径操作函数返回的是普通字典而非 dataclass 实例,FastAPI 会依据 response_model=Item 对其进行输出转换。仓库测试 tests/test_tutorial/test_dataclasses/test_tutorial002.py 证实:即使返回字典里没有 tax 键,序列化后的 JSON 仍会包含 "tax": None——这正是"经过 dataclass 模型转换"留下的痕迹。

下面的截图取自仓库 docs/en/docs/img/tutorial/dataclasses/image01.png,展示了 /items/next 接口在自动生成的 API 文档中的响应 Schema 与示例值——nameprice 为必填,tags 为字符串数组,descriptiontax 可空:

FastAPI 自动文档中 response_model 使用 dataclass 时展示的 Item 响应 Schema 与示例

对应的 OpenAPI JSON 里,Item 会被登记为 components/schemas/Item,其中 required: ["name", "price"],其余可空字段以 anyOf 组合 null 表达,与 Pydantic 模型生成的 schema 完全同构(见上述测试的 test_openapi_schema 断言)。

dataclasses 用于嵌套数据结构

dataclass 还可以与其它类型注解自由组合,构造出嵌套数据结构。不过文档特别指出:在个别场景(例如自动生成的 API 文档报错)下,你仍需要使用 Pydantic 版的 dataclass。此时只需把标准库的 dataclasses 换成 pydantic.dataclasses——它是开箱即用的直接替换(drop-in replacement)。

完整示例见 docs_src/dataclasses_/tutorial003_py310.py

from dataclasses import field  # (1)

from fastapi import FastAPI
from pydantic.dataclasses import dataclass  # (2)


@dataclass
class Item:
    name: str
    description: str | None = None


@dataclass
class Author:
    name: str
    items: list[Item] = field(default_factory=list)  # (3)


app = FastAPI()


@app.post("/authors/{author_id}/items/", response_model=Author)  # (4)
async def create_author_items(author_id: str, items: list[Item]):  # (5)
    return {"name": author_id, "items": items}  # (6)


@app.get("/authors/", response_model=list[Author])  # (7)
def get_authors():  # (8)
    return [  # (9)
        {
            "name": "Breaters",
            "items": [
                {
                    "name": "Island In The Moon",
                    "description": "A place to be playin' and havin' fun",
                },
                {"name": "Holy Buddies"},
            ],
        },
        {
            "name": "System of an Up",
            "items": [
                {"name": "Salt", "description": "The kombucha mushroom people's favorite"},
                {"name": "Pad Thai"},
                {"name": "Lonely Night", "description": "The mostests lonliest nightiest of allest"},
            ],
        },
    ]

文档对这段代码给出了 9 条逐行说明,逐条翻译整理如下:

  1. 第 1 行field 仍然从标准库 dataclasses 导入(Pydantic 版 dataclass 同样基于标准库机制,field 可复用)。
  2. 第 4 行pydantic.dataclasses 是标准库 dataclasses 的直接替换品,用法一致。
  3. 第 16 行Author dataclass 内含一个 Item dataclass 列表,形成一层嵌套。
  4. 第 22 行Author dataclass 被用作 response_model 参数。
  5. 第 23 行:可以把 dataclass 与其它标准类型注解组合起来声明请求体——这里请求体是 Item dataclass 的列表(list[Item])。
  6. 第 24 行:函数返回一个字典,内含 items(dataclass 列表)。FastAPI 仍然有能力把数据序列化成 JSON。
  7. 第 27 行response_model 使用 list[Author] 这种"类型注解 + dataclass"的组合,再次验证 dataclass 可与标准类型注解自由搭配。
  8. 第 28 行:这条路径操作函数用的是普通 def 而非 async def。在 FastAPI 中你可以按需混用 defasync def;关于如何取舍可回顾 async 与 await 相关文档
  9. 第 29-53 行:这条函数并没有直接返回 dataclass 实例(尽管可以),而是返回嵌套的字典列表。FastAPI 会依据包含 dataclass 的 response_model 参数去转换并校验响应。

仓库测试 tests/test_tutorial/test_dataclasses/test_tutorial003.py 验证了两个端点的完整行为:

  • POST /authors/foo/items/:请求体 [{"name": "Bar"}, {"name": "Baz", "description": "Drop the Baz"}] 返回 200,响应中缺失的 description 会被填充为 None
  • GET /authors/:返回两段字典列表数据,JSON 中每个缺省 description 的条目都出现 "description": None
  • OpenAPI schema 中,Author(必填 nameitems 引用 Item)、Item(必填 namedescription 可空)都被登记在 components/schemas 下,请求体为 Item 数组,GET 响应为 Author 数组。

该测试同时表明:这类用 Pydantic dataclass 组合出的嵌套模型,其 OpenAPI 文档生成完全正常,这正对应文档中"遇到自动生成文档报错时才需要换用 pydantic.dataclasses"的提示。

不同写法与不同调用路径的取舍

把三种写法放在一起对比,可以得到清晰的选用原则:

场景 推荐写法 参考文件
请求体直接用标准 dataclass from dataclasses import dataclass tutorial001_py310.py
response_model 使用 dataclass 标准 dataclass + response_model=Item tutorial002_py310.py
嵌套结构 / 自动文档报错时 from pydantic.dataclasses import dataclass tutorial003_py310.py

补充两点从仓库代码可以确认的实现细节:

  • 序列化路径:当路径操作函数直接返回 dataclass 实例(不经过 response_model 强制转换)时,FastAPI 的 jsonable_encoder 会走 dataclass 专门分支——先用 dataclasses.is_dataclass(obj) 判断,再通过 dataclasses.asdict(obj) 转成字典后继续递归编码,见 fastapi/encoders.py(约 259-272 行)。这保证任何 dataclass(包括嵌套字段)都能稳定输出为 JSON。
  • 复杂注解判定:请求体是否需要解析为"复杂模型",取决于 fastapi/_compat/shared.pyis_dataclass(annotation) 的判定;同时 fastapi/_compat/v2.py 在构造模型字段时也会用 is_dataclass(type_) 把 dataclass 归入需要完整建模的类型。正因为这两处判定,dataclass 才获得了与 Pydantic 模型一致的请求体识别、校验与文档生成能力。

更多扩展与版本信息

文档的 "Learn More" 一节提示:你还可以把 dataclass 与其它 Pydantic 模型组合、从它们继承、把它们放进自己的模型中,等等。想深入了解 Pydantic 对 dataclass 的完整支持细节(含与 BaseModel 混用、配置项等),可直接查阅 Pydantic 官方文档中关于 dataclasses 的章节。

关于可用性:此能力自 FastAPI 0.67.0 版本起提供。也就是说,如果你的项目锁定的 FastAPI 低于该版本,请先升级;由于该能力依赖 Pydantic 内置的 dataclass 支持,使用时也需保证当前安装的 Pydantic 版本与之兼容。结合仓库中的官方示例与测试(docs_src/dataclasses_tests/test_tutorial/test_dataclasses),你可以把上述代码直接复制运行,在 uvicorn 启动后访问 /docs 观察每个 dataclass 生成的 Schema——一套无需 BaseModel 的完整请求-响应建模流程就此打通。

登录后查看全文
热门项目推荐
相关项目推荐