FastAPI 全局依赖(Global Dependencies):用 FastAPI(dependencies=...) 为整应用统一挂载公共依赖
在 FastAPI 中,依赖(Dependency)不仅可以挂在单个端点或路由上,还可以通过 FastAPI() 构造器的 dependencies 参数声明为全局依赖,使其自动作用于应用中的每一条路径操作(path operation),包括通过 include_router 挂载的子路由。读完本篇,你将掌握全局依赖的完整可运行示例、其生效范围与执行顺序、从源码层面验证“全局”二字的实现机制,以及它与 Router 级分组依赖、端点级 Depends 的取舍策略。
什么场景需要全局依赖
对于某些类型的应用(例如多租户 SaaS、需要统一鉴权的 API 网关),你希望每一次请求——无论访问哪个端点——都先经过相同的公共逻辑,比如:
- 校验统一携带的 Token / API Key 请求头;
- 解析租户(tenant)标识并写入上下文;
- 审计日志、限流标记等横切逻辑。
与其在每个端点函数里重复写 Depends(...),不如把这些依赖声明一次、作用于整个应用。这与你给路径操作装饰器添加 dependencies 列表的思路完全一致(参见官方文档 在路径操作装饰器中添加 dependencies),区别只在于作用域从“单个操作”放大到了“整个应用”。
完整示例:应用级 Token 与 Key 双重校验
官方教程对应的示例源码是 docs_src/dependencies/tutorial012_an_py310.py(标准 Annotated 风格),完整代码如下:
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
# 关键一行:把依赖声明在 FastAPI 应用上
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"}]
几个要点:
- 关键就是第 17 行
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])。dependencies参数接受一个Sequence[Depends],列表中每个元素都会应用到应用中的每条路径操作上。 - 端点函数
read_items和read_users本身没有任何依赖参数,但请求仍必须先通过verify_token与verify_key两道校验。 - 注意
verify_key虽然return x_key,但全局依赖列表中的依赖不会把返回值注入端点函数参数——这与路由级dependencies列表的语义一致。如果你需要在端点里拿到某个依赖的返回值,应当改用函数参数中的Depends(...)。 Header()默认开启convert_underscores,因此参数x_token对应请求头X-Token,x_key对应X-Key。
仓库同时提供等价的旧式默认参数风格版本 docs_src/dependencies/tutorial012_py310.py:
from fastapi import Depends, FastAPI, Header, HTTPException
async def verify_token(x_token: str = Header()):
...
async def verify_key(x_key: str = Header()):
...
app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
运行与验证
uvicorn tutorial012_an_py310:app --reload
请求缺失请求头(预期返回 400):
curl -i http://127.0.0.1:8000/items/
# HTTP/1.1 400 Bad Request
# {"detail":"X-Token header invalid"}
携带合法请求头(预期返回 200):
curl -i \
-H "X-Token: fake-super-secret-token" \
-H "X-Key: fake-super-secret-key" \
http://127.0.0.1:8000/items/
# HTTP/1.1 200 OK
# [{"item":"Portal Gun"},{"item":"Plumbus"}]
/users/ 端点表现完全相同——这正是“全局”的含义。
源码剖析:全局依赖如何落到每一条路由上
1. FastAPI 构造器:参数定义与传递
在 fastapi/applications.py 中,FastAPI.__init__ 的 dependencies 参数有如下类型与文档:
dependencies: Annotated[
Sequence[Depends] | None,
Doc(
"""
A list of global dependencies, they will be applied to each
*path operation*, including in sub-routers.
...
"""
),
] = None,
文档明确写着“applied to each path operation, including in sub-routers”。随后在构造函数内(fastapi/applications.py),该列表被原样传给应用的主路由:
self.router: routing.APIRouter = routing.APIRouter(
routes=routes,
...
dependencies=dependencies,
...
)
也就是说,应用级全局依赖本质上就是主 APIRouter 上的路由级依赖。
2. 子路由继承:为什么 include_router 进来的端点也会被覆盖
“全局”的关键在于路由的合并逻辑。在 fastapi/routing.py 的 APIRouter.include_router 中:
dependencies=[*parent_router.dependencies, *(dependencies or [])],
父路由(即主 app.router,携带着全局依赖列表)的 dependencies 会被前置合并进每一个被 include 的子路由上下文;随后注册具体路由时(fastapi/routing.py):
dependencies=[*self.include_context.dependencies, *route.dependencies],
每条 APIRoute 最终拿到的依赖列表 = 主路由(全局)依赖 + 子路由自身依赖 + 装饰器上的 dependencies。这就是为什么即使端点定义在 APIRouter(prefix="/v1") 里再 app.include_router(...),全局依赖依然生效。
3. 依赖如何被求解:_build_dependant_with_parameterless_dependencies
合并后的列表最终在 fastapi/routing.py 被转换进路由的依赖图:
def _build_dependant_with_parameterless_dependencies(
*,
dependant: params.Dependant,
dependencies: Sequence[params.Depends],
):
for depends in dependencies[::-1]:
dependant.dependencies.insert(
0,
get_dependant(path=dependant.path, call=depends.dependency),
)
return dependant
从源码结构看,这里以逆序遍历 + 逐个 insert(0) 的方式把列表依赖插入依赖图,最终保持了列表的声明顺序——即 dependencies=[Depends(verify_token), Depends(verify_key)] 会先执行 verify_token,它抛出 HTTPException 时 verify_key 就不会再被执行。每个请求处理时,主路由会调用 solve_dependencies(见 fastapi/routing.py)来真正求解依赖图并执行这些依赖函数。
4. 全局依赖同样支持 dependency_overrides
全局依赖在测试时可以像其他依赖一样被整体替换。FastAPI 实例上的 dependency_overrides 字典(fastapi/applications.py)用于“把昂贵的依赖替换成测试版本”:
from fastapi.testclient import TestClient
from docs_src.dependencies.tutorial012_an_py310 import app, verify_key, verify_token
def fake_token():
pass
def fake_key():
return "fake-key"
app.dependency_overrides[verify_token] = fake_token
app.dependency_overrides[verify_key] = fake_key
client = TestClient(app)
r = client.get("/items/") # 无需真实请求头即可通过校验
print(r.json())
这说明测试全局约束(如鉴权)时,不需要真的伪造合法的 Token 请求头,直接覆写依赖即可。
三种作用域对比:FastAPI 级、Router 级、装饰器级
| 声明位置 | 生效范围 | 代码形态 |
|---|---|---|
FastAPI(dependencies=[...]) |
整个应用的所有路径操作,含子路由 | app = FastAPI(dependencies=[Depends(func_dep_1)]) |
APIRouter(dependencies=[...]) / app.include_router(..., dependencies=[...]) |
该路由(组)下的所有路径操作 | router = APIRouter(dependencies=[Depends(get_token_header)]) |
路径操作装饰器 @app.get("/x/", dependencies=[...]) |
单条路径操作 | @app.get("/generate", dependencies=[Depends(get_user)]) |
| 端点函数参数 | 单个端点(且返回值可注入) | def f(user=Depends(get_user)) |
官方教程在讲解全局依赖时同时给出了分组依赖的预告:当你阅读关于如何组织大型应用(官方文档 Bigger Applications – Multiple Files 一节)时,会学到如何为一组路径操作声明单个 dependencies 参数——即在 APIRouter 构造器或 include_router 上传入 dependencies。在 fastapi/applications.py 中,include_router 的 dependencies 参数文档也给出了同样的示例:
app.include_router(
users_router,
prefix="/users",
dependencies=[Depends(get_current_user)],
)
经验法则是:能收窄就收窄。只有“所有端点无一例外都需要”的逻辑(如统一鉴权头)才值得放进 FastAPI(dependencies=...);只有一部分端点需要的校验应放在 Router 级或端点参数上,避免无关端点承担多余的求解开销与耦合。
测试用例印证
官方测试套件对本文示例做了回归验证:tests/test_tutorial/test_dependencies/test_tutorial012.py 同时参数化覆盖了 tutorial012_py310 与 tutorial012_an_py310 两种写法,确保“应用级 dependencies 对全部端点生效、缺失请求头返回 400”的行为在不同风格写法下保持一致。
小结
FastAPI(dependencies=[Depends(...), ...])是声明应用级全局依赖的唯一入口,官方文档对应页面为 docs/de/docs/tutorial/dependencies/global-dependencies.md;- 从源码看,它被传入主
APIRouter,并在include_router/ 路由注册时通过[*parent_router.dependencies, *route.dependencies]逐级合并,保证子路由中的端点同样被覆盖; - 列表中依赖按声明顺序求解,前置依赖抛错即短路后续依赖;其返回值不注入端点参数;
- 全局依赖可被
app.dependency_overrides整体替换,便于测试; - 若只需要覆盖“一部分”端点,优先使用
APIRouter(dependencies=...)或装饰器级dependencies=[...],而不是应用级参数。
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