FastAPI 请求体(Request Body)完全指南:用 Pydantic 模型声明与校验 POST/PUT 数据
2026/9/8 20:39:14 网站建设 项目流程

FastAPI 请求体(Request Body)完全指南:用 Pydantic 模型声明与校验 POST/PUT 数据

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

本指南以 FastAPI 官方教程「请求体(Request Body)」为核心,系统讲解如何用 Pydantic 模型接收客户端(如浏览器、移动端)提交的 JSON 数据,涵盖模型定义、必填/可选字段声明、与路径参数、查询参数的混用规则,并结合仓库内 docs_src/body/ 下的示例源码与 tests/test_tutorial/test_body/ 中的测试用例,向你展示 FastAPI 如何把「类型声明」变成「JSON 解析 + 数据校验 + OpenAPI 文档」一整条自动化链路。读完本文,你将能独立写出健壮的 POST/PUT 接口,并精确理解每个参数最终落在请求的哪个位置。

什么是请求体,何时需要发送

当客户端(比如浏览器或移动 App)需要向你的 API 发送数据时,数据通常以请求体(request body)的形式发送。相应地,响应体(response body)是你的 API 返回给客户端的数据。

需要区分的是:API 几乎总要返回响应体,但客户端并不总是需要发送请求体——有时客户端只是请求某个路径,可能带上几个查询参数,但不会携带 body。因此,FastAPI 教程对请求体的定位是:在“确有必要”时才用它来承载结构化数据。

关于请求方法,官方文档给出了明确提醒:

  • 要发送数据,应使用POST(最常见)、PUTDELETEPATCH之一;
  • GET请求中发送 body,在规范(HTTP 规范)中属于未定义行为。FastAPI 出于对非常复杂/极端场景的兼容性仍然支持它,但这是被强烈不鼓励的做法——Swagger UI 交互式文档不会为GET展示 body 文档,且中间代理(proxy)也很可能不支持这种用法。

声明请求体的方式十分简单:使用 Pydantic 模型——借助 Pydantic 的全部能力与优势来完成类型转换、校验和序列化。

第一步:导入 Pydantic 的BaseModel

在开始前,先引入必要的依赖。示例代码位于 docs_src/body/tutorial001_py310.py,首先从pydantic导入BaseModel

from fastapi import FastAPI from pydantic import BaseModel

这里的fastapi.FastAPI用于创建应用实例,而BaseModel是你定义数据模型的基类。

第二步:定义数据模型(Data Model)

把数据模型声明为继承自BaseModel的类,并使用标准的 Python 类型标注所有属性:

class Item(BaseModel): name: str description: str | None = None price: float tax: float | None = None

必填与可选字段的约定

与声明查询参数时的规则一致:

  • 模型属性有默认值→ 该字段非必填
  • 模型属性没有默认值→ 该字段必填
  • 使用None作为默认值即可让字段可选

在上面的Item模型中,name: strprice: float没有默认值,因此必填;descriptiontax的默认值为None,因此是可选的。这个模型对应一个 JSON「对象」(即 Python 的dict),例如:

{ "name": "Foo", "description": "An optional description", "price": 45.2, "tax": 3.5 }

因为descriptiontax可选,下面的 JSON 同样是合法的请求体:

{ "name": "Foo", "price": 45.2 }

可选项的文本内容,但别选错可选语义

注意区分两种“可选”:

  • str | None(Python 3.10+ 联合类型语法)只表达“类型上允许为 None”,它本身并不决定字段是否必填
  • 真正让字段“非必填”的是= None这个默认值。

这一点在下文混用查询参数时会再次印证。

第三步:把模型声明为路径操作函数的参数

路径操作(path operation)函数里,把它当作参数声明即可——就像之前声明路径参数和查询参数那样,并把类型标注为你创建的模型Item

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

只靠这一处 Python 类型声明,FastAPI 就会自动完成:

  1. 将请求体作为 JSON 读取
  2. 转换对应的类型(如有需要,比如把字符串"50.5"转成float);
  3. 校验数据——若数据非法,会返回清晰明确的错误,精确指出错误位置与错误内容(HTTP 422 与ValidationError结构);
  4. 把接收到的数据放入参数item——由于你在函数中把item的类型声明为Item,编辑器会对该参数的全部属性及其类型提供自动补全等支持;
  5. 为模型生成 JSON Schema 定义,这些 Schema 可被项目在其他地方复用;
  6. 这些 Schema 会成为生成的OpenAPI Schema的一部分,并被自动文档 UI 使用。

