FastAPI 请求体(Request Body)详解:基于 Pydantic 模型的声明、校验与自动文档实战
导读
本篇技术指南以 FastAPI 官方教程中「Request Body(请求体)」章节为骨架,完整讲解如何在 FastAPI 中通过 Pydantic 模型声明、接收并校验客户端提交的请求体数据,并剖析其在当前仓库源码中的解析与 OpenAPI 生成原理。读完本文你将掌握:如何用几行 Python 类型标注完成请求体的 JSON 解析、类型转换、数据校验与编辑器智能提示,如何将请求体与路径参数、查询参数混合使用,以及请求体校验失败时 FastAPI 返回标准化 422 错误的底层机制。
什么是请求体(Request Body)
当你需要从客户端(例如一个浏览器或移动 App)向 API 发送数据时,这些数据通常以 request body(请求体) 的形式传递。与之对应,API 返回给客户端的数据称为 response body(响应体):
- 请求体(request body):客户端发送给 API 的数据;
- 响应体(response body):API 返回给客户端的数据。
API 几乎总是需要返回响应体,但客户端并非每次都要发送请求体——有时只访问一个路径,可能附带几个查询参数(query),却不发送任何 body。
注意:要发送请求体数据,应使用以下 HTTP 方法之一:
POST(最常见)、PUT、DELETE或PATCH。HTTP 规范并未定义在GET请求中发送 body 的行为,尽管 FastAPI 出于极少数复杂/极端场景仍予以支持,但由于官方并不推荐,Swagger UI 交互式文档在GET请求下不会展示该 body 的文档,且中间的代理服务器也可能不支持这种用法。
在 FastAPI 中声明请求体,依靠的是 Pydantic 模型及其全部能力。仓库中对应的官方教程源码位于 docs_src/body/,本文后续所有示例均可在该目录下找到可运行的 tutorial00x_py310.py 文件。
第一步:从 Pydantic 导入 BaseModel
要声明请求体,首先从 pydantic 导入 BaseModel:
from fastapi import FastAPI
from pydantic import BaseModel
源码出处:docs_src/body/tutorial001_py310.py。
第二步:创建你的数据模型
然后,将你的数据模型声明为一个继承 BaseModel 的类,类的所有属性均使用 Python 标准类型标注。下面的 Item 模型覆盖了 str、str | None、float、float | None 四类字段:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
与声明查询参数时的规则一致:模型属性带有默认值时该字段为可选项,否则为必填项;将默认值设为 None 即可让字段变成纯可选。
例如,上面的模型声明了一个 JSON "object"(Python 中的 dict),形如:
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
由于 description 与 tax 是可选的(默认值为 None),下面的 JSON 同样是合法请求体:
{
"name": "Foo",
"price": 45.2
}
这种"默认值决定必填性"的设计最终会反映到自动生成的 OpenAPI Schema 中。仓库测试 tests/test_tutorial/test_body/test_tutorial001.py 对 Item 生成的 JSON Schema 快照显示,required 数组中只有 ["name", "price"],而 description、tax 被建模为 anyOf 中允许 null 的可选属性。
第三步:把模型声明为路径操作函数的参数
将数据模型加入 path operation,方法与声明路径参数、查询参数完全一致——直接在函数签名中声明参数,并标注类型为刚刚创建好的模型类 Item:
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
return item
完整源码见 docs_src/body/tutorial001_py310.py。这里的 async def 也可换成普通的 def,FastAPI 对两者都支持。
一次类型声明,FastAPI 自动完成的七件事
仅凭上面这一个 Python 类型标注,FastAPI 就会自动为你完成以下工作:
- 读取请求体并将其解析为 JSON;
- 类型转换(必要时),例如把 JSON 字符串
"50.5"转换成float; - 校验数据,若数据非法,返回清晰明确的错误,精确指出出错位置与原因;
- 把接收到的数据注入
item参数。由于参数被标注为Item类型,编辑器会对该对象的所有属性及其类型提供完整补全(autocompletion)支持——这是直接接收dict无法获得的体验; - 为模型生成 JSON Schema,可在项目其他有意义的场景复用;
- 这些 Schema 会并入自动生成的 OpenAPI schema;
- OpenAPI schema 又被 Swagger UI 等自动文档 UI 使用,从而免去手写接口文档的负担。
自动文档:JSON Schema 如何呈现给客户端
模型生成 JSON Schema 后,会在 /docs 的交互式 API 文档中直观呈现。下图为数据模型部分对 Item(含必填标记 name*、price*)以及自动生成的 ValidationError、HTTPValidationError 错误模型的可视化展示:
同时,每个使用该模型的 path operation 的文档中也会显示对应的请求体区域,包括 required 标识、application/json 媒体类型以及可直接填写的示例值:
这两张截图对应的原始引用位于 docs/en/docs/tutorial/body.md 的自动文档章节,实际文档图片存放在 docs/en/docs/img/tutorial/body/。从 OpenAPI 层面看,仓库测试 tests/test_tutorial/test_body/test_tutorial001.py 对 /openapi.json 返回的 schema 做了完整快照断言:请求体的 requestBody 被标记为 "required": True,其内容类型为 application/json,schema 通过 $ref 指向 #/components/schemas/Item——这正是 Swagger UI 渲染出上图界面的数据来源。
编辑器支持与类型安全
在函数体内,由于参数被标注为 Item 模型类型,编辑器会提供全量的类型标注与自动补全;若你对属性执行了错误类型的操作(例如对 str 调用数值运算),编辑器会直接给出错误提示。这种开发体验并非偶然——FastAPI 整个框架正是围绕 Python 类型标注这一设计原则构建的:在正式实现之前,团队曾在设计阶段进行过严格验证,以确保类型系统能与各类主流编辑器协同工作,甚至为支持这一设计对 Pydantic 做出过相应调整。上述截图均来自 Visual Studio Code,但 PyCharm 及绝大多数主流 Python 编辑器都能获得同样的支持。若使用 PyCharm,还可以搭配 Pydantic PyCharm 插件,获得针对 Pydantic 模型的自动补全、类型检查、重构、搜索与代码检查等增强能力。
在函数体内使用模型:以含税价格计算为例
一旦 FastAPI 将请求体注入参数,就可以像操作普通 Python 对象一样直接访问模型的每个属性。下面的 tutorial002 演示了如何计算含税价格:先用 model_dump() 把模型转为字典,再在 tax 存在时追加 price_with_tax 字段并返回:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.post("/items/")
async def create_item(item: Item):
item_dict = item.model_dump()
if item.tax is not None:
price_with_tax = item.price + item.tax
item_dict.update({"price_with_tax": price_with_tax})
return item_dict
完整源码见 docs_src/body/tutorial002_py310.py。注意:本例使用 Pydantic v2 风格的方法 item.model_dump() 将模型序列化为字典;返回的字典会被 FastAPI 自动转换为 JSON 响应。仓库中对应测试为 tests/test_tutorial/test_body/test_tutorial002.py。
请求体 + 路径参数:同时声明
你可以同时声明路径参数与请求体。FastAPI 会智能识别:与路径(path)匹配的函数参数从 URL 路径中取值,声明为 Pydantic 模型的参数则从请求体中取值:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.model_dump()}
源码见 docs_src/body/tutorial003_py310.py。该接口的 OpenAPI 文档同时生成了 item_id 路径参数和 Item 请求体;仓库测试 tests/test_tutorial/test_body/test_tutorial003.py 验证了 PUT /items/123 携带完整 JSON 时返回 item_id 与模型字段合并的结果,也验证了只提交必填字段、或提交空对象 {} 时分别返回 200 与 422 的行为。
请求体 + 路径参数 + 查询参数:三合一
更进一步,你可以同时声明 body、path、query 三种参数,FastAPI 会分别识别并各自从正确的位置取值:
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
app = FastAPI()
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, q: str | None = None):
result = {"item_id": item_id, **item.model_dump()}
if q:
result.update({"q": q})
return result
源码见 docs_src/body/tutorial004_py310.py。函数参数的具体识别规则如下:
- 若参数同时出现在 path 中,则作为路径参数使用;
- 若参数是单一类型(如
int、float、str、bool等),则解释为查询参数; - 若参数声明为 Pydantic 模型类型,则解释为请求体。
可选性判定:默认值优先于类型标注
在混合参数的例子中,FastAPI 之所以知道 q 不是必填的,是因为它带有默认值 = None。这里有一个容易混淆的细节:str | None 这个联合类型标注本身并不会让 FastAPI 认为参数可选,参数是否为必填只取决于它是否有默认值。不过加上完整的类型标注仍非常值得——它能让你使用的编辑器提供更好的智能提示并及早发现类型错误。
底层原理:请求体在 FastAPI 中如何被解析
理解了"如何用",再看"为何如此"。在仓库源码中,请求体的处理链路清晰可循:
- 参数分类:FastAPI 在 fastapi/dependencies/utils.py 中依据参数来源将其归类。源码函数
request_body_to_args()(见 fastapi/dependencies/utils.py)专门负责把解析后的请求数据映射回 Pydantic 模型字段,并据此生成校验错误。 - 是否嵌入 body 的判定:函数
_should_embed_body_fields()(见 fastapi/dependencies/utils.py)与_get_body_field()(见 fastapi/dependencies/utils.py)决定当函数只有一个 body 模型时,是把整个 JSON 对象直接交给该模型(这正是本文所有示例的行为),还是需要额外包裹一层——后者对应多 body 参数的"嵌入式"场景。 - Body 参数的底层默认值:FastAPI 在 fastapi/params.py 中定义了
Body类,其默认media_type为application/json,并支持embed、alias、gt/ge/lt/le、min_length/max_length/pattern等约束参数。当请求的Content-Type不是 JSON(如发送表单编码数据)时,校验将失败——这一点被测试 tests/test_tutorial/test_body/test_tutorial001.py 覆盖。 - OpenAPI 生成:fastapi/openapi/utils.py 依据
body_field及其字段信息生成requestBody的媒体类型、schema 引用与required标记,最终呈现为前文测试快照中的 OpenAPI 3.1.0 结构。
关于校验失败时的错误形态,仓库测试 tests/test_tutorial/test_body/test_tutorial001.py 给出了丰富的实证:缺少必填字段返回 422 且错误 loc 指向 ["body", "price"];price 传字符串 "twenty" 返回 float_parsing 类型错误;空 body 或无 Content-Type 的请求也会被拒绝;而 application/geo+json 这类合法的 JSON 媒体类型则能正常通过——说明 FastAPI 对 +json 后缀的媒体类型同样宽容处理。
不使用 Pydantic 时的替代方案
如果不想使用 Pydantic 模型,也可以直接使用 Body 参数声明单个标量值。相关内容请见教程 Body - 多个参数:请求体中的单个值(对应英文原文为 docs/en/docs/tutorial/body-multiple-params.md,本指南来源的西班牙语版本见 docs/es/docs/tutorial/body-multiple-params.md),其中详细说明了如何用 Body(...) 把单个值放入请求体并携带约束条件。此外,若继续阅读 docs/zh/docs/tutorial/body-multiple-params.md 之后的系列章节,你还会看到单值 body 与多个 Pydantic 模型共存时 FastAPI 如何自动"嵌入"参数,这正是前文 _should_embed_body_fields() 所对应的用户可见行为。
小结
在本仓库中,请求体的全部官方示例源码集中在 docs_src/body/(tutorial001_py310.py 至 tutorial004_py310.py),配套测试位于 tests/test_tutorial/test_body/,底层解析实现则分布在 fastapi/dependencies/utils.py、fastapi/openapi/utils.py 与 fastapi/params.py。掌握了本文的声明范式,你即可用统一的类型标注风格优雅地组合路径参数、查询参数与请求体,让 FastAPI 自动完成解析、校验、文档生成三大工作,将精力集中在真正的业务逻辑上。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0631
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python09
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

