首页
/ FastAPI 全局依赖(Global Dependencies):为整应用统一执行校验依赖的完整指南

FastAPI 全局依赖(Global Dependencies):为整应用统一执行校验依赖的完整指南

2026-09-07 17:54:33作者:柯茵沙

导读

在部分应用中,你可能希望对整应用下的每一个请求都强制执行某类依赖,例如统一的 Token / API Key 校验、访问日志、请求计数或租户识别。FastAPI 提供了 FastAPI(dependencies=[...]) 这一应用级(全局)依赖声明方式:只需在创建应用时传入一次依赖列表,应用内所有 path operations(无论之后通过 @app.get() 等装饰器添加,还是通过子路由 include_router 引入)都会在请求进入时自动执行这些依赖。读完本文,你将掌握全局依赖的完整写法(Annotated 与传统默认参数两种风格)、其与路径操作装饰器级依赖的区别、底层执行机制,以及如何借助仓库中的测试与源码验证行为。

本文基于仓库文档 docs/es/docs/tutorial/dependencies/global-dependencies.md(对应英文原文 docs/en/docs/tutorial/dependencies/global-dependencies.md)及其配套示例代码展开,并结合 FastAPI 源码与测试用例进行原理印证。

一、为什么需要全局依赖

FastAPI 的依赖注入体系允许你在多处声明依赖,核心是 Depends

  1. 作为 path operation function 的参数——最常见,依赖返回值直接注入函数体内使用;
  2. 放在 path operation decoratordependencies 列表中——不需要返回值,只想让依赖“被执行”,详见 Dependencies in path operation decorators
  3. 放在 FastAPI() 应用构造器中——即本文主题,等价于把上面的“装饰器级依赖”批量应用到整应用所有路径操作

当你的应用存在大量路径操作,而每一层都需要校验同一套请求头(如 X-TokenX-Key)时,逐个在装饰器里重复写 dependencies=[Depends(verify_token)] 会非常冗长且易漏。全局依赖把“每个请求都必须通过校验”这一横切关注点收敛到应用创建处一处声明。

二、最小可用示例:声明全局依赖

官方示例位于 docs_src/dependencies/tutorial012_py310.py,下面结合注释完整展示其逻辑(Python 3.10+,经典默认参数风格):

from fastapi import Depends, FastAPI, Header, HTTPException


async def verify_token(x_token: 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: 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"}]

仓库同时提供了现代 Annotated 写法,见 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


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=[...]) 这一行(即源文档中高亮的第 17 行)完全相同。

与“装饰器级依赖”示例的对照

为了更直观理解全局依赖的定位,可对照装饰器级写法 docs_src/dependencies/tutorial006_an_py310.py

app = FastAPI()

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

两个示例中的 verify_token / verify_key 依赖函数与校验逻辑一模一样,唯一区别是 dependencies=[...] 声明的位置:一个挂在单个路径操作装饰器上,另一个挂在 FastAPI() 应用构造器上。tutorial006 只保护 /items/,而 tutorial012 同时保护 /items//users/。官方文档明确指出:装饰器级依赖的所有概念(可声明请求要求、可抛出异常、可返回值或返回值为空、返回值不会被使用)在全局依赖上依然成立,只是作用范围从“单一路径操作”扩大为“应用中所有路径操作”。

三、实际运行与行为验证

将上述任一文件保存后运行:

uvicorn tutorial012_an_py310:app --reload

或使用 FastAPI 的 CLI:fastapi dev tutorial012_an_py310.py

1. 缺少必要请求头 → 422 校验错误

由于 verify_tokenverify_key 声明了必填的请求头参数(没有默认值的 Header()),任何请求只要缺少其中一个头,就会在进入路径操作前被 FastAPI 的请求校验层拦截:

curl -i http://127.0.0.1:8000/items/

返回 422 Unprocessable Entity,响应体为 detail 列表,同时列出缺失的 x-tokenx-key 两个字段,typemissingloc 指明 ["header", "x-token"]

{
  "detail": [
    {"type": "missing", "loc": ["header", "x-token"], "msg": "Field required", "input": null},
    {"type": "missing", "loc": ["header", "x-key"], "msg": "Field required", "input": null}
  ]
}

注意:无论请求打到 /items/ 还是 /users/,都会得到同样的 422,这正是“全局”二字的体现。

