FastAPI 全局依赖(Global Dependencies):为整个应用统一施加校验与预处理
本指南围绕 FastAPI 官方教程中的"Global Dependencies"章节展开,讲解如何把依赖从单个路径操作(path operation)提升到整个应用级别,让应用内每一个路由在进入处理函数之前都统一执行身份令牌、API Key 校验等逻辑。读完本文,你将掌握 FastAPI(dependencies=[...]) 的完整用法、其与路径操作装饰器级依赖的差异,以及它在子路由、依赖覆盖测试等场景中的实际行为,并能直接对照本仓库的示例代码与测试用例进行验证。
什么时候需要"应用级"的依赖
在某些类型的应用中,你会希望对整个应用(而不是单个路由)统一添加依赖。典型场景包括:
- 全站都需要校验的访问令牌(如
X-Token请求头); - 全站都需要校验的 API Key(如
X-Key请求头); - 需要在每次请求开始前统一执行的日志、鉴权、限流等横切逻辑。
这与在路径操作装饰器中添加 dependencies的做法非常相似,区别只在于作用范围:前者仅对当前这一个路由生效,而后者会作用于应用内所有的路径操作。你同样可以把它们加到 FastAPI 应用上,这就是全局依赖。
快速上手:把依赖传入 FastAPI() 构造函数
全局依赖的声明方式极其简单:在创建 FastAPI() 实例时,通过 dependencies 参数传入一个由 Depends() 组成的列表即可。以下示例取自本仓库的官方示例源码 docs_src/dependencies/tutorial012_an_py310.py:
from typing import Annotated
from fastapi import Depends, FastAPI, Header, HTTPException
async def verify_token(x_token: Annotated[str, Header()]):
if x_token != "fake-super-secret-token":
raise HTTPException(status_code=400, detail="X-Token header invalid")
async def verify_key(x_key: Annotated[str, Header()]):
if x_key != "fake-super-secret-key":
raise HTTPException(status_code=400, detail="X-Key header invalid")
return x_key
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
@app.get("/items/")
async def read_items():
return [{"item": "Portal Gun"}, {"item": "Plumbus"}]
@app.get("/users/")
async def read_users():
return [{"username": "Rick"}, {"username": "Morty"}]
请注意加粗高亮的一行 app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)]):两个依赖被声明在应用创建阶段。这样一来,/items/、/users/ 以及此后新增的每一个路由,都会在进入各自的路径操作函数前先执行 verify_token 与 verify_key。
这段代码的请求校验语义是:
- 每个请求都必须携带
X-Token请求头,且其值必须等于fake-super-secret-token,否则抛出HTTPException(400),响应体为{"detail": "X-Token header invalid"}; - 每个请求还必须携带
X-Key请求头,且值必须等于fake-super-secret-key,否则抛出HTTPException(400),响应体为{"detail": "X-Key header invalid"}; - 若校验通过,依赖会继续向后续依赖链返回结果(
verify_key返回了x_key),但应用级依赖的返回值不会传入路径操作函数——路径函数无需、也无法接收它。
提示:
X-Token、X-Key是教程中虚构的自定义请求头。在真实项目中做安全校验时,通常使用 FastAPI 内置的 Security 工具(OAuth2、JWT、API Key 等)收益更大;本节内容更适合理解"应用级横切逻辑"这一通用机制。
验证行为:缺失或错误的请求头会得到什么响应
本仓库针对该示例提供了完整的端到端测试 tests/test_tutorial/test_dependencies/test_tutorial012.py,可以直接佐证上述行为。例如,测试中先访问了不带请求头的接口,确认两个路由都会被拦截:
response = client.get("/items/") # 或 client.get("/users/")
assert response.status_code == 400
assert response.json() == {"detail": "X-Token header invalid"}
当 X-Token 正确但 X-Key 错误时:
response = client.get(
"/items/",
headers={"X-Token": "fake-super-secret-token", "X-Key": "invalid"},
)
assert response.status_code == 400
assert response.json() == {"detail": "X-Key header invalid"}
只有当两者都正确时,请求才会进入路径操作并正常返回数据:
response = client.get(
"/items/",
headers={
"X-Token": "fake-super-secret-token",
"X-Key": "fake-super-secret-key",
},
)
assert response.status_code == 200
assert response.json() == [{"item": "Portal Gun"}, {"item": "Plumbus"}]
该测试文件还对 OpenAPI Schema 做了断言:因为依赖声明了 Header() 参数,/items/ 与 /users/ 两个接口在交互式文档(/docs)中都会把 X-Token 标注为必填请求头。这正是"全局依赖"与"每个路由单独声明"表现一致的地方——所有接口的文档都会被更新。
运行验证方式:上述逻辑通过 FastAPI 官方测试工具完成,仓库内实际命令可参考 scripts/test.sh 中约定的测试运行方式,也可以直接在项目根目录使用你的测试运行器执行
tests/test_tutorial/test_dependencies/test_tutorial012.py。
全局依赖与"路径操作装饰器依赖"的关系
全局依赖并不是一套全新的机制,它复用了路径操作装饰器级依赖的全部思想,只是把作用域从"单个路由"扩展到"整个应用"。可对照的按路由声明版本见 docs_src/dependencies/tutorial006_an_py310.py,其差异仅为依赖挂在单个路由上:
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
return [{"item": "Foo"}, {"item": "Bar"}]
两处声明依赖的共同点在于:
- 执行方式相同:都会被当成普通依赖解析与执行;
- 返回值均不使用:在装饰器的
dependencies里声明的依赖即使返回了值,也不会传给路径操作函数。正因如此,声明在"全局"或"装饰器"里的依赖不需要(也不应该)被声明为路径函数参数,从而避免编辑器报"未使用参数",也让新读者不至于误删看似多余的参数。
其区别如下表所示:
| 对比维度 | 路径操作装饰器依赖 | 应用级(全局)依赖 |
|---|---|---|
| 声明位置 | @app.get("/items/", dependencies=[...]) |
app = FastAPI(dependencies=[...]) |
| 作用范围 | 仅当前这一个路径操作 | 应用中所有路径操作 |
| 是否作用于之后 include 的子路由 | 否 | 是 |
| 返回值是否传入路径函数 | 否 | 否 |
| 能否声明请求需求(Header 等) | 能 | 能 |
| 能否抛出异常中断请求 | 能 | 能 |
关于"该章节中关于路径操作装饰器依赖的全部要点在此同样适用",具体可展开为以下三类能力(与教程 dependencies-in-path-operation-decorators.md 中描述一致):
- 依赖可以声明自身的请求需求或子依赖:如示例中的
verify_token(x_token: Annotated[str, Header()]),FastAPI 会先从请求头解析出x_token,再执行函数体; - 依赖可以抛出异常:校验失败时
raise HTTPException(status_code=400, ...)会立即中断请求并返回错误响应,后续代码不会执行; - 依赖可以返回值也可以不返回:返回值只是给依赖链中的下游使用(例如本例
verify_key返回x_key),对于应用级声明,该值最终不会被用于任何路径操作,但依赖本身一定会被执行。
因此,你完全可以把一个已经在别处复用的普通依赖(返回值的那个)同时挂到应用级,即便它的返回值用不到,校验逻辑依然会被执行。
源码视角:全局依赖是如何生效的
从源码结构看,全局依赖的实现路径非常清晰。在 fastapi/applications.py 中,FastAPI.__init__ 接收可选的 dependencies 参数,其类型标注为 Sequence[Depends] | None,官方文档字符串明确写道:
A list of global dependencies, they will be applied to each path operation, including in sub-routers.
(全局依赖列表,会被应用到每一个路径操作,包括子路由中。)
接着,FastAPI 在初始化时会创建内部路由 self.router,并把该参数原样传递给 APIRouter(见 fastapi/applications.py):
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
redirect_slashes=redirect_slashes,
dependency_overrides_provider=self,
...
dependencies=dependencies,
)
也就是说,所有通过 @app.get()、@app.post() 等装饰器注册到 self.router 上的路由,以及之后通过 app.include_router(...) 挂载的子路由(FastAPI 的 Bigger Applications 多文件组织方式 场景),都会继承这一组全局依赖。可以推断:因为应用自身的全部路由最终都收敛于这一个 APIRouter,把依赖放在这里等价于"每个路由天然携带这份依赖",这与官方文档"它们会被应用于应用中所有的路径操作"的描述完全吻合。
这一实现细节也解释了使用要点:全局依赖应在创建应用实例时就确定,它面向的是"整个应用统一策略";若你只想对某一组路由或某个路由生效,则应把 dependencies 声明在 APIRouter() 或路由装饰器上(具体见下文)。
对"一组路径操作"施加依赖
官方文档特别指出:当应用逐渐变大、需要拆分到多个文件时,你会学到如何为"一组路径操作"声明一个统一的 dependencies 参数——这正是 Bigger Applications - Multiple Files 章节的内容。简单预告其思路:
- 用
APIRouter(prefix=..., tags=..., dependencies=[Depends(...)])创建子路由,则该子路由内所有路径操作都会执行这份依赖; - 再用
app.include_router(router)挂载到主应用。
由此形成三种作用域从窄到宽的依赖声明层级:单个路径操作装饰器 → 某个 APIRouter 分组 → 整个 FastAPI 应用。选择哪一个,取决于校验逻辑的覆盖范围:全站统一用全局依赖,仅某个模块(如 /admin 前缀下的管理接口)需要时用分组依赖。
测试与替换:依赖覆盖依然可用
全局依赖本质仍是普通依赖,因此 FastAPI 的依赖覆盖机制(app.dependency_overrides)对它同样适用。在 fastapi/applications.py 中可以看到,FastAPI 实例持有 dependency_overrides 字典(每个键为原始依赖可调用对象,值为实际要调用的替代版本),并把它作为 dependency_overrides_provider 传给了内部路由。这意味着在测试阶段,你可以把挂在全局的 verify_token 等真实校验替换为测试用桩依赖,从而绕开真实鉴权去测业务逻辑。这与仓库中 tests/test_tutorial/test_dependencies/ 目录下多个教程测试的验证思路一致,也适用于你为应用编写的自动化测试。
小结
- 全局依赖通过在
FastAPI(dependencies=[Depends(...), ...])传入,应用于应用中所有路径操作(含子路由),每个请求进入业务函数前都会依次执行它们; - 它复用普通依赖的全部能力:声明 Header 等请求需求、依赖其他子依赖、抛出异常、返回值(返回值不会被使用);
- 它适合全站统一鉴权、请求头校验等横切逻辑;按模块分组时改用
APIRouter(dependencies=...),仅单个接口时用路径操作装饰器的dependencies; - 官方示例与测试分别位于 docs_src/dependencies/tutorial012_an_py310.py 与 tests/test_tutorial/test_dependencies/test_tutorial012.py,可对照阅读以加深理解。
掌握这一机制后,你便能用最小样板为整站统一施加"进入业务逻辑前"的约束,让鉴权与校验策略集中管理、一处维护、处处生效。
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 StartedRust0624
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