完整可运行的示例见 docs_src/body/tutorial001_py310.py。

源码级佐证:请求体在 OpenAPI 中如何呈现

仓库中的测试 tests/test_tutorial/test_body/test_tutorial001.py 对生成的/openapi.json做了快照断言(test_openapi_schema)。从中可以看到,POST /items/的 OpenAPI 定义里出现了:

"requestBody": { "content": { "application/json": { "schema": {"$ref": "#/components/schemas/Item"} } }, "required": true }

同时components.schemas.Item被生成为type: object,其中required列表恰好是["name", "price"](两个无默认值的字段),而descriptiontax被描述为anyOf: [{"type": "string"}, {"type": "null"}]/anyOf: [{"type": "number"}, {"type": "null"}]。这就是“默认值决定必填性、类型标注决定 Schema”最直观的代码证据。

校验失败的真实形态(来自测试断言)

同样是上述测试文件,覆盖了各种异常请求并断言返回 HTTP 422:

请求场景结果
缺少必填字段(如只传{"name": "Foo"}422,错误type: "missing"loc: ["body", "price"]
传了无法解析为数字的字符串(price: "twenty"422,错误type: "float_parsing"
请求体为空对象{}422,同时报告nameprice缺失
请求体为null422,错误定位到loc: ["body"]
JSON 语法损坏422,错误type: "json_invalid",并附ctx.error
提交表单而非 JSON(application/x-www-form-urlencoded422
Content-Type不是 JSON 类型422

反过来,只要Content-Type: application/json(或application/geo+json等 JSON 变体)且数据合法,返回就是 200。另外测试还演示了类型强制转换price传字符串"50.5"时,响应中会变成浮点数50.5——这正是“按需转换类型”的直接证据。

在函数中使用模型对象

进入函数内部后,可以直接访问模型对象的各个属性。教程的进阶版示例 docs_src/body/tutorial002_py310.py 展示了更真实的业务用法:先把模型转为dict,再依据可选字段动态加工数据。

@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.model_dump()(Pydantic v2 中替代旧版item.dict()的方法)把模型实例序列化为普通dict,随后检查可选字段item.tax是否真的传入了值,只有非None时才计算含税价格并追加键price_with_tax

对应的测试 tests/test_tutorial/test_body/test_tutorial002.py 也很有意思:

  • tax: 0.3时,响应为{"name": "Foo", "price": 50.5, "description": "Some Foo", "tax": 0.3, "price_with_tax": 50.8}——证明price_with_tax由 50.5 + 0.3 计算而来;
  • 不传tax时,响应中不含price_with_tax键,taxNone——证明model_dump()保留了可选键为None,而条件分支正确地跳过了计算;
  • 测试用参数化同时验证了price"50.5"字符串与50.5浮点数时都能得到相同的50.5数值结果,再次佐证类型转换能力。

请求体与路径参数同时使用

你完全可以同时声明路径参数与请求体。FastAPI 会智能识别:函数参数中与路径参数同名/匹配的,从路径中取值;被声明为Pydantic 模型类型的参数,则从请求体中读取

来看示例 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()}

这里item_id出现在路径"/items/{item_id}"中,因此被当作路径参数解析为intitem的类型是 Pydantic 模型,于是被当作请求体解析。返回值把路径中的item_id与模型字段合并成一个响应对象。

测试 tests/test_tutorial/test_body/test_tutorial003.py 中用client.put("/items/123", json={...})验证:路径中的"123"被转换为整数123,响应为{"item_id": 123, ...}。同时该测试生成的 OpenAPI 快照中,路径参数item_id位于parametersin: "path"required: truetype: "integer"),而请求体独立出现在requestBody——这说明二者在文档和解析上完全分离、互不干扰。

请求体 + 路径参数 + 查询参数三者混用

更进一步,你还可以在同一个接口里同时声明请求体、路径参数和查询参数,FastAPI 会为每个参数自动从正确位置取值。示例见 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

调用/items/123?q=somequery并携带 JSON 请求体时,item_id来自路径、q来自查询串、item来自请求体。

FastAPI 的参数识别规则

函数参数会被按如下规则归类(官方文档明确给出):

  • 若参数同时声明在路径中 → 作为路径参数
  • 若参数是单一类型(如intfloatstrbool等)→ 作为查询参数
  • 若参数类型被声明为Pydantic 模型→ 作为请求

关于q: str | None = None的必填性说明

文档特别提示:FastAPI 判断q非必填,依据的是默认值= None而不是str | None这个类型注解本身。不加默认值的q: str就会变成必填查询参数。当然,写出str | None这样的类型注解依然有意义——它能让编辑器给出更好的补全与错误提示,这属于“类型正确性”的范畴。

对应的测试 tests/test_tutorial/test_body/test_tutorial004.py 做了两点关键验证:

  1. 行为上:client.put("/items/123", json={...}, params={"q": "somequery"})时响应包含"q": "somequery";不带q时响应不含该键。
  2. 文档上:OpenAPI 快照中q出现在parameters里,且为"required": falsein: "query"参数;模型Item仍然只在requestBody中被引用。这正好与“默认值None→ 非必填”及“单值类型 → 查询参数”两条规则互相印证。

自动生成的交互式文档

由于模型会被纳入 OpenAPI Schema,自动生成的交互式 API 文档会直接展示你的数据模型,例如:

  • 在「Schemas」区域列出模型的 JSON Schema(文档截图见 docs/en/docs/img/tutorial/body/image01.png);
  • 在每个使用该模型的路径操作内部,也会内嵌展示该请求体 Schema 与 422 校验错误结构(见 docs/en/docs/img/tutorial/body/image02.png)。

你可以访问应用根路径的/docs(Swagger UI)直接试发请求体并观察校验响应。

编辑器的类型提示与自动补全支持

使用 Pydantic 模型而非裸dict的另一个巨大好处是编辑器支持

  • 在函数体内编写item.时,编辑器会给出所有属性及其类型的补全与类型提示(截图见 docs/en/docs/img/tutorial/body/image03.png);
  • 对类型不正确的操作(例如把item.pricefloat)当字符串拼接),编辑器会直接标出错误(截图见 docs/en/docs/img/tutorial/body/image04.png)。

