FastAPI Cookie 参数模型(Cookie Parameter Models):用 Pydantic 模型统一声明、校验与限制 Cookie
在 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):
- 该特性自 FastAPI
0.115.0版本起支持; - 相同的技术同样适用于
Query、Cookie和Header(即 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_id、fatebook_tracker、googall_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 效果图):
不过官方文档特别提醒(配合上图一起理解):
浏览器会以特殊且“幕后”的方式处理 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_id 是 required: true 的 in: 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返回422(type: 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。结合本文源码与测试证据,实践要点可归纳为:
- 聚合声明:把一组相关的 Cookie 定义为 Pydantic model 字段,用
Annotated[Model, Cookie()](或model: Model = Cookie())接收,替代逐个Cookie()参数的写法; - 集中校验与复用:必填、默认值、类型等规则集中在 model 内,可在多个路径操作间复用;缺少必填 Cookie 时自动返回带
loc: ["cookie", ...]的422; - 限制多余 Cookie:需要严格控制输入时,在 model 上设置
model_config = {"extra": "forbid"},越界 cookie 会触发extra_forbidden错误;默认情况下多余 cookie 会被忽略; - 注意文档 UI 局限:浏览器安全策略导致 Swagger UI 无法发送 cookie,端到端调试请使用能自由设置
Cookie头的 HTTP 客户端(参考测试中用TestClient的client.cookies.set(...)思路); - 该模式可推广:同一技术在
Query与Header参数上同样适用(本次文档基于 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 阅读,效果最佳。
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 StartedRust0627
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
