FastAPI 多 Body 参数详解:混合 Path/Query/Body、Body() 函数与 embed 嵌入机制
2026/9/7 8:49:10 网站建设 项目流程

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

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

本篇基于 FastAPI 官方教程文档Body – Mehrere Parameter(docs/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 中会自动生成一个组合请求体 SchemaBody_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

假设你只声明了一个itemBody 参数(类型为 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 行为。

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

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

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

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

立即咨询