首页
/ FastAPI Cookie 参数模型(Cookie Parameter Models):用 Pydantic 模型统一声明、校验与限制 Cookie

FastAPI Cookie 参数模型(Cookie Parameter Models):用 Pydantic 模型统一声明、校验与限制 Cookie

2026-09-07 13:30:06作者:郦嵘贵Just

在 FastAPI 中,当一组 Cookie 彼此相关时(例如会话 ID、多个跟踪器 Cookie),你可以把它们收敛到一个 Pydantic model 中整体声明,并在路径操作函数中用一个 Cookie 参数接收整个模型。本篇文章将围绕 FastAPI 官方教程文档(见 docs/en/docs/tutorial/cookie-param-models.md,以及对应的 docs/hi/docs/tutorial/cookie-param-models.md 译文)讲解:如何定义 Cookie 参数模型、FastAPI 如何自动从请求中提取每个字段并完成校验、如何生成对应的 /docs 接口文档,以及如何通过 Pydantic 的 extra: forbid 配置拒绝客户端发送的多余 Cookie。读完本文,你将能写出可复用、可集中校验、可自动生成 OpenAPI 文档的 Cookie 参数代码,并理解其底层工作方式。

什么是 Cookie 参数模型

按官方文档的说法(原文用一句俏皮话开场:If you have a group of cookies that are related, you can create a Pydantic model to declare them),当一个接口需要同时读取多个 Cookie 时,逐个用 cookie_params: str = Cookie() 这类写法会显得零散。更优雅的做法是:

  • 先定义一个 Pydantic model,把所有需要的 Cookie 字段、默认值、校验与元数据集中声明;
  • 然后在路径操作函数中把参数类型标成该 model,并用 Cookie() 作为其声明方式;
  • FastAPI 会自动从请求携带的 cookies 中 逐个字段提取 数据并组装成该 model 的实例交给你的函数。

好处(官方文档同样强调):model 可以在 多处复用,所有参数的 validations 和 metadata 可以 一次性 声明。

需要留意两个前提(文档中的 note 与 tip):

  1. 该特性自 FastAPI 0.115.0 版本起支持;
  2. 相同的技术同样适用于 QueryCookieHeader(即 Pydantic 查询参数模型、请求头参数模型也是同一套机制)。

用 Pydantic Model 声明一组 Cookies

完整可运行的示例代码在仓库的 docs_src/cookie_param_models/tutorial001_an_py310.py

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

要点拆解:

  • model 字段即 Cookie 名:Pydantic model Cookies 中的每个字段(session_idfatebook_trackergoogall_tracker)就是客户端请求中携带的 Cookie 名称;
  • 必填与可选session_id: str 没有默认值,因此是必填 Cookie;另外两个字段用 str | None = None 声明为可选,缺省时值为 None
  • 接收方式:路径操作参数 cookies: Annotated[Cookies, Cookie()] 告诉 FastAPI 该参数来自 Cookie(Cookie()),而其类型注解 Cookies 表明应使用 Pydantic model 来组装。

对于不使用 Annotated 的写法(等价),可参考 docs_src/cookie_param_models/tutorial001_py310.py

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: Cookies = Cookie()):
    return cookies

运行后,例如客户端请求携带了 Cookie: session_id=abc123; fatebook_tracker=xyz,那么函数收到的 cookies 就是一个字段值为 {"session_id": "abc123", "fatebook_tracker": "xyz", "googall_tracker": None}Cookies 实例;接口直接把它作为 JSON 返回。

为什么能这样写:底层依赖解析机制

你可能会好奇“一个 Cookie 参数如何撑起多个 Cookie 字段”。从源码看,这套能力由 FastAPI 的依赖参数处理逻辑实现,核心在 fastapi/dependencies/utils.py

  • add_param_to_fields()(第 550-563 行附近)根据 ParamTypes.cookie 把参数归入 dependant.cookie_params
  • 真正的提取发生在 request_params_to_args()(第 780-850 行附近):当 len(fields) == 1 且该字段的类型注解是 BaseModel 子类时,FastAPI 会把请求中收到的全部 cookie 收集成一个 params_to_process 字典(第 830-839 行),再交给 _validate_value_with_model_field() 用你定义的 Pydantic model 做整体校验(第 841-850 行),最终返回 {first_field.name: v_} 即组装好的 model 实例。

也就是说,模型级校验(含必填、可选、以及下文要讲的 extra 限制)都发生在用 model 对整包 cookie 字典做验证这一步。这也是为什么 session_id 缺失时能正确报出 "loc": ["cookie", "session_id"] 这样的校验错误。

在 OpenAPI 文档一侧,fastapi/openapi/utils.py 中的 _get_flat_fields_from_params()(第 169-172 行)以及 _get_openapi_operation_parameters()(第 159-209 行)会把“单个 Pydantic model 参数”摊平(flatten)成多个 in: cookie 的独立参数写入 schema,因此接口文档中每个 Cookie 字段都单独列出。

在 /docs 接口文档中查看与实测

按官方文档说明,在 /docs 的 Swagger UI 中可以看到路径操作声明的所有 cookies:它们会以 cookie 类型的参数形式逐项展示。

仓库中存在对应的运行截图 docs/en/docs/img/tutorial/cookie-param-models/image01.png(注意各语言文档共用同一静态资源,图片标题即 Cookie Parameter Models 教程的 docs UI 效果图):

FastAPI /docs 界面中 Cookie 参数模型渲染出的多个 cookie 参数

不过官方文档特别提醒(配合上图一起理解):