2. 请求头值非法 → 400

携带错误的 Token:

curl -i -H "X-Token: invalid" http://127.0.0.1:8000/items/

verify_token 中的比较失败,主动 raise HTTPException(status_code=400, detail="X-Token header invalid")。即使 X-Key 正确、Token 错误也会被拦截;同理,Token 正确而 Key 错误时返回 "X-Key header invalid"

3. 两个请求头都正确 → 200

curl -i -H "X-Token: fake-super-secret-token" -H "X-Key: fake-super-secret-key" \
  http://127.0.0.1:8000/items/

此时依赖全部通过,路径操作正常执行并返回:

[{"item": "Portal Gun"}, {"item": "Plumbus"}]

访问 /users/ 则返回 [{"username": "Rick"}, {"username": "Morty"}]

需要说明的是,文档示例中的 X-Token / X-Key自定义的模拟头,仅用于演示机制。若做真实安全认证,应使用 FastAPI 集成的安全工具(OAuth2、APIKey、HTTP Bearer 等),这一点在 dependencies-in-path-operation-decorators.md 的注释中亦有强调。

四、全局依赖与装饰器级、函数参数级依赖的关系

返回值不会注入路径操作函数

与装饰器级依赖一致:在应用构造器中列出的依赖,即使返回了值(如上例 verify_key 返回了 x_key),这个值也不会被传给任何 path operation function。依赖在这里扮演的是“拦截/前置检查”角色。这也顺带规避了一个工程问题:如果以函数参数形式注入却不使用该参数,部分编辑器/工具会把它标为“未使用参数”错误,而采用装饰器级 / 应用级声明则不会。

三种依赖叠加执行

当全局依赖与路径操作内的依赖同时存在时,它们会叠加执行,互不排斥

  • 在路径操作函数参数中通过 Depends(...) 声明的普通依赖,依旧照常解析并把返回值传给函数;
  • 在路径操作装饰器 dependencies=[...] 中声明的依赖,同样会执行;
  • 应用构造器 FastAPI(dependencies=[...]) 声明的全局依赖,对每条路径操作都生效。

依据仓库对 APIRouter 文档 bigger-applications.md 中的描述,执行顺序为:先执行 Router 级依赖,再执行装饰器级 dependencies,最后才解析函数参数里的普通依赖。全局(应用级)依赖属于最外层,自然先于内层依赖执行——这意味着请求在到达具体业务函数前,已经历完整的“认证/鉴权链”。

典型适用场景

  • 全局 API 访问认证(每个端点都必须携带有效凭证);
  • 统一的请求头/环境信息校验(区域、版本、网关标记等);
  • 全局性的审计、日志、指标上报钩子;
  • 结合 app.dependency_overrides 在测试中替换昂贵的全局依赖(如鉴权服务、数据库连接)为桩实现。

五、源码层原理:全局依赖如何覆盖所有路径操作

从源码可以确认“全局依赖 = 应用内部路由器的默认依赖”,机制清晰且可验证。

1. FastAPI.__init__dependencies 参数

fastapi/applications.py 中,FastAPI 构造器接收 dependencies 参数,其文档字符串明确写道:“A list of global dependencies, they will be applied to each path operation, including in sub-routers(包含子路由器中的路径操作)”,并给出与示例完全一致的用法:

app = FastAPI(dependencies=[Depends(func_dep_1), Depends(func_dep_2)])

构造器内部把 dependencies 透传给应用内置的路由器(参见 applications.py):

self.router: routing.APIRouter = routing.APIRouter(
    ...
    dependencies=dependencies,
    ...
)

也就是说,应用级依赖最终落在 self.router(一个 APIRouter)的默认依赖列表中

2. APIRouter.add_api_route 将默认依赖合并进每条路由

APIRouter 构造时会把传入的依赖保存为实例字段(fastapi/routing.py):

self.dependencies = list(dependencies or [])

随后,每当通过 @app.get() / @app.post() 等注册一条路径操作(内部走 add_api_route),FastAPI 都会把路由器的默认依赖与本次操作自身的依赖合并后交给路由对象(fastapi/routing.py):

current_dependencies = self.dependencies.copy()
if dependencies:
    current_dependencies.extend(dependencies)