这种体验并非巧合:FastAPI 整个框架正是围绕“类型声明驱动”这一设计目标构建的,并且在实现之前就经过了设计阶段的严格测试,以确保能与各家编辑器协同工作,Pydantic 本身也为此做过相应改动。上面的截图来自 Visual Studio Code,但在 PyCharm 及大多数主流 Python 编辑器中也有一致的体验(PyCharm 下的效果见 docs/en/docs/img/tutorial/body/image05.png)。若使用 PyCharm,还可以安装 Pydantic PyCharm Plugin 来获得对 Pydantic 模型更完善的自动补全、类型检查、重构、搜索与代码检查能力。

不使用 Pydantic 的替代方案

如果你不想使用 Pydantic 模型,也可以直接用Body参数来接收请求体中的单个值。相关内容属于「请求体 – 多参数」教程的范畴,详见官方文档 docs/en/docs/tutorial/body-multiple-params.md#singular-values-in-body 中「body 中的单值」一节(法语文档入口见 docs/fr/docs/tutorial/body.md 结尾的指引)。

小结

从一次简单的POST /items/到「路径参数 + 查询参数 + 请求体」三合一接口,FastAPI 请求体的核心心智模型只有一句话:类型声明即一切。写对类型、写对默认值,FastAPI 就替你完成了 JSON 读取、类型转换、数据校验、422 错误响应与 OpenAPI 文档生成的全部工作;而 Pydantic 模型带来的编辑器补全与静态检查,则让这类代码从「能跑」走向「可靠、可维护」。想进一步实践,可以运行仓库内docs_src/body/下的四个示例,并用tests/test_tutorial/test_body/目录中的测试用例对照学习各种边界情况(缺字段、坏 JSON、错误 Content-Type、类型强转等)。

【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询