FastAPI 依赖注入参考:Depends() 与 Security() 的参数、执行时机与源码实现剖析
本文基于 FastAPI 官方参考文档 docs/en/docs/reference/dependencies.md,完整讲解 Depends() 与 Security() 两个核心函数的全部参数(dependency、use_cache、scope、scopes)、用法示例与默认值,并沿着仓库源码梳理它们从参数解析、依赖树构建到请求时求解的完整调用链,帮助你在生产项目中正确使用依赖注入、控制缓存与执行范围,并理解 OAuth2 scopes 如何写入 OpenAPI 与自动 API 文档。
1. 概览:依赖是如何声明的
FastAPI 的依赖注入体系主要由一个特殊函数 Depends() 承载,它接收一个可调用对象(通常是函数,也可以是类实例):
from fastapi import Depends
与 Path()、Query() 等参数装饰函数一样,Depends() 定义在 fastapi/param_functions.py 中(第 2283 行起),其作用是包装一个“可依赖”的 callable 并返回一个内部参数对象,供框架在解析端点签名时使用:
# fastapi/param_functions.py
def Depends(
dependency: Annotated[Callable[..., Any] | None, Doc(...)] = None,
*,
use_cache: Annotated[bool, Doc(...)] = True,
scope: Annotated[Literal["function", "request"] | None, Doc(...)] = None,
) -> Any:
return params.Depends(dependency=dependency, use_cache=use_cache, scope=scope)
返回的 params.Depends 是一个冻结 dataclass,定义在 fastapi/params.py(第 746-749 行):
# fastapi/params.py
@dataclass(frozen=True)
class Depends:
dependency: Callable[..., Any] | None = None
use_cache: bool = True
scope: Literal["function", "request"] | None = None
@dataclass(frozen=True)
class Security(Depends):
scopes: Sequence[str] | None = None
Security 直接继承 Depends,只多了一个 scopes 字段。这个类层次关系解释了二者在行为上的唯一差异——Security 额外把 OAuth2 scopes 传递给 OpenAPI。
在多数场景下,认证、授权都可以直接用 Depends() 的依赖函数实现。但当需要同时声明 OAuth2 scopes 并让这些 scopes 出现在 OpenAPI(以及 /docs 自动 UI)中时,应使用 Security() 替代 Depends():
from fastapi import Security
2. Depends() 参数参考
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dependency |
Callable[..., Any] | None |
None |
一个“可依赖”的 callable(如函数)。不要直接调用它,FastAPI 会替你调用,只需把对象直接传入 |
use_cache |
bool |
True |
请求内首次调用后,若该依赖在后续再次被声明(如多个子依赖共用同一依赖),结果会被复用到请求结束;设为 False 可禁用,确保同一请求内重复声明时再次执行 |
scope |
Literal["function", "request"] | None |
None |
主要用于含 yield 的依赖,控制 yield 前/后代码的开始与结束时机(见下文 执行范围) |
2.1 基本用法
依赖声明有两种等价写法:Annotated(推荐)或旧式默认值写法。
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def common_parameters(q: str | None = None, skip: int = 0, limit: int = 100):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: Annotated[dict, Depends(common_parameters)]):
return commons
上例即 Depends() 函数 docstring 中的官方示例(见 fastapi/param_functions.py)。
注意两点:
- 不要把依赖当函数调用。
Depends(common_parameters)传入的是对象本身,不是common_parameters();由 FastAPI 在请求处理时负责调用。 Annotated注解与“默认值”写法二选一,不能同时使用,源码中对此有明确断言(见下文参数解析)。
2.2 依赖自身的参数也是声明
依赖函数的签名同样遵循 FastAPI 参数体系:Path、Query、Header、Cookie、Body、Form 均可用于依赖参数,且依赖可以嵌套依赖(子依赖),从而把查询逻辑、数据库会话、用户校验等横切关注点从路径操作中剥离。
3. 依赖的 yield 与执行范围(scope)
对于含 yield 的依赖(例如打开数据库会话、计时、锁定资源),scope 参数决定“代码在 yield 后何时结束执行”:
"function":依赖在路径操作函数执行前开始(yield前代码),在路径操作函数结束后、但在响应发回客户端之前结束(yield后代码)。即依赖包裹的是路径操作函数。"request":依赖在路径操作函数前开始(与"function"相同),但结束于响应发回客户端之后。即依赖包裹的是整个请求与响应周期(含响应发送与后台任务收尾)。- 不设置(
None):从源码看(fastapi/dependencies/models.py 的_get_computed_scope),生成器依赖默认计算为"request",普通依赖为None(无生命周期语义)。
from typing import Annotated
from fastapi import Depends, FastAPI
app = FastAPI()
async def verify_safeword(
scope: Annotated[str | None, Depends(None, scope="request")],
):
# 此处省略实际校验逻辑
pass
说明:
scope是Depends()的专属参数,Security()的公开签名中没有scope参数(见 fastapi/param_functions.py 中Security的定义,仅有dependency、scopes、use_cache)。
一个值得注意的源码级约束:在 fastapi/dependencies/utils.py 的 get_dependant() 中,如果一个含 yield 的依赖自身计算作用域为 "request",而它又声明了一个 scope="function" 的子依赖,框架会抛出 DependencyScopeError,提示“作用域为 request 的依赖不能依赖作用域为 function 的依赖”。这是因为 "function" 作用域的收尾代码会在响应发送前执行,无法被外层 "request" 作用域正确包裹。
scope 在运行时如何生效?在 fastapi/dependencies/utils.py 的 solve_dependencies() 中:
use_astack = request_astack
if sub_dependant.scope == "function":
use_astack = function_astack
solved = await _solve_generator(
dependant=use_sub_dependant,
stack=use_astack,
sub_values=solved_result.values,
)
依赖生成器被包进 AsyncExitStack 上下文管理器(_solve_generator,第 566-574 行)。请求作用域与函数作用域使用两个不同的栈(分别存放在 request.scope 的 fastapi_inner_astack 与 fastapi_function_astack 中,见第 601-608 行),因此 scope="function" 的依赖其 yield 后代码会在函数栈关闭时(路径操作函数返回后、响应发出前)执行,而默认/"request" 作用域的依赖则存活到请求栈关闭(响应发回客户端之后)。
4. use_cache:请求内缓存与缓存键
默认 use_cache=True。在 solve_dependencies() 中的对应逻辑(fastapi/dependencies/utils.py):
sub_dependant_cache_key = _get_cache_key(
dependant=sub_dependant,
uses_scopes_cache=_uses_scopes_cache,
)
if sub_dependant.use_cache and sub_dependant_cache_key in dependency_cache:
solved = dependency_cache[sub_dependant_cache_key]
# ... 否则执行依赖,并把结果写入 dependency_cache
缓存键的构造在 fastapi/dependencies/models.py:
def _get_cache_key(
*,
dependant: Dependant,
uses_scopes_cache: _UsesScopesCache | None = None,
) -> DependencyCacheKey:
scopes_for_cache = (
tuple(sorted(set(_get_oauth_scopes(dependant=dependant))))
if _uses_scopes(dependant=dependant, cache=uses_scopes_cache)
else ()
)
return (
dependant.call,
scopes_for_cache,
_get_computed_scope(dependant=dependant) or "",
)
从源码结构看,缓存键是三元组:(依赖 callable, scopes 排序去重后的元组, 计算作用域)。这意味着:
- 同一请求内,同一依赖(同一 callable、同一 scopes、同一 scope)第二次出现时直接复用首次结果——这是“同一依赖在多个子依赖中重复声明只执行一次”的底层原因;
- 若两个地方对同一依赖声明了不同的 scopes(例如
Security(get_current_user, scopes=["items"])与Security(get_current_user, scopes=["users:write"])),缓存键不同,依赖会被分别执行,从而为每个 scope 集合得到独立的解析结果; - 将
use_cache=False时,sub_dependant.use_cache为假,跳过命中检查,保证每次声明都重新执行。
Dependant 模型(fastapi/dependencies/models.py)保存了 own_oauth_scopes、parent_oauth_scopes、use_cache、scope 等字段,是理解上述机制的关键数据结构:
@dataclass(slots=True)
class Dependant:
...
use_cache: bool = True
path: str | None = None
scope: Literal["function", "request"] | None = None
own_oauth_scopes: list[str] | None = None
parent_oauth_scopes: list[str] | None = None
5. Security() 参数参考
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
dependency |
Callable[..., Any] | None |
None |
同 Depends() 的 dependency:可依赖的 callable,由 FastAPI 调用 |
scopes |
Sequence[str] | None |
None |
使用该 Security 依赖的路径操作所需的 OAuth2 scopes。“scope”一词来自 OAuth2 规范,通常指权限(permission)或角色(role);这些 scopes 会集成进 OpenAPI,因而在 /docs 等自动文档中可见 |
use_cache |
bool |
True |
同 Depends() 的 use_cache |
Security() 与 Depends() 的唯一区别是:它可以声明 OAuth2 scopes,并将这些 scopes 写入 OpenAPI 与自动 UI 文档(见 fastapi/param_functions.py 中 Security 的 docstring 示例):
from typing import Annotated
from fastapi import FastAPI, Security
app = FastAPI()
@app.get("/users/me/items/")
async def read_own_items(
current_user: Annotated[User, Security(get_current_active_user, scopes=["items"])]
):
return [{"item_id": "Foo", "owner": current_user.username}]
5.1 scopes 在请求时的传递
scopes 不只是文档装饰。在 fastapi/dependencies/utils.py 中,get_parameterless_sub_dependant() 处理无参依赖时:
own_oauth_scopes: list[str] = []
if isinstance(depends, params.Security) and depends.scopes:
own_oauth_scopes.extend(depends.scopes)
return get_dependant(
path=path,
call=depends.dependency,
scope=depends.scope,
own_oauth_scopes=own_oauth_scopes,
)
而在 get_dependant() 中,每个带 Security 注解的参数都会把 scopes 记录到子 Dependant,并把当前层累积的 scopes 作为 parent_oauth_scopes 传给更深层(第 302-331 行)。合并规则见 fastapi/dependencies/models.py 的 _get_oauth_scopes()——保留顺序、去重地拼接“父层 scopes + 自身 scopes”。
最终,若依赖(或路径操作)的参数类型标注为 SecurityScopes,solve_dependencies() 会把合并后的 scopes 注入(fastapi/dependencies/utils.py):
if dependant.security_scopes_param_name:
values[dependant.security_scopes_param_name] = SecurityScopes(
scopes=_get_oauth_scopes(dependant=dependant)
)
于是你可以在依赖内这样读取当前累积的 scopes:
from fastapi.security import SecurityScopes
async def get_current_user(security_scopes: SecurityScopes):
# security_scopes.scopes: list[str],如 ["items"]
...
5.2 scopes 如何进入 OpenAPI
OpenAPI 生成时,fastapi/openapi/utils.py 会通过依赖树的 own_oauth_scopes / parent_oauth_scopes 汇总出每个操作的 security 定义(_get_openapi_security_definitions,第 132 行起),使 Security(..., scopes=[...]) 声明的 scopes 出现在生成的 OpenAPI JSON 中,/docs(Swagger UI)等界面因此能展示每个操作要求的 scope 列表,并在“Authorize”按钮的 scope 勾选中体现。
6. 源码剖析:一条 Depends() 声明的完整生命周期
结合仓库源码,Depends() / Security() 的处理可以分成四步。
6.1 参数解析(analyze_param)
fastapi/dependencies/utils.py 的 analyze_param() 负责从端点/依赖函数签名中识别 Depends:
fastapi_annotations = [
arg
for arg in annotated_args[1:]
if isinstance(arg, (FieldInfo, params.Depends))
]
...
# Get Annotated Depends
elif isinstance(fastapi_annotation, params.Depends):
depends = fastapi_annotation
# Get Depends from default value
if isinstance(value, params.Depends):
assert depends is None, (
"Cannot specify `Depends` in `Annotated` and default value"
f" together for {param_name!r}"
)
...
depends = value
两条要点:
Depends支持两种位置:Annotated注解内(推荐)或参数默认值(旧式);- 两者不可同时出现,源码会直接断言失败;
- 若
Depends()未显式传入 callable(depends.dependency is None),源码会用类型注解作为依赖对象(第 467-471 行):depends = dataclasses.replace(depends, dependency=type_annotation)。这支持Annotated[User, Depends(use_cache=False)]这类写法。
6.2 构建依赖树(get_dependant)
get_dependant()(fastapi/dependencies/utils.py)递归地把每个带 Depends 的参数转换为子 Dependant,形成依赖树:
- 提取
Annotated中的Depends/Security,若为Security则把scopes记入own_oauth_scopes; - 把
use_cache、scope透传给子Dependant; - 若依赖参数类型是
Request、Response、BackgroundTasks、SecurityScopes等特殊类型,则记为对应的注入名(add_non_field_param_to_dependency,第 350-371 行),由框架直接注入。
6.3 请求时求解(solve_dependencies)
请求到来时,solve_dependencies() 递归求解依赖树(第 586-731 行),关键行为:
- 依赖覆盖(override):若
dependency_overrides_provider.dependency_overrides中存在原 callable 的替换(第 623-638 行),则用替换 callable 重新构建Dependant并求解——这是app.dependency_overrides测试机制的底层实现; - 缓存命中:按
use_cache与缓存键决定是否复用(见 第 4 节); - 执行方式:生成器依赖走
_solve_generator并进入对应作用域的AsyncExitStack;协程依赖await call(...);同步依赖通过run_in_threadpool在线程池中执行(第 673-676 行),保证不阻塞事件循环; - 特殊注入:
Request/WebSocket/HTTPConnection/Response/BackgroundTasks/SecurityScopes等按参数名注入(第 709-724 行); - 结果通过
values[sub_dependant.name] = solved传回父层,最终作为路径操作函数的入参。
6.4 Scope 与生成器依赖的时序
含 yield 的依赖通过 contextlib.contextmanager / asynccontextmanager 包装(fastapi/dependencies/utils.py),进入 AsyncExitStack。scope 参数(或生成器默认的 "request")决定它进入哪个栈:
"function"→function_astack:在路径操作函数返回后、响应发送前关闭,yield后代码先执行;"request"(生成器依赖默认)→request_astack:在整个请求-响应周期结束后关闭,yield后代码晚执行。
这与 scope 参数的官方文档描述完全一致:"function" 包裹“路径操作函数”,"request" 包裹“请求与响应周期”。
7. 常见错误与行为要点
基于源码中的断言与校验逻辑,整理以下实践要点:
| 场景 | 行为 | 源码依据 |
|---|---|---|
Annotated 与默认值中同时写 Depends |
触发断言错误,二者只能取其一 | analyze_param |
request 作用域生成器依赖声明了 function 作用域子依赖 |
抛出 DependencyScopeError |
get_dependant |
同一依赖在不同 scopes 下被多次声明 |
缓存键不同,依赖分别执行 | _get_cache_key |
use_cache=False |
跳过缓存命中检查,每次声明都执行 | solve_dependencies |
| 同步(非 async)依赖 | 在线程池中执行,不阻塞事件循环 | solve_dependencies |
此外,Depends() 的 callable 不限于函数:从 analyze_param 对 dataclasses.replace(depends, dependency=type_annotation) 的处理以及 get_parameterless_sub_dependant 对类调用的支持来看,依赖也可以是实现了 __call__ 的类实例(便于携带状态或依赖注入的配置对象),Security() 同理。
8. 小结
Depends()是 FastAPI 依赖注入的核心声明函数,参数为dependency、use_cache=True、scope=None;Security()在其基础上增加scopes参数,用于把 OAuth2 scopes 写入 OpenAPI 与自动文档。- 从源码看,
params.Depends/params.Security是轻量冻结 dataclass(fastapi/params.py),真正的工作发生在依赖树构建(get_dependant)与请求求解(solve_dependencies)两个阶段。 use_cache基于(call, scopes, scope)三元组缓存键实现请求内去重执行;scope基于两个AsyncExitStack实现yield依赖的差异化生命周期;scopes通过own_oauth_scopes/parent_oauth_scopes自底向上合并,最终注入SecurityScopes并进入 OpenAPI。- 相关参考文档:docs/en/docs/reference/dependencies.md、docs/en/docs/tutorial/dependencies/(依赖教程)、docs/en/docs/advanced/security/oauth2-scopes/(OAuth2 scopes 进阶)。
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