首页
/ FastAPI 在 Path Operation 装饰器中声明依赖:`dependencies` 参数实战指南

FastAPI 在 Path Operation 装饰器中声明依赖:`dependencies` 参数实战指南

2026-09-07 20:42:52作者:秋泉律Samson

导读

在 FastAPI 的依赖注入体系中,大部分场景通过 Depends()path operation 函数 的参数上注入依赖,并把返回值交给业务逻辑使用。但有一种常见诉求是:某个依赖只需要"被执行/被解析",而它的返回值并不需要进入你的业务函数——例如仅用于校验请求头、执行鉴权、记录日志的副作用型依赖。本篇文章将聚焦 FastAPI 教程中 dependencies-in-path-operation-decorators 章节 讲解的解决方案:把 dependencies 作为一个 list 传给 path operation 装饰器。读完后你将掌握它的正确写法、错误处理与返回值语义、底层执行原理,以及它与 Router 级、全局级依赖的延伸关系。

为什么需要"装饰器级依赖"而非函数参数

在某些情况下,你并不真的需要某个依赖在 path operation function 里的返回值:

  • 依赖本身不返回任何值,例如一个只负责验证请求头合法性的校验器;
  • 依赖会返回一个值,但该值在你的路径函数中根本没有用武之地(比如其它地方已能取得同样的信息)。

如果在这种场景下仍然把依赖声明为 path operation function 的普通参数,就会产生副作用:

  1. 一些编辑器/IDE 会检查"未使用的函数参数"并将其高亮为错误或警告;
  2. 团队中的新开发者看到代码里有一个从未被使用的参数,可能误以为它是多余的、可以删除——而一旦删除,依赖也就不会执行了,很可能破坏掉隐含的校验逻辑。

为了解决这些场景,FastAPI 允许在 path operation 装饰器 上通过一个可选的 dependencies 参数来声明依赖,而不是把依赖作为函数参数。这些依赖仍会像普通依赖一样被"执行/解析",但它们的返回值不会被传入路径函数。

dependencies 加到 path operation 装饰器

path operation 装饰器@app.get()@app.post() 等)接收一个可选的 dependencies 参数。它必须是一个由 Depends() 组成的 list

{* ../../docs_src/dependencies/tutorial006_an_py310.py hl[19] *}

两种等价的代码写法

仓库中为该教程提供了两个示例文件,二者功能完全一致,仅在类型声明风格上有差异:

写法一:基于 Annotated 的版本docs_src/dependencies/tutorial006_an_py310.py

from typing import Annotated

from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()


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.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

写法二:依赖默认值版本的等价写法docs_src/dependencies/tutorial006_py310.py),把 Annotated[str, Header()] 等价地写成 str = Header(),其余逻辑一致。两种写法编译与运行结果完全相同,教程测试对两个文件都会运行(见下文测试章节)。

关键点拆解

  • 装饰器中的 dependencies 是一个列表,按顺序容纳多个 Depends(...) 项;
  • 每个 Depends(...) 里的依赖既可以是一个函数,也可以是一个可调用对象/类(从底层实现看,FastAPI 通过 get_dependant 统一将其解析为子依赖,见下文原理章节);
  • 这些依赖与普通依赖一样具备完整的请求上下文能力——它们可以读取 header、query、cookie、body,也可以依赖其它"子依赖",并共享同一套缓存机制;
  • 与函数参数式依赖唯一的差别:解析结果不会被注入到 read_items() 的签名中,所以 read_items() 保持参数极简、意图清晰。

::: tip 提示 利用这种装饰器级依赖,可以保证依赖确实被执行,同时规避编辑器对"未使用参数"的误报;也能避免新开发者误删看似无用的参数而破坏隐含逻辑。 :::

::: note 关于示例中的自定义请求头 教程示例中使用了自造的请求头 X-KeyX-Token 来演示思路。但请注意:在生产项目中做真实的鉴权/安全控制,应优先使用下一章将要讲解的内置安全工具FastAPI 内置安全工具),例如 OAuth2HTTPBearerAPIKey 等。这里的自定义头示例仅用于说明"装饰器级依赖"这个通用机制。 :::

依赖中声明的请求要求(依赖的依赖)

放在装饰器 dependencies 列表里的依赖函数,与放在函数参数里的依赖在能力上没有任何差别——它们可以正常声明对请求的要求(例如 header 参数),也可以继续声明自己的子依赖:

