首页
/ FastAPI Cookie 参数模型(Cookie Param Models)实战:用 Pydantic 统一声明、校验与约束 Cookie

FastAPI Cookie 参数模型(Cookie Param Models)实战:用 Pydantic 统一声明、校验与约束 Cookie

2026-09-06 18:15:17作者:平淮齐Percy

本篇技术指南围绕 FastAPI 官方教程中的 Cookie Parameter Models 主题展开,讲解如何把一组业务相关的 Cookie 收拢进一个 Pydantic 模型,用 Cookie() 一次性完成集中声明、类型校验、默认值与 OpenAPI 文档生成,并进一步通过 Pydantic 模型配置来限制并拒绝非预期的"多余 Cookie"。读完本文,你将掌握 Cookie 参数模型的两种写法(Annotated 与默认值风格)、底层解析机制、/docs 交互注意事项,以及 model_config = {"extra": "forbid"} 的严格模式实战用法。

为什么要用 Cookie 参数模型

在 FastAPI 中,单个 Cookie 参数通常像单个查询参数那样逐个声明,写法上你需要在函数签名里为每个 Cookie 单独定义一个参数(可参考 Cookie 参数基础教程)。

但很多场景下 Cookie 是成组出现的,例如:

  • session_id:会话标识,几乎所有请求都必须携带;
  • fatebook_trackergoogall_tracker:一类分析/追踪型 Cookie,可选且常常伴随默认值;
  • 更多相互关联的业务 Cookie。

此时更优雅的做法是:把这些 Cookie 声明进一个 Pydantic 模型,再把函数参数的类型标注为这个模型。这样做有两个直接收益:

  1. 模型可复用:同一个 BaseModel 可以被多个路径操作、多个 API 模块重复引用,避免在每处重复罗列参数;
  2. 集中声明校验与元数据:字段的必填/可选、类型、约束、描述等信息只需在模型里写一次,所有使用它的接口统一生效。

该能力自 FastAPI 0.115.0 版本起提供支持。需要注意,同一套"参数模型"技术同样适用于 QueryCookieHeader 三类非 Body 参数(详见 Query 参数模型Header 参数模型),本文以 Cookie 为讲解对象。

快速上手:用 Pydantic 模型声明一组 Cookie

下面是最核心的完整示例,对应仓库中的 cookie_param_models/tutorial001_an_py310.py(Annotated 写法,推荐):

from typing import Annotated

from fastapi import Cookie, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Cookies(BaseModel):
    session_id: str
    fatebook_tracker: str | None = None
    googall_tracker: str | None = None


@app.get("/items/")
async def read_items(cookies: Annotated[Cookies, Cookie()]):
    return cookies

代码的关键点拆解如下:

  • class Cookies(BaseModel):把要接收的 Cookie 作为 Pydantic 模型的字段集中定义。这里 session_id: str 为必填字段;fatebook_trackergoogall_tracker 声明为 str | None = None,表示可选,缺省时模型字段值为 None
  • cookies: Annotated[Cookies, Cookie()]:函数参数的类型注解是 Pydantic 模型,再配合 Cookie() 告诉 FastAPI"这是一组从 Cookie 里提取的参数",而非查询参数或请求体。
  • 返回模型本身:示例直接 return cookies,FastAPI 会自动把模型序列化回 JSON 返回给客户端。

如果你不想使用 Annotated,也可以采用经典默认值风格,见 cookie_param_models/tutorial001_py310.py

@app.get("/items/")
async def read_items(cookies: Cookies = Cookie()):
    return cookies

请求到达后,FastAPI 会从请求携带的 Cookie 中为模型的每一个字段提取对应数据(按字段名与 Cookie 名一一匹配),校验通过后组装成你定义的 Cookies 模型实例注入函数。

仓库配套测试 test_tutorial001.py 对上述行为给出了直接验证:

  • 同时发送 session_idfatebook_trackergoogall_tracker 三个 Cookie,接口返回 200,响应体逐一回显三个值;
  • 只发送 session_id 时,返回 200,可选字段自动补为 None
  • 完全不发送任何 Cookie 时返回 422,错误体为:
{
    "detail": [
        {
            "type": "missing",
            "loc": ["cookie", "session_id"],
            "msg": "Field required",
            "input": {}
        }
    ]
}