浏览器会以特殊且“幕后”的方式处理 Cookie,并不允许 JavaScript 轻易读写它们。docs UI 本身是用 JavaScript 驱动的,因此即便你在界面上填好数据并点击 “Execute”,cookie 也不会被真正发送,最终你会看到一条“好像什么都没填”的报错。

这意味着:仅靠 Swagger UI 无法端到端验证 Cookie 参数。想要手动实测,应改用真正的 HTTP 客户端(如 curl -H "Cookie: ..."、Postman 或浏览器开发者工具中的请求),它们能按需设置 Cookie 头。

用 TestClient 验证(对应仓库测试)

仓库的自动化测试正好演示了“真正携带 Cookie”的验证方式,见 tests/test_tutorial/test_cookie_param_models/test_tutorial001.py。测试覆盖了三种情况:

  • 全部字段齐全test_cookie_param_model):通过 client.cookies.set(...) 依次写入三个 cookie 后请求 /items/,断言返回 {"session_id": "123", "fatebook_tracker": "456", "googall_tracker": "789"}
  • 只给必填项test_cookie_param_model_defaults):仅设置 session_id,其余两个字段按 model 默认值返回 None
  • 缺少必填项test_cookie_param_model_invalid):不设置任何 cookie 时得到 422,错误结构为 {"type": "missing", "loc": ["cookie", "session_id"], "msg": "Field required", "input": {}}

同一文件中 test_openapi_schema 还断言了生成的 OpenAPI schema:session_idrequired: truein: cookie 参数,两个可选字段则被声明为 anyOf: [string, null],充分印证了文档中“每个 model 字段都会被展开为独立 cookie 参数”的描述。

限制接收的 Cookies:forbid extra fields

在少数特殊场景下,你可能希望收紧 API 接收的 Cookie 集合(官方文档把它调侃成“API 也能拥有自己的 cookie 同意权”)。这可以通过 Pydantic 的 model 配置实现——把 extra 设为 "forbid",任何不在 model 中声明的多余 cookie 字段都会被拒绝。

可运行示例在 docs_src/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

Annotated 等价写法见 docs_src/cookie_param_models/tutorial002_py310.py,核心只有一处差别——model 定义中加入 model_config = {"extra": "forbid"}

说明:model_config 是 Pydantic v2 的写法(等价于 v1 中 class Config: extra = "forbid")。本仓库当前代码基按 Pydantic v2 风格声明。

开启后,如果客户端试图发送一个 model 之外的 cookie,例如发送名为 santa_tracker、值为 good-list-please 的 cookie,客户端会收到如下 422 校验错误响应(原文给出的示例 JSON):

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

这里的 "loc": ["cookie", "santa_tracker"] 精确指出了问题出处:在 cookie 来源中,santa_tracker 属于未被允许的额外输入。

forbid 行为的测试佐证

对应测试见 tests/test_tutorial/test_cookie_param_models/test_tutorial002.py

  • test_cookie_param_model / test_cookie_param_model_defaults:合法 cookie 正常返回 200,缺省字段回落为 None
  • test_cookie_param_model_invalid:缺少必填 session_id 返回 422type: missing);
  • test_cookie_param_model_extra:当额外发送一个名为 extra、值为 track-me-here-too 的 cookie 时,响应为 422,错误体与上面 JSON 结构一致,只是 loc 变为 ["cookie", "extra"]input 变为 "track-me-here-too"

值得注意的是:对比 test_tutorial001.py 中同名 test_cookie_param_model_extra 的用例——在 设置 extra: forbid 的 tutorial001 模型下,多余 cookie 会被静默忽略并返回 200;而开启 forbid 后则转为 422 报错。这说明“是否容忍额外 cookie”完全由 Pydantic model 的配置决定,两种策略各有用武之地。

小结与最佳实践

依据官方文档的总结,你完全可以 使用 Pydantic model 来声明 FastAPI 中的 cookies。结合本文源码与测试证据,实践要点可归纳为:

  1. 聚合声明:把一组相关的 Cookie 定义为 Pydantic model 字段,用 Annotated[Model, Cookie()](或 model: Model = Cookie())接收,替代逐个 Cookie() 参数的写法;
  2. 集中校验与复用:必填、默认值、类型等规则集中在 model 内,可在多个路径操作间复用;缺少必填 Cookie 时自动返回带 loc: ["cookie", ...]422
  3. 限制多余 Cookie:需要严格控制输入时,在 model 上设置 model_config = {"extra": "forbid"},越界 cookie 会触发 extra_forbidden 错误;默认情况下多余 cookie 会被忽略;
  4. 注意文档 UI 局限:浏览器安全策略导致 Swagger UI 无法发送 cookie,端到端调试请使用能自由设置 Cookie 头的 HTTP 客户端(参考测试中用 TestClientclient.cookies.set(...) 思路);
  5. 该模式可推广:同一技术在 QueryHeader 参数上同样适用(本次文档基于 Cookie 展开,相关机制可进一步查阅教程中 query/header 参数模型章节以及 fastapi/openapi/utils.py 中参数展平逻辑)。

更进一步想研究实现细节,可顺藤摸瓜阅读三处核心代码:负责参数分类的 fastapi/dependencies/utils.py、负责“用模型校验整包 cookie”的 request_params_to_args(同文件 fastapi/dependencies/utils.py),以及负责把模型摊平成多个 OpenAPI cookie 参数的 fastapi/openapi/utils.py。对照官方的教程原文 docs/en/docs/tutorial/cookie-param-models.md 阅读,效果最佳。

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