FastAPI 多 Body 参数详解:混合 Path/Query/Body、Body() 函数与 embed 嵌入机制
本篇基于 FastAPI 官方教程文档 Body – Mehrere Parameter(docs/de/docs/tutorial/body-multiple-params.md)展开,讲解当请求体不再只有一个 Pydantic 模型时的进阶用法:如何自由混合 Path、Query 与 Body 参数、如何声明多个 Body 参数、如何用 Body 把单个值收进请求体、以及用 embed=True 让单个 Body 参数也按"键包裹"的方式接收。读完本文,你可以掌握组合式请求体的完整声明方式,并从源码层面理解 FastAPI 判断"何时自动嵌入"的底层逻辑。
混用 Path、Query 与 Body 参数
在已经掌握 Path 与 Query 的基础上,FastAPI 允许你在同一个路径操作函数中自由混合 Path、Query 与请求体(Body)参数声明,框架会自动判断每个参数的数据来源。并且 Body 参数也可以设为可选——只需把默认值设为 None:
from typing import Annotated
from fastapi import FastAPI, Path
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: Annotated[int, Path(title="The ID of the item to get", ge=0, le=1000)],
q: str | None = None,
item: Item | None = None,
):
results = {"item_id": item_id}
if q:
results.update({"q": q})
if item:
results.update({"item": item})
return results
对应仓库示例文件:tutorial001_an_py310.py。
要点说明:
item_id: Annotated[int, Path(...)]从 URL 路径取值,并可附加ge=0, le=1000之类的数值约束与title元数据;q: str | None = None是单个值参数,FastAPI 默认将其解释为 Query 参数(即?q=...),不要求显式写Query(...);item: Item | None = None是 Pydantic 模型参数,FastAPI 将其识别为 Body 参数。注意此处的关键细节:由于Item | None的默认值是None,这个 Body 参数是可选的——请求可以不带请求体。
注意:在这种情况下,来自 Body 的
item是可选的,因为它以None作为默认值。
多个 Body 参数:自动以参数名为键嵌入
在上述示例中,路径操作期望一个包含 Item 各字段的 JSON 请求体,例如:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
但当你需要同时接收多个模型(例如"物品"和"操作用户")时,可以声明多个 Body 参数:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User):
results = {"item_id": item_id, "item": item, "user": user}
return results
对应仓库示例文件:tutorial002_py310.py。
此时 FastAPI 会检测到函数中不止一个 Body 参数(两个 Pydantic 模型参数),于是**以参数名作为键(字段名)**来组织请求体,期望的 Body 变为:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
}
}
注意:尽管
item的声明方式与之前相同,但现在它被期望位于 Body 中item这个键之下。
FastAPI 会自动完成请求数据的转换——item 参数得到它专属的嵌套内容,user 参数同理;并对这份组合数据进行校验,同时将其记录进 OpenAPI Schema 与自动生成的文档中。
源码与测试佐证:组合 Schema 的生成与校验位置
从源码结构看,"多个 Body 参数自动嵌入"的判定发生在依赖解析阶段。fastapi/dependencies/utils.py 中的 _should_embed_body_fields() 函数给出了明确规则:
def _should_embed_body_fields(fields: list[ModelField]) -> bool:
if not fields:
return False
# More than one dependency could have the same field, it would show up as multiple
# fields but it's the same one, so count them by name
body_param_names_set = {field.name for field in fields}
# A top level field has to be a single field, not multiple
if len(body_param_names_set) > 1:
return True
first_field = fields[0]
# If it explicitly specifies it is embedded, it has to be embedded
if getattr(first_field.field_info, "embed", None):
return True
...
规则可以归纳为:
- Body 参数名超过一个 → 必须嵌入(自动按键包裹);
- 任一参数显式设置了
embed=True→ 必须嵌入; Form/File字段若不是BaseModel(或其联合),也需嵌入以便提取键值对。
真正的取值与校验在 request_body_to_args() 中完成:当 single_not_embedded_field(只有一个 Body 字段且未嵌入)成立时,整个请求体直接作为该字段的值进行校验;否则逐个字段执行 body_to_process.get(get_validation_alias(field)),即按键名从 Body 字典中提取,再对每个字段独立做 Pydantic 校验——这解释了为什么多 Body 参数时每个参数各自嵌套在顶层键下。
仓库测试 test_tutorial002.py 完整验证了上述行为:
test_post_all:发送{"item": {...}, "user": {...}}返回 200,缺失字段(description、full_name)自动补None;test_post_no_body/test_post_no_item/test_post_no_user:缺少任一键即返回 422,且loc精确到["body", "item"]或["body", "user"],便于定位错误来源;test_post_missing_required_field_in_item:嵌套字段缺失时,loc会细化到["body", "item", "price"];test_openapi_schema:快照断言 OpenAPI Schema 中会自动生成一个组合请求体 SchemaBody_update_item_items__item_id__put,其properties为item与user两个$ref,且required: ["item", "user"]——证明组合结构会被如实写入文档,前端可直接据此调用。
单个值收进 Body:Body() 的用途
与用于 Query 和 Path 参数的 Query、Path 相对应,FastAPI 提供 Body 来为 Body 参数声明额外信息。例如在上面的双模型基础上,你还想在同一个 Body 中多加一个 importance 键:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
item_id: int, item: Item, user: User, importance: Annotated[int, Body()]
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
return results
对应仓库示例文件:tutorial003_an_py310.py。
如果直接写 importance: int 而不加 Body(),FastAPI 会把它当作 Query 参数(因为它是单个值)。使用 Annotated[int, Body()] 后,它被识别为 Body 中的另一个键,期望的请求体为:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
},
"user": {
"username": "dave",
"full_name": "Dave Grohl"
},
"importance": 5
}
数据类型转换、校验、文档生成等行为与模型参数一致。
多个 Body 参数与 Query 参数并存
Body 参数与 Query 参数可以任意组合。由于单个值参数默认就是 Query 参数,你甚至不需要显式写 Query(...),直接 q: str | None = None 即可:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
class User(BaseModel):
username: str
full_name: str | None = None
@app.put("/items/{item_id}")
async def update_item(
*,
item_id: int,
item: Item,
user: User,
importance: Annotated[int, Body(gt=0)],
q: str | None = None,
):
results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
if q:
results.update({"q": q})
return results
对应仓库示例文件:tutorial004_an_py310.py。
这个例子还顺带展示了两个细节:
Body(gt=0):Body与Query、Path一样支持相同的附加校验与元数据参数。这里gt=0表示importance必须大于 0,非法值会触发 422 校验错误;- 参数列表开头的
*强制后续参数必须按关键字传入,避免位置参数误配——这在参数较多时是良好的防御性写法。
嵌入单个 Body 参数:embed=True
假设你只声明了一个 item Body 参数(类型为 Pydantic 模型 Item)。默认情况下,FastAPI 期望请求体直接就是模型内容。但若你希望它像多参数场景那样,在 item 键下接收模型内容,可以设置 Body 的 embed 参数:
item: Annotated[Item, Body(embed=True)]
完整示例:
from typing import Annotated
from fastapi import Body, FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str | None = None
price: float
tax: float | None = None
@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Annotated[Item, Body(embed=True)]):
results = {"item_id": item_id, "item": item}
return results
对应仓库示例文件:tutorial005_an_py310.py。
此时 FastAPI 期望的请求体是:
{
"item": {
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
}
而不是:
{
"name": "Foo",
"description": "The pretender",
"price": 42.0,
"tax": 3.2
}
embed 参数的官方定义见 fastapi/param_functions.py:当 embed=True 时,参数会被期望"作为 JSON Body 中的一个键,而不是 JSON Body 本身";并且文档注明,声明多于一个 Body 参数时该行为会自动发生。
测试佐证的典型误区
test_tutorial005.py 中有一个值得注意的用例 test_post_like_not_embedded:当设置 embed=True 后,若客户端仍然发送"未嵌入"的扁平结构 {"name": "Foo", "price": 50.5},请求会返回 422,错误位置为 ["body", "item"]——即 FastAPI 在 item 键下找不到值,整个 item 字段被判定为缺失。这提醒开发者:切换 embed 语义属于接口契约变更,客户端必须同步调整,否则会产生隐蔽的 422 错误。
小结
一个请求只能有一个 Body,但这并不妨碍你在路径操作函数中声明多个 Body 参数:
- 自由混用
Path、Query、Body 参数,FastAPI 依据类型(Pydantic 模型 vs 单个值)与显式声明自动区分来源; - 多个模型参数会自动以参数名为键嵌入请求体,FastAPI 负责转换、校验,并生成正确的组合 OpenAPI Schema(如
Body_update_item_items__item_id__put)写入文档; - 单个值参数可用
Body()收进请求体,并可携带gt、ge等与Query/Path一致的校验和元数据参数; - 单个模型参数默认"裸接收"整个 Body,用
Body(embed=True)可强制按键包裹接收,从源码看该判定集中在_should_embed_body_fields()与request_body_to_args()(fastapi/dependencies/utils.py)中实现。
以上示例文件均位于 docs_src/body_multiple_params/,配套测试位于 tests/test_tutorial/test_body_multiple_params/,可直接运行验证各场景的请求/响应与 OpenAPI Schema 行为。
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 StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
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