FastAPI 请求体(Request Body)实战指南:基于 Pydantic 模型声明与校验请求数据
FastAPI 的请求体(Request Body)功能,让你可以用一个 Python 类型标注就能完成 JSON 数据的读取、类型转换、数据校验与自动化文档生成。本指南以 body 教程 为主线,结合仓库内 docs_src/body 的示例源码与 tests/test_tutorial/test_body 测试用例,系统讲解如何在 FastAPI 中声明并处理请求体。学完你将掌握:使用 Pydantic BaseModel 定义数据模型、将模型与 path/query 参数混合声明、理解 FastAPI 的参数识别规则,以及模型自动生成 OpenAPI Schema 背后的实现细节。
什么是 Request Body
当客户端(比如浏览器)需要向 API 发送数据时,数据以 request body(请求体) 的形式发送;而 API 返回给客户端的数据则称为 response body(响应体)。
- request body:客户端发送给 API 的数据;
- response body:API 返回给客户端的数据。
API 几乎总是需要返回 response body,但客户端并不总是需要发送 request body——有时客户端仅仅请求一个路径,最多携带几个 query 参数,并不发送任何 body。
在 FastAPI 中声明 request body,使用的是 Pydantic 模型,可以完整继承 Pydantic 的全部能力与收益。
发送数据应使用的 HTTP 方法
官方教程特别强调:要发送数据,应使用 POST(最常见)、PUT、DELETE 或 PATCH 中的一种。
- 在
GET请求中携带 body,其行为在 HTTP 规范中属于 undefined(未定义)状态。FastAPI 出于兼容性仍然支持这种用法,但仅面向非常复杂/极端的场景; - 由于该用法本身被建议避免,Swagger UI 交互式文档在使用
GET时不会为 body 展示文档说明,中间的代理服务器也可能不支持这种请求。
核心示例:声明一个携带 JSON 请求体的接口
这一部分对应的完整示例位于仓库 docs_src/body/tutorial001_py310.py,它将从零到一演示声明请求体的三个步骤。
第一步:导入 Pydantic 的 BaseModel
from pydantic import BaseModel
第二步:创建数据模型
将数据模型声明为一个继承自 BaseModel 的类,属性使用标准 Python 类型:
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
这里与声明 query 参数时同样的规则生效:当某个模型属性带有默认值时,它不是必填的;否则它就是必填的。要让某个属性仅作 optional 处理,将默认值设为 None。
因此,上面的 Item 模型声明的就是一个类似如下的 JSON "object"(等价于 Python dict):
{
"name": "Foo",
"description": "An optional description",
"price": 45.2,
"tax": 3.5
}
由于 description 和 tax 是可选的(默认值为 None),下面的 JSON "object" 同样是合法的请求体:
{
"name": "Foo",
"price": 45.2
}
技术细节:
description: str | None = None中的类型标注与默认值分工不同。在 Pydantic v2(也是本仓库当前使用的版本)中,str | None描述的是字段可以取的类型(string 或 null),而= None才决定字段是否必填。测试文件 tests/test_tutorial/test_body/test_tutorial004.py 中的test_put_only_required验证了仅发送必填字段{"name": "Foo", "price": 50.1}时请求成功,且缺省字段以None出现在响应中。
第三步:在路径操作中将其声明为参数
把模型添加进你的 path operation,声明方式与你之前声明 path 参数和 query 参数完全相同——直接把它作为函数参数,并把类型声明为 Item:
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):
return item
FastAPI 会自动做哪些事
仅仅依靠上面这行 Python 类型声明,FastAPI 就会自动完成:
- 将请求体作为 JSON 读取;
- 执行类型转换(必要时);
- 校验数据——如果数据无效,会返回一个友好、清晰的错误响应,精确指出错误数据所在的位置与具体内容;
- 把接收到的数据交给参数
item——因为你在函数中把它声明为Item类型,IDE 会对它的所有属性及属性类型提供完整的编辑器支持(自动补全等); - 为你的模型生成 JSON Schema 定义——如果对你的项目有意义,这些 Schema 可以在其他任何地方复用;
- 将这些 Schema 纳入生成的 OpenAPI Schema,并供自动文档 UI 使用。
参数识别规则:FastAPI 如何区分 body、path 与 query
在 FastAPI 中,函数参数的数据来源完全由类型标注和声明位置决定。以一个同时包含 path 参数、query 参数和 Pydantic 模型的接口为例(完整源码见 docs_src/body/tutorial004_py310.py):
@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
FastAPI 会按如下规则识别每一个函数参数:
- 如果该参数同时声明在路径(path)中,则作为 path 参数使用;
- 如果参数是单一类型(singular type),例如
int、float、str、bool等,则被解释为 query 参数; - 如果参数被声明为 Pydantic 模型的类型,则被解释为 request body。
这一点在单元测试 tests/test_tutorial/test_body/test_tutorial004.py 的 test_openapi_schema 中有精确的落点:生成出的 OpenAPI 3.1.0 Schema 中,item_id 出现在 parameters 且 in: "path"、required: True;q 出现在 parameters 且 in: "query"、required: False;而 Item 模型则被挂在 requestBody.content["application/json"].schema 的 $ref 上,同时 components.schemas.Item 中的 required 数组精确为 ["name", "price"]——可见这一识别与拆分发生在框架层并被完整序列化进了 OpenAPI 文档。
关于可选参数的常见误区
在上面的例子中,FastAPI 之所以知道 q 的值不是必填的,是因为它的默认值是 = None,而不是因为标注了 str | None。
官方文档明确:
str | None这种类型标注并不被 FastAPI 用来判断值是否必填;它判断"非必填"的依据是存在默认值= None。不过,添加类型标注仍然有价值——它让编辑器提供更好的支持并帮助你检测错误。
请求体校验失败的实战表现
当一个必填字段缺失时,FastAPI 会返回 422 Unprocessable Entity 以及结构化的错误明细。test_put_with_no_data 测试对 /items/123 发送空对象 {},得到的响应为:
{
"detail": [
{
"type": "missing",
"loc": ["body", "name"],
"msg": "Field required",
"input": {}
},
{
"type": "missing",
"loc": ["body", "price"],
"msg": "Field required",
"input": {}
}
]
}
注意 loc 字段从 "body" 开始,精确标注了缺失字段位于请求体中的哪个位置——这正是"清晰错误"的具体体现,且该错误模型(ValidationError / HTTPValidationError)也会被自动写入 OpenAPI 的 components.schemas。
自动文档:模型 Schema 自动进入交互式 API 文档
你定义的模型 JSON Schema 会自动成为 OpenAPI 生成 Schema 的一部分,并显示在交互式 API 文档中:
这些 Schema 也会被用在 API 文档中每一个需要它们的 path operation 内部,展示该接口期望接收的请求体结构:
由于 body 与 OpenAPI 的深度绑定,你不仅可以查看文档,还可以直接调用 /openapi.json 获取机器可读的完整 Schema——这正是生成客户端 SDK、做契约测试的基础。
编辑器支持:类型提示、自动补全与错误检查
在你的编辑器里,函数内部随处都能得到类型提示(type hints)和自动补全(completion)。如果你接收的是一个普通 dict 而不是 Pydantic 模型,这些支持将完全不存在:
# 若 item 是 dict,item.name 无法得到补全与类型推断
# 声明为 Item 后,item.name / item.price / item.tax 全部拥有类型信息
编辑器还会对错误的类型操作给出错误检查。这不是巧合——整个 FastAPI 框架就是围绕"类型驱动"这一设计理念构建的,甚至在设计阶段、任何实现落地之前就对这一机制进行了充分测试,以确保它在各类编辑器中都能正常工作;为此 Pydantic 本身也做出过相应改动。上述效果在 Visual Studio Code、PyCharm 及大多数主流 Python 编辑器中都能获得。
技巧:如果你使用 PyCharm,可以安装 Pydantic PyCharm 插件,它针对 Pydantic 模型增强了编辑器支持,包括自动补全、类型检查、重构、搜索与代码检查(inspections)。
在函数体内使用模型对象
在函数内部,你可以直接访问模型对象的全部属性。一个更贴近业务的用法是结合 Pydantic v2 的 model_dump() 把模型转为字典,再基于可选字段追加计算逻辑,完整源码见 docs_src/body/tutorial002_py310.py:
@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
这里 item.tax is not None 的判空逻辑展示了如何利用"可选字段默认 None"这一约定,仅在客户端显式传入 tax 时才计算含税价格。
Request Body 与 Path 参数同时声明
你可以在同一个接口中同时声明 path 参数与请求体(示例源码见 docs_src/body/tutorial003_py310.py):
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
return {"item_id": item_id, **item.model_dump()}
FastAPI 会自动识别:凡是与 path 中已声明参数同名的函数参数,从 path 中取值;凡是声明为 Pydantic 模型的函数参数,从 request body 中取值。两者互不干扰、可同时使用。
Request Body + Path + Query 三参数并存
更进一步,你可以在同一接口中同时声明 body、path 与 query 三类参数(完整源码见 docs_src/body/tutorial004_py310.py):
@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
FastAPI 会识别出每一个参数,并从正确的位置获取数据。测试用例 test_put_all(见 tests/test_tutorial/test_body/test_tutorial004.py)一次性验证了三种来源数据的正确合并:请求行中 item_id=123 取自路径、name/price 等取自 JSON body、q="somequery" 取自 query string,最终响应按 {"item_id": 123, ..., "q": "somequery"} 返回。
一个值得留意的实现细节是:即便客户端请求体只包含部分字段,Pydantic 模型的缺省属性在经 model_dump() 转换后仍会以 None 保留在字典中,因此上述返回结构是确定且可预期的。
不使用 Pydantic 时的替代方案
如果你不想(或不方便)使用 Pydantic 模型,FastAPI 还提供了 Body 参数,用于把单个值放入请求体中。详见仓库文档 Body - Multiple Parameters: Singular values in body。
结合源码验证与运行建议
你可以在当前仓库中完整复现并验证本文涉及的全部行为:
- 源码示例:docs_src/body 目录下的
tutorial001_py310.py至tutorial004_py310.py,对应本指南的四个递进场景; - 测试用例:tests/test_tutorial/test_body 目录,覆盖合法请求、仅必填字段、空 body 的 422 校验错误以及 OpenAPI Schema 快照断言;
- 运行单测:直接以仓库根目录为工作目录执行
pytest tests/test_tutorial/test_body/ -q; - 本地起服务体验交互式文档:先确认
docs_src可被 Python 导入(本仓库测试即以docs_src.body.tutorial00X_py310方式 importlib 加载模块),然后执行uvicorn docs_src.body.tutorial001_py310:app --reload,访问http://127.0.0.1:8000/docs即可看到文中展示的自动生成文档,http://127.0.0.1:8000/openapi.json可查看机器可读 Schema。
在此基础上,如果你需要为请求体字段附加更细的约束、在 body 中传递多个模型或嵌套结构,建议进一步阅读仓库中 Body - Fields、Body - Nested Models 与 Body - Multiple Parameters 等后续章节。
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 StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00