async def verify_token(x_token: Annotated[str, Header()]):  # 声明了一个必填请求头 x_token
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")

在上面这段来自 docs_src/dependencies/tutorial006_an_py310.py 的代码里,verify_token 通过 Header() 声明它需要读取名为 X-Token 的请求头。这意味着:

  • 一旦请求缺少 X-Token 头,FastAPI 会在解析依赖时返回 422 Validation Error(校验失败),根本不会进入路径函数;
  • 依赖参数里声明的请求要求,与普通路径函数参数一样会被自动纳入 OpenAPI schema 的文档(Swagger UI 中会显示为必填 header 参数)。

这一点已被仓库测试直接验证:测试 tests/test_tutorial/test_dependencies/test_tutorial006.py 中的 test_get_no_headers 断言——不带任何请求头请求 /items/ 时返回 422,且响应体里同时列出 x-tokenx-key 两个字段缺失的错误详情;而 test_openapi_schema 则断言 /openapi.json/items/ 路径下的 GET 操作确实把 x-tokenx-key 都声明为 required: true 的 header 参数。这说明装饰器级依赖的请求声明同样会被完整暴露到 OpenAPI 契约里

抛出异常:把校验失败挡在业务逻辑之前

这些依赖可以像普通依赖一样抛出异常(例如 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")

对应测试 test_get_invalid_one_headertest_get_invalid_second_header(见 tests/test_tutorial/test_dependencies/test_tutorial006.py)验证了:

  • 只带错误 X-Token 时,返回 400{"detail": "X-Token header invalid"}
  • 即使 X-Token 正确但 X-Key 错误,仍返回 400{"detail": "X-Key header invalid"}
  • 只有当两个头都正确(fake-super-secret-token / fake-super-secret-key)时,请求才会进入 read_items,返回 200[{"item": "Foo"}, {"item": "Bar"}]

值得强调的是,dependencies 列表中的多个依赖会按顺序依次解析,任何一个抛出异常都会让请求在进入路径函数前被中止。因此这是一种把"横切关注点"(校验、鉴权)从业务函数中剥离出来、集中声明的干净做法。

返回值存在与否都不影响执行

装饰器级依赖"可以返回值,也可以不返回",但无论如何,其返回值不会被使用。这带来一个非常实用的推论:

你可以复用一个本来"会返回值"的普通依赖(例如已经写好的、用于其它路径函数的依赖),把它同时挂在装饰器的 dependencies 里;虽然它的返回值在这里用不到,但它依然会被完整执行。

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  # 返回值存在,但在装饰器级依赖场景下不会被使用

对比同一目录下教程中的其它示例即可理解这种复用价值:例如 docs_src/dependencies/tutorial012_an_py310.py(全局依赖示例)复用了完全相同的依赖函数写法。依赖本身是普通函数,它是否被"消费返回值"由使用方式决定,而不是写死在函数里。

底层原理:FastAPI 是如何"无参"执行这些依赖的

从源码层面可以更清楚地看到装饰器级依赖的运行机制。

fastapi/routing.py 中,APIRoute 在构造阶段会调用 _build_dependant_with_parameterless_dependencies

def _build_dependant_with_parameterless_dependencies(
    *,
    path: str,
    call: Callable[..., Any],
    dependencies: Sequence[params.Depends],
) -> tuple[Dependant, list[ModelField], bool]:
    dependant = get_dependant(path=path, call=call, scope="function")
    for depends in dependencies[::-1]:
        dependant.dependencies.insert(
            0,
            get_parameterless_sub_dependant(depends=depends, path=path),
        )
    ...

也就是说,FastAPI 会把装饰器 dependencies 列表里的每一项通过 get_parameterless_sub_dependant 转化为 "无参数子依赖"(parameterless sub-dependant),并逆序插入 Dependant 的依赖链。因此它们在请求处理阶段(solve_dependencies,见 fastapi/dependencies/utils.py)会像函数签名里的依赖一样被正常解析、缓存与执行——唯一的差别是它们并不产生供路径函数使用的 kwargs

get_parameterless_sub_dependantfastapi/dependencies/utils.py)的实现进一步揭示了限制与能力边界:

def get_parameterless_sub_dependant(*, depends: params.Depends, path: str) -> Dependant:
    assert callable(depends.dependency), (
        "A parameter-less dependency must have a callable dependency"
    )
    ...
    return get_dependant(
        path=path,
        call=depends.dependency,
        scope=depends.scope,
        own_oauth_scopes=own_oauth_scopes,
    )

