首页
/ FastAPI 多 Body 参数详解:混合 Path/Query/Body、Body() 函数与 embed 嵌入机制

FastAPI 多 Body 参数详解:混合 Path/Query/Body、Body() 函数与 embed 嵌入机制

2026-09-06 21:38:06作者:董灵辛Dennis

本篇基于 FastAPI 官方教程文档 Body – Mehrere Parameterdocs/de/docs/tutorial/body-multiple-params.md)展开,讲解当请求体不再只有一个 Pydantic 模型时的进阶用法:如何自由混合 PathQuery 与 Body 参数、如何声明多个 Body 参数、如何用 Body 把单个值收进请求体、以及用 embed=True 让单个 Body 参数也按"键包裹"的方式接收。读完本文,你可以掌握组合式请求体的完整声明方式,并从源码层面理解 FastAPI 判断"何时自动嵌入"的底层逻辑。

混用 Path、Query 与 Body 参数

在已经掌握 PathQuery 的基础上,FastAPI 允许你在同一个路径操作函数中自由混合 PathQuery 与请求体(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
    ...

规则可以归纳为:

  1. Body 参数名超过一个 → 必须嵌入(自动按键包裹);
  2. 任一参数显式设置了 embed=True → 必须嵌入;
  3. 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,缺失字段(descriptionfull_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 中会自动生成一个组合请求体 Schema Body_update_item_items__item_id__put,其 propertiesitemuser 两个 $ref,且 required: ["item", "user"]——证明组合结构会被如实写入文档,前端可直接据此调用。

单个值收进 Body:Body() 的用途

与用于 Query 和 Path 参数的 QueryPath 相对应,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)BodyQueryPath 一样支持相同的附加校验与元数据参数。这里 gt=0 表示 importance 必须大于 0,非法值会触发 422 校验错误;
  • 参数列表开头的 * 强制后续参数必须按关键字传入,避免位置参数误配——这在参数较多时是良好的防御性写法。

嵌入单个 Body 参数:embed=True

假设你只声明了一个 item Body 参数(类型为 Pydantic 模型 Item)。默认情况下,FastAPI 期望请求体直接就是模型内容。但若你希望它像多参数场景那样,在 item 键下接收模型内容,可以设置 Bodyembed 参数:

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 参数:

  • 自由混用 PathQuery、Body 参数,FastAPI 依据类型(Pydantic 模型 vs 单个值)与显式声明自动区分来源;
  • 多个模型参数会自动以参数名为键嵌入请求体,FastAPI 负责转换、校验,并生成正确的组合 OpenAPI Schema(如 Body_update_item_items__item_id__put)写入文档;
  • 单个值参数可用 Body() 收进请求体,并可携带 gtge 等与 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 行为。

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