即:必填的 session_id 缺失会被 Pydantic 校验拦下,错误定位路径 loc["cookie", "session_id"]。这个细节也说明——参数模型的路由错误与普通 Pydantic 校验错误完全同构,客户端便于统一解析。

底层原理:模型字段如何被拆成独立 Cookie 参数

从源码层面看,FastAPI 在 fastapi/dependencies/utils.py 中会把参数按其"位置类型(in_)"分类收纳。其中将字段挂入依赖的过程在 add_param_to_fields() 函数(约 第 550~563 行)实现:

if field_info_in == params.ParamTypes.path:
    dependant.path_params.append(field)
elif field_info_in == params.ParamTypes.query:
    dependant.query_params.append(field)
elif field_info_in == params.ParamTypes.header:
    dependant.header_params.append(field)
else:
    assert field_info_in == params.ParamTypes.cookie, (
        f"non-body parameters must be in path, query, header or cookie: {field.name}"
    )
    dependant.cookie_params.append(field)

这从实现层面印证了:非 Body 的参数只可能归入 path / query / header / cookie 四类之一。CookieQueryPath 的"姊妹类",共同继承自 Param,其定义位于 fastapi/params.pyclass Cookie(Param),约第 387 行);而从 fastapi 包中导入的 Cookie 实际是返回特定参数类的工厂函数(这一点与 QueryPath 一致)。

当参数注解为 Pydantic 模型且配以 Cookie() 时,FastAPI 会把模型中的每个字段分别登记为一个 OpenAPI cookie 参数(每个字段独立出现在文档中,携带各自的必填/可选与类型信息),请求到达后先取整组 Cookie,再做模型级校验与组装。这一点可以从上述测试中的 test_openapi_schema 快照得到印证——生成的 /openapi.json/items/parameters 包含三个 "in": "cookie" 的参数:

{
    "name": "session_id",
    "in": "cookie",
    "required": true,
    "schema": {"type": "string", "title": "Session Id"}
},
{
    "name": "fatebook_tracker",
    "in": "cookie",
    "required": false,
    "schema": {
        "anyOf": [{"type": "string"}, {"type": "null"}],
        "title": "Fatebook Tracker"
    }
}

可见:required 标志由字段是否必填自动推导(session_id 无默认值故为必填,两个 tracker 带 None 默认值故为可选、类型含 null)。文档、客户端代码生成与运行时校验由同一份模型定义驱动,不会出现文档与行为不一致的问题。

在 /docs 交互式文档中查看 Cookie 参数

声明好模型后,启动应用并打开 /docs,你可以直观看到这些 Cookie 参数已自动出现在对应路径操作的文档区域。下图展示了示例应用中 GET /items/ 的 Cookie 参数文档界面(图片原始文件):

FastAPI 自动生成的 /docs 界面中展示的 Cookie 参数(session_id 必填,两个 tracker 可选)

界面中 session_id 被标记为 required,fatebook_trackergoogall_tracker 为非必填,三者位置均标注 (cookie)

一个浏览器层面的重要限制

请务必注意:浏览器对 Cookie 有特殊且隐蔽的处理机制,通常不允许 JavaScript 直接"触碰"它们

  • 打开 /docs 的 API 文档界面,你可以正常看到各路径操作的 Cookie 文档说明;
  • 但即使你在界面上填好了 Cookie 的值并点击 "Execute",由于该文档 UI 是靠 JavaScript 发请求的,Cookie 并不会被真正发送出去,你将看到类似"未填写任何值"的校验错误。

这意味着在本地用 Swagger UI 直接"试执行"带 Cookie 的接口往往行不通。Cookie 参数模型的接口应当使用真实的浏览器访问(同域 Cookie 自动携带),或使用 TestClienthttpx 等在代码/命令行中显式设置 Cookie 后再请求验证,而不是依赖 /docs 的 Execute 按钮。