因此,无论这条路径操作是否额外声明依赖,都会携带 self.dependencies——也就是当初通过 FastAPI(dependencies=[...]) 传入的全局依赖。依赖列表最终被组装成 Dependant,在每次请求进入时被统一解析执行。

3. 通过子路由引入的路径操作同样受保护

FastAPI.include_router()(见 applications.py)在内部调用 self.router.include_router(...) 并将 dependencies 继续传递;APIRouter.include_router 合并父路由器的依赖并沿树传播(routing.py 附近),这从源码上印证了“包括子路由器路径操作”也会执行全局依赖。

六、测试用例验证

仓库为本文所述示例提供了端到端测试 tests/test_tutorial/test_dependencies/test_tutorial012.py,同时参数化加载 tutorial012_py310tutorial012_an_py310 两个示例模块,验证两种写法行为一致:

测试函数 请求方式 期望结果
test_get_no_headers_items / test_get_no_headers_users 不带任何请求头访问 /items//users/ 422detail 同时列出 x-tokenx-key 两个缺失字段
test_get_invalid_one_header_items / test_get_invalid_one_users 仅带 X-Token: invalid 400{"detail": "X-Token header invalid"}
test_get_invalid_second_header_items / test_get_invalid_second_users X-Token 正确、X-Key: invalid 400{"detail": "X-Key header invalid"}
test_get_valid_headers_items / test_get_valid_headers_users 两个头均正确 200,返回对应业务数据

测试还包含 test_openapi_schema:检查生成的 OpenAPI 中 /items//users/ 两条路径的 parameters 都包含必填的 x-tokenx-key 两个 header 参数,同时 schema 中携带 422HTTPValidationError 响应定义。这意味着全局依赖声明的请求头会如实反映到每个路径操作的接口文档中——在 /docs(Swagger UI)里,每个端点都会展示这两个必填头字段,这对前后端联调与 API 消费者非常友好。

七、为“一组路径操作”声明依赖(前瞻)

全局依赖解决“整应用都要”,但如果只想让一部分路径操作强制校验,官方文档将指引你走向“更大应用结构”(多文件、多 APIRouter)章节,见 Bigger Applications - Multiple Files。核心做法有两种:

  1. APIRouter() 构造器中声明 dependencies=[...],只影响挂在该路由器下的路径操作;
  2. app.include_router(router, dependencies=[...]) 时传入,只影响被包含的这个子路由组。

仓库在该教程中(结合 docs_src/bigger_applications/ 示例)明确说明:Router 级依赖会对该路由器内所有路径操作执行,并且“若你在具体路径操作里也声明了依赖,它们会一并执行”。应用级全局依赖、Router 级依赖、装饰器级依赖三者可以组合使用,形成“全局 + 分组 + 单点”的分层校验策略,从而优雅地控制代码重复。

八、小结与实践建议

  • 声明位置决定作用范围FastAPI(dependencies=[...]) 是最大范围(整应用,含子路由);APIRouter(dependencies=[...]) / include_router(..., dependencies=[...]) 是分组范围;装饰器 dependencies=[...] 是单条路径操作范围;函数参数 Depends 才需要把依赖返回值注入函数。
  • 适合横切校验:全局依赖对“每条请求都必须先过某道闸门”的需求几乎零样板,且统一出现在应用入口,便于审查。
  • 返回值默认被忽略:不要依赖全局依赖的返回值给业务函数传数据,需要数据时应改用函数参数注入。
  • 错误处理照常生效:依赖内可声明 Header/Query 等请求要求(缺失时返回 422),也可主动抛 HTTPException(可映射到任意状态码),可继续依赖其他子依赖。
  • 在测试中替换:这些全局依赖同样可以通过 app.dependency_overrides 在测试中被覆盖(FastAPI 将 dependency_overrides_provider=self 传入路由器),便于在测试环境把真实鉴权替换成假实现。
  • 真实认证请用安全工具:示例中的自定义头校验只是机制演示,落地时建议迁移到 OAuth2、API Key、Bearer 等标准安全方案。

通过源码(applications.pyrouting.py)与测试(test_tutorial012.py)的相互印证可以看到:全局依赖本质是“注册在应用内置路由器上的默认依赖,随每一条路径操作被合并执行”。理解这一点后,无论应用扩张到多少路由、多少文件,你都能准确预测依赖的执行范围与时机。

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