FastAPI 全局依赖(Global Dependencies):用FastAPI(dependencies=[...])为整个应用统一注入校验逻辑
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
全局依赖是 FastAPI 依赖注入体系在“应用(Application)级”的应用方式:与在单个路径操作装饰器上声明dependencies类似,你可以在创建FastAPI()实例时传入一个依赖列表,使这些依赖被应用到该应用内的每一个路径操作上。本文以 docs/fr/docs/tutorial/dependencies/global-dependencies.md 为核心,结合仓库内的可运行示例(docs_src/dependencies/tutorial012_an_py310.py)、单元测试(tests/test_tutorial/test_dependencies/test_tutorial012.py)以及 fastapi/applications.py 源码,带你掌握:如何编写应用级依赖、它与路径装饰器依赖的异同、内部是如何传递到每条路由的,以及如何用include_router(..., dependencies=...)为“一组路径”统一声明依赖。
为什么要用全局依赖
对于某些类型的应用,你希望某些检查或逻辑无条件作用于所有接口,而不是逐个手写。典型场景包括:
- 全站统一的简单鉴权或网关校验(如校验每个请求都携带的
X-Token、X-Key头); - 全站统一的访问审计、限流计数、租户上下文解析;
- 全站统一的入口级异常拦截或资源初始化。
你当然可以把这些依赖逐条写进每一个@app.get(...)的dependencies=[...]里,但那样既重复又容易漏写。FastAPI 允许把同样的依赖列表直接交给FastAPI应用本身——正如你可以在路径操作装饰器上添加dependencies,你也可以把它们添加到FastAPI应用上。
基本用法:把依赖挂到FastAPI()构造器
当依赖被加到应用级别后,它们会作用于应用里的所有路径操作。仓库中完整的可运行示例位于 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=[Depends(verify_token), Depends(verify_key)])FastAPI()构造器接受一个可选的dependencies参数,它是一个由Depends()组成的列表。这里的两个依赖都没有被任何端点函数作为参数使用,但每次请求进入/items/或/users/时,它们都会被自动解析并执行。
仓库同时提供了不使用Annotated的等价版本 docs_src/dependencies/tutorial012_py310.py,仅将参数写法换成默认值形式:
async def verify_token(x_token: str = Header()): ...两种写法行为一致,Annotated版本是当前 FastAPI 文档推荐的主流风格。
逐行解读两个校验依赖
verify_token:声明了一个必填的请求头参数x_token(Header()不带默认值即为必填)。如果它不等于预设的fake-super-secret-token,就抛出HTTPException(status_code=400, detail="X-Token header invalid")。verify_key:同样声明必填请求头x_key并校验值,校验通过后return x_key。注意:这个返回值并不会被传给任何端点函数(因为全局依赖没有对应的函数参数),它只是“顺便返回”而已,完全不影响逻辑。
说明:示例中使用的是虚构的
X-Key/X-Token自定义头。在真实项目中实现安全校验时,更推荐使用内置的 安全相关工具,这里的写法只是为了演示“依赖会在每个路径上被执行”这一机制。
与路径操作装饰器dependencies的异同
在深入全局依赖前,先回顾一下它在路径操作装饰器上的兄弟用法:
@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)]) async def read_items(): return [{"item": "Foo"}, {"item": "Bar"}]对照仓库示例 docs_src/dependencies/tutorial006_an_py310.py 可以看出,两者的核心机制完全一致,只是作用范围不同:
| 声明位置 | 写法 | 生效范围 |
|---|---|---|
| 路径操作装饰器 | @app.get("/items/", dependencies=[...]) | 仅该路径操作 |
| FastAPI 应用实例 | FastAPI(dependencies=[...]) | 应用中全部路径操作 |
| APIRouter / include_router | include_router(router, dependencies=[...]) | 该路由分组下的路径操作 |
无论声明在哪一层,这些依赖都会:
- 与普通依赖一样被解析(resolve)与执行;
- 不把返回值注入端点函数的参数(因此可以放心复用那些“会返回值”的普通依赖,即使返回值用不上,依赖也照常执行);
- 支持声明自身的请求约束(如必填头参数),也能继续嵌套依赖其他子依赖;
- 可以
raise异常(如HTTPException)来中断请求。
在应用级声明还有一个附带好处:端点函数签名保持干净,不会出现“看起来没用”的未使用参数,避免编辑器静态检查告警和初学者的困惑——这一点同样来自路径操作装饰器依赖文档中的 Tip 说明。
用测试用例验证全局依赖的行为
仓库的测试 tests/test_tutorial/test_dependencies/test_tutorial012.py 同时针对tutorial012_py310与tutorial012_an_py310两个模块运行,用TestClient逐一验证了全局依赖的各种行为。下面这些断言就是文档所述机制的直接证据:
缺少请求头 → 422 校验错误,且两个端点表现一致:
def test_get_no_headers_items(client: TestClient): response = client.get("/items/") assert response.status_code == 422, response.text assert response.json() == { "detail": [ {"type": "missing", "loc": ["header", "x-token"], "msg": "Field required", "input": None}, {"type": "missing", "loc": ["header", "x-key"], "msg": "Field required", "input": None}, ] }对应文件中的test_get_no_headers_users断言/users/返回完全相同的 422,验证了“全局依赖作用于所有路径操作”。
请求头值错误 → 400,detail 指出是哪一个头非法:
response = client.get("/items/", headers={"X-Token": "invalid"}) assert response.status_code == 400 assert response.json() == {"detail": "X-Token header invalid"}两个头都正确 → 200 并正常返回业务数据:
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"}]此外,测试里的test_openapi_schema还印证了一个细节:/items/与/users/两个路径的 OpenAPI 描述中都自动带上了x-token、x-key两个必填的header参数。也就是说,应用级依赖所声明的请求约束会扩散到每一个路径操作的 API 文档与参数校验中——这是它和“中间件式”黑盒处理的一个重要区别:依赖的请求参数是可声明、可校验、可文档化的。
源码视角:dependencies是如何传到每条路由的
FastAPI 应用层的全局依赖并非魔法,它的传递链在源码中清晰可见。在 fastapi/applications.py 中,FastAPI.__init__的签名里就包含dependencies参数,其 docstring(约第 331–348 行)明确写着:
A list of global dependencies, they will be applied to each request...(一组全局依赖,它们会被应用到每一个请求)
FastAPI 实例在内部维护了一个APIRouter(即self.router),构造FastAPI()时传入的dependencies会被交给这个内部路由器,而通过@app.get(...)、@app.post(...)等注册的每个路径操作,本质上都是在往这个路由器上添加路由。因此,应用级的dependencies会对之后注册的、以及经由include_router挂载进来的所有路径操作统一生效。
从实现角度看,可以理解为 FastAPI 采用了“路由器级依赖”的聚合模型:无论依赖声明在应用层还是路由分组层,最终都会在路由构建阶段被合并进对应路径操作的依赖解析链中,由 FastAPI 的依赖求解器统一处理(包括子依赖、缓存、异常上抛等语义)。这也是为什么测试中verify_token抛出的HTTPException(status_code=400)能精确中断请求并返回该状态码。
进阶:为“一组路径操作”声明依赖
全局依赖只提供“全部”与“单个路径”两个极端,中间的“一组路径”粒度该怎么做?原文档在结尾给出指引:阅读更大的应用 - 多文件结构教程,学习如何为一个路径操作分组声明单个dependencies参数。
这一能力的落点有两个:
- 创建
APIRouter时传dependencies,使该分组内所有路由共享一组依赖; - 在
app.include_router(...)挂载时传dependencies。
例如(示意,模式来自 applications.py 中include_router的dependencies参数说明):
from fastapi import APIRouter, Depends router = APIRouter( prefix="/admin", dependencies=[Depends(verify_token)], ) @router.get("/stats/") async def get_stats(): return {"ok": True} app.include_router(router)这样/admin/stats/及其余所有挂在该router下的路径,都会先经过verify_token。在 fastapi/applications.py 中,include_router(约第 1441–1521 行)同样开放了dependencies参数(docstring 明确说明“一组会被应用到该分组下所有路径操作的依赖”),并会把它转发给内部的router.include_router(...)(见 applications.py 中调用处)。
于是你可以把依赖按作用域组织成清晰的层次:应用级(全站强制逻辑)→路由分组级(模块化子应用的公共逻辑,配合 更大的应用教程 的多文件结构)→路径装饰器级(单接口特有逻辑)→函数参数级(需要消费返回值时)。
实践建议与使用边界
结合示例代码、测试与源码,使用全局依赖时有几点值得留意:
- 适合放全局依赖的:对整站所有接口无条件生效的入口校验、统一约定检查。这类逻辑通常“只需要被执行,不需要消费返回值”,正好契合
dependencies列表的语义。 - 不要混淆依赖与中间件:全局依赖仍然拥有完整的参数声明/校验能力,会把其参数并入每个路径的 OpenAPI 文档(测试的
test_openapi_schema已证明);而Middleware是更底层的 WSGI/ASGI 层面的处理。需要“可声明、可校验、可文档化”的请求约束时优先考虑依赖。 - 依赖也可以有自己的依赖:全局依赖函数内同样可以继续声明请求参数或嵌套
Depends,完整复用依赖注入体系的所有能力。 - 返回值为空或不被使用都没关系:如
verify_token不返回值,verify_key返回了值但无处接收——两者都会在每个请求到来时按声明顺序被解析执行。 - 真实安全场景:示例的硬编码 Token 头仅用于教学;做生产级认证时应改用 OAuth2、API Key 等内置安全方案(见安全教程),避免在代码中写死密钥比对。
如果想在本地亲手跑一遍效果,可以基于仓库的测试模块启动验证:把 docs_src/dependencies/tutorial012_an_py310.py 中定义的app交给fastapi.testclient.TestClient(与 tests/test_tutorial/test_dependencies/test_tutorial012.py 的做法一致),分别用“无头请求、错误 Token、正确 Token、缺 Key、全部正确”等组合打向/items/和/users/,即可观察 422、400、200 三种结果——这正是“一份依赖,全站生效”最直观的验证方式。
【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考