要点解读:

  • depends.dependency 必须可调用,否则会触发断言错误 "A parameter-less dependency must have a callable dependency"——这意味着不能把无法直接调用的对象塞进装饰器 dependencies
  • 它同样支持 SecurityOAuth2 scopes 等高级场景(代码中单独处理了 depends.scopes);
  • 由于这些依赖会被并入同一套 solve_dependencies 解析管线,它们天然支持 子依赖缓存(同一请求内对同一依赖只解析一次)、yield 依赖的清理逻辑 等全部 FastAPI 依赖注入特性。

验证该行为的一手测试用例

仓库为本章提供了完备的自动化测试,见 tests/test_tutorial/test_dependencies/test_tutorial006.py。该测试通过 pytest fixture 对 tutorial006_py310tutorial006_an_py310 两个示例分别创建 TestClient,并断言了四条核心行为:

场景 请求构造 期望结果
不带任何请求头 client.get("/items/") 422,缺失 x-tokenx-key 两个字段
只带错误的 X-Token X-Token: invalid 400{"detail": "X-Token header invalid"}
X-Token 正确但 X-Key 错误 两个头都提供 400{"detail": "X-Key header invalid"}
两个头都正确 两个头都为 fake-... 200,返回 [{"item": "Foo"}, {"item": "Bar"}]

此外 test_openapi_schema 用快照断言了 /openapi.json 的完整结构,证明这两个自定义 header 被正确记录进 OpenAPI 契约。如果你希望用本仓库代码做一次本地验证,可参考 tests/utils.pyTestClient 的使用约定,结合 uvicorn 运行示例文件后按上表构造请求即可复现。

为一组 path operation 声明依赖(路由/子应用层面)

装饰器级 dependencies 的作用域是单条 path operation。当你面对更大的应用、需要把路由拆分到多个文件时,就会希望"对一组路径统一声明依赖",而不是在每一条路径上重复粘贴同一个 dependencies=[...]

这正是 FastAPI 教程后续章节「更大的应用 - 多文件结构」要解决的问题:APIRouter 同样接收 dependencies 参数,并在 include_router 时与路由自身的依赖合并。从 fastapi/routing.py 的源码可以看到依赖合并的传递路径:

  • Router 构造时保存自己的 dependencies
  • APIRouter.include_router(...) 会把父路由/上下文依赖与子路由依赖拼接(源码中存在 dependencies=[*parent_router.dependencies, *(dependencies or [])] 之类的展开合并逻辑);
  • 最终每个 APIRoute 通过 _build_dependant_with_parameterless_dependencies 把合并后的列表转化为无参子依赖。

也就是说,装饰器级 → 路由级 → 全局级的依赖机制共用同一套无参依赖解析管线,只是作用域逐级扩大。

全局依赖:作用于应用中的每一个 path operation

如果把装饰器级依赖的作用域再扩大一步,就是为整个 FastAPI 应用声明依赖——见 docs/es/docs/tutorial/dependencies/global-dependencies.md(英文原版为 docs/en/docs/tutorial/dependencies/global-dependencies.md)。此时依赖会被应用到应用中每一个 path operation

app = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])

「把 dependencies 加到 path operation 装饰器」一章中讲到的所有概念——请求要求、抛异常、返回值不被使用、依赖复用——在全局依赖中依然成立,只是生效范围覆盖到全部路径。

小结:三种"无参依赖"作用域速查

作用域 声明位置 生效范围 参考实现/文档
Path Operation @app.get(..., dependencies=[Depends(...)]) 单条路由 docs_src/dependencies/tutorial006_an_py310.py
路由组级 APIRouter(dependencies=[...])include_router(..., dependencies=[...]) 该路由组及其子路由 docs/es/docs/tutorial/bigger-applications.md
应用全局 FastAPI(dependencies=[...]) 应用中所有路由 docs_src/dependencies/tutorial012_an_py310.py

当某个校验或副作用逻辑需要在请求进入业务函数之前强制执行、而其结果又不被业务代码消费时,把 Depends(...) 放进 path operation 装饰器dependencies 列表就是最贴合语义的写法——它让路径函数签名保持纯净,同时把横切关注点交给 FastAPI 的依赖注入引擎统一处理。

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

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388