首页
/ FastAPI 依赖注入参考:Depends() 与 Security() 的参数、执行时机与源码实现剖析

FastAPI 依赖注入参考:Depends() 与 Security() 的参数、执行时机与源码实现剖析

2026-09-06 17:14:23作者:郁楠烈Hubert

本文基于 FastAPI 官方参考文档 docs/en/docs/reference/dependencies.md,完整讲解 Depends()Security() 两个核心函数的全部参数(dependencyuse_cachescopescopes)、用法示例与默认值,并沿着仓库源码梳理它们从参数解析、依赖树构建到请求时求解的完整调用链,帮助你在生产项目中正确使用依赖注入、控制缓存与执行范围,并理解 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)。

注意两点:

  1. 不要把依赖当函数调用Depends(common_parameters) 传入的是对象本身,不是 common_parameters();由 FastAPI 在请求处理时负责调用。
  2. Annotated 注解与“默认值”写法二选一,不能同时使用,源码中对此有明确断言(见下文参数解析)。

2.2 依赖自身的参数也是声明

依赖函数的签名同样遵循 FastAPI 参数体系:PathQueryHeaderCookieBodyForm 均可用于依赖参数,且依赖可以嵌套依赖(子依赖),从而把查询逻辑、数据库会话、用户校验等横切关注点从路径操作中剥离。

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

说明:scopeDepends() 的专属参数,Security() 的公开签名中没有 scope 参数(见 fastapi/param_functions.pySecurity 的定义,仅有 dependencyscopesuse_cache)。

一个值得注意的源码级约束:在 fastapi/dependencies/utils.pyget_dependant() 中,如果一个含 yield 的依赖自身计算作用域为 "request",而它又声明了一个 scope="function" 的子依赖,框架会抛出 DependencyScopeError,提示“作用域为 request 的依赖不能依赖作用域为 function 的依赖”。这是因为 "function" 作用域的收尾代码会在响应发送前执行,无法被外层 "request" 作用域正确包裹。

scope 在运行时如何生效?在 fastapi/dependencies/utils.pysolve_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.scopefastapi_inner_astackfastapi_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_scopesparent_oauth_scopesuse_cachescope 等字段,是理解上述机制的关键数据结构:

@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.pySecurity 的 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”。

最终,若依赖(或路径操作)的参数类型标注为 SecurityScopessolve_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.pyanalyze_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_cachescope 透传给子 Dependant
  • 若依赖参数类型是 RequestResponseBackgroundTasksSecurityScopes 等特殊类型,则记为对应的注入名(add_non_field_param_to_dependency,第 350-371 行),由框架直接注入。

6.3 请求时求解(solve_dependencies

请求到来时,solve_dependencies() 递归求解依赖树(第 586-731 行),关键行为:

  1. 依赖覆盖(override):若 dependency_overrides_provider.dependency_overrides 中存在原 callable 的替换(第 623-638 行),则用替换 callable 重新构建 Dependant 并求解——这是 app.dependency_overrides 测试机制的底层实现;
  2. 缓存命中:按 use_cache 与缓存键决定是否复用(见 第 4 节);
  3. 执行方式:生成器依赖走 _solve_generator 并进入对应作用域的 AsyncExitStack;协程依赖 await call(...);同步依赖通过 run_in_threadpool 在线程池中执行(第 673-676 行),保证不阻塞事件循环;
  4. 特殊注入Request/WebSocket/HTTPConnection/Response/BackgroundTasks/SecurityScopes 等按参数名注入(第 709-724 行);
  5. 结果通过 values[sub_dependant.name] = solved 传回父层,最终作为路径操作函数的入参。

6.4 Scope 与生成器依赖的时序

yield 的依赖通过 contextlib.contextmanager / asynccontextmanager 包装(fastapi/dependencies/utils.py),进入 AsyncExitStackscope 参数(或生成器默认的 "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_paramdataclasses.replace(depends, dependency=type_annotation) 的处理以及 get_parameterless_sub_dependant 对类调用的支持来看,依赖也可以是实现了 __call__ 的类实例(便于携带状态或依赖注入的配置对象),Security() 同理。

8. 小结

  • Depends() 是 FastAPI 依赖注入的核心声明函数,参数为 dependencyuse_cache=Truescope=NoneSecurity() 在其基础上增加 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.mddocs/en/docs/tutorial/dependencies/(依赖教程)、docs/en/docs/advanced/security/oauth2-scopes/(OAuth2 scopes 进阶)。
登录后查看全文
热门项目推荐
相关项目推荐