严格模式:禁止接收额外的 Cookie

默认情况下,如果客户端多发送了模型中没有声明的 Cookie,FastAPI 会静默忽略这些多余 Cookie 并正常返回 200——仓库测试 test_cookie_param_model_extra 就验证了这一点(额外发送一个 extra Cookie 依然返回成功响应)。

在某些特殊场景(尽管不太常见)下,你可能希望只接受白名单内的 Cookie,拒绝任何多余的 Cookie。此时只需利用 Pydantic 的模型配置把 extra 字段行为设为 forbid,完整代码见 cookie_param_models/tutorial002_an_py310.py

from typing import Annotated

from fastapi import Cookie, FastAPI
from pydantic import BaseModel

app = FastAPI()


class Cookies(BaseModel):
    model_config = {"extra": "forbid"}

    session_id: str
    fatebook_tracker: str | None = None
    googall_tracker: str | None = None


@app.get("/items/")
async def read_items(cookies: Annotated[Cookies, Cookie()]):
    return cookies

与前面示例唯一的区别就是增加了一行:

model_config = {"extra": "forbid"}

加上这行后,一旦客户端尝试发送未声明的 Cookie(例如发送一个名为 santa_tracker、值为 good-list-please 的 Cookie),就会收到校验错误响应,而不是被静默忽略:

{
    "detail": [
        {
            "type": "extra_forbidden",
            "loc": ["cookie", "santa_tracker"],
            "msg": "Extra inputs are not permitted",
            "input": "good-list-please"
        }
    ]
}

错误体信息与 Cookie 校验错误保持一致:loc 定位到 ["cookie", "<多余 Cookie 名>"]typeextra_forbidden。对应测试实现位于 test_tutorial002.py。需要权衡的是:一旦开启 forbid,后续客户端新增任何追踪、埋点类 Cookie 都会被拒绝,因此仅在你确实需要强约束 Cookie 集合时才开启

设计要点与使用建议

综合官方文档、配套示例源码(docs_src/cookie_param_models/)与测试(tests/test_tutorial/test_cookie_param_models/),落地 Cookie 参数模型时有几点值得留意:

  • 必填字段不要给默认值:像 session_id: str 这样无默认值的字段会被推导为必填 Cookie,缺失时返回 422,错误定位为 ["cookie", "session_id"],对客户端排错友好。
  • 可选追踪型 Cookie 用 str | None = None:既能保证模型实例化成功,又能把"字段存在但为空"与"字段完全缺失"统一为 None,处理逻辑更简单。
  • 校验与元数据集中管理:模型字段沿用标准 Pydantic 字段能力,可针对 Cookie 内容做类型转换、长度/格式约束并补充描述信息;一处定义,多接口复用,OpenAPI 文档与运行时校验自动保持一致。
  • Cookie 模型只适用于合适的位置:从底层源码可看到非 Body 参数必须落入 path / query / header / cookie 四类,Cookie 参数模型必须配合 Cookie() 使用,否则参数会被当作查询参数处理。
  • 严格模式按需开启extra="forbid" 会拒绝白名单外的任意 Cookie,适合安全敏感或内部专用的接口;普通对外接口建议保持默认的"忽略多余项"行为,避免过度耦合客户端行为。

小结

本文完整覆盖了 FastAPI Cookie 参数模型从声明到落地的全链路:用 Pydantic BaseModel 集中声明一组 Cookie 并通过 Cookie() 注入路径操作(自 FastAPI 0.115.0 起支持)、两种函数签名写法、/docs 中文档展示及其浏览器 Cookie 限制、底层参数分组机制,以及用 model_config = {"extra": "forbid"} 拒绝多余 Cookie 的严格模式。该技术与 Query 参数模型、Header 参数模型同源同法,掌握一种即可触类旁通;如果你还希望对模型的单个字段做更强约束,可进一步结合 Pydantic Field 与 Field Validator 扩展,相关示例与测试都可在上述 docs_srctests 目录中对照研读。

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