ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

FastAPI 依赖注入进阶:子依赖(Sub-dependencies)图解析与 use_cache 缓存机制实战

FastAPI 依赖注入进阶:子依赖(Sub-dependencies)图解析与 use_cache 缓存机制实战 FastAPI 依赖注入进阶子依赖Sub-dependencies图解析与 use_cache 缓存机制实战【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiFastAPI 的依赖注入系统允许依赖函数再次声明自己的依赖从而形成任意深度的依赖图。本文以依赖注入教程中的子依赖章节为核心结合仓库中的源码实现完整讲解如何构建query_extractor → query_or_cookie_extractor → 路径操作这样一条多级依赖调用链并深入剖析同一次请求中重复依赖的缓存语义与use_cacheFalse的真实行为帮助你写出层次清晰、可复用的真实业务代码。什么是子依赖Sub-dependencies在 FastAPI 中依赖不一定是一层到底的。你可以创建本身还声明了其他依赖的依赖函数这就是子依赖sub-dependencies。只要嵌套得当这种结构可以做得多深都行而FastAPI 会自动负责解析整棵依赖树你不必手动关心调用顺序。用官方术语来说dependable作为被依赖方存在的依赖函数dependant声明了自身依赖的调用方。而一个函数可以同时扮演两个角色——它既可以是别人的 dependable也可以是某个依赖的 dependant。这正是子依赖体系的精髓所在。第一步定义一个最基础的 dependable先从最简单的依赖函数开始。它只声明了一个可选的查询参数q然后原样把它返回源码见 docs_src/dependencies/tutorial005_an_py310.pydef query_extractor(q: str | None None): return q这个函数本身非常简单甚至没什么用但它是理解子依赖如何工作的理想起点依赖函数和路径操作函数长得完全一样——同样可以声明路径参数、查询参数、请求体、Cookie 等FastAPI 会一视同仁地做校验与注入。第二步一个同时是 dependable 和 dependant 的依赖接着创建第二个依赖函数。它既是一个独立的依赖dependable又在内部声明了对query_extractor的依赖因此它同时也是一个 dependant见 tutorial005_an_py310.pydef query_or_cookie_extractor( q: Annotated[str, Depends(query_extractor)], last_query: Annotated[str | None, Cookie()] None, ): if not q: return last_query return q拆解它的两个参数声明q: Annotated[str, Depends(query_extractor)]该函数依赖query_extractor并把它的返回值绑定到参数q。FastAPI 会保证在调用query_or_cookie_extractor之前先调用query_extractor并把结果传入。last_query: Annotated[str | None, Cookie()] None声明一个可选的字符串 Cookie。业务逻辑是当用户没有在请求里提供q查询参数时就回退使用之前保存在 Cookie 里的上一次查询。这就是一个完整的兜底查询场景——查询参数优先Cookie 次之。如果你不使用Annotated语法Python 3.10 但偏好默认值写法等价版本见 tutorial005_py310.py效果完全相同def query_or_cookie_extractor( q: str Depends(query_extractor), last_query: str | None Cookie(defaultNone) ): if not q: return last_query return q提示官方文档建议优先使用Annotated版本写法更统一、可读性更好。第三步在路径操作中只声明最外层的依赖路径操作函数里只需声明一个依赖也就是最外层的query_or_cookie_extractorapp.get(/items/) async def read_query( query_or_default: Annotated[str, Depends(query_or_cookie_extractor)], ): return {q_or_cookie: query_or_default}注意一个关键点你完全没有手动声明query_extractor。但 FastAPI 会从query_or_cookie_extractor的签名中读懂它还需要query_extractor于是自动先行求解query_extractor再把结果喂给query_or_cookie_extractor。整条调用链可以用依赖图表示从源码看 FastAPI 如何构建依赖树这套自动求解在底层发生在 fastapi/dependencies/utils.py 的get_dependant()函数中。当 FastAPI 为一个路径操作构建Dependant对象时会遍历目标函数的签名参数一旦发现某个参数被Depends包裹param_details.depends is not None就会递归地对该依赖函数再调用一次get_dependant()生成一个子Dependant并挂到父节点的dependrant.dependencies列表中utils.pysub_dependant get_dependant( pathpath, callparam_details.depends.dependency, nameparam_name, ... use_cacheparam_details.depends.use_cache, ) dependant.dependencies.append(sub_dependant)可以看到每个Depends上的use_cache标志会随递归一路透传下去最终在真正求解请求时决定该子依赖的返回值是否走缓存。这也就是为什么你可以声明任意深度的嵌套依赖——递归构建保证了整棵树在请求到来之前就已经被完整地铺开。同一依赖被多次声明默认只调用一次缓存机制真实项目里很容易出现这样的场景多个依赖都共享同一个子依赖。例如A和B都依赖common而路径操作又同时依赖A和B。如果在每个地方都显式声明一遍common直觉上它似乎会被调用两次。但FastAPI 知道在同一路径操作中对每个 request 只会调用该子依赖一次首次求解时返回的值会被保存在一个请求级别的缓存cache中之后所有需要该值的 dependants 都会直接复用这个缓存值而不会再执行一次依赖函数。官方文档对 cache 的定义是一种用来存储已计算/已生成值的工具系统以便复用而不是重复计算。这一点在源码中也有印证——fastapi/dependencies/utils.py 的求解逻辑里只有当sub_dependant.use_cache为真且缓存键命中时才会复用已算出的结果。这一默认行为不仅省掉了重复的 I/O比如重复查库、重复请求外部服务还保证了同一请求内依赖实例的状态一致性。高级场景use_cacheFalse 强制每次重新求值默认缓存语义对绝大多数情况都是最优的但存在少数高级场景你明确希望该依赖在同一请求的每一步、每出现一次都被真实调用而不是复用缓存值。此时可以把Depends的use_cache参数设为False。Annotated写法Python 3.10async def needy_dependency(fresh_value: Annotated[str, Depends(get_value, use_cacheFalse)]): return {fresh_value: fresh_value}非Annotated写法async def needy_dependency(fresh_value: str Depends(get_value, use_cacheFalse)): return {fresh_value: fresh_value}提示能使用Annotated时优先使用Annotated版本。从源码看use_cache是Depends这个不可变数据类的字段之一见 fastapi/params.pydataclass(frozenTrue) class Depends: dependency: Callable[..., Any] | None None use_cache: bool True scope: Literal[function, request] | None None默认值为True。此外Security是继承自Depends的子类fastapi/params.py因此同样的use_cache语义也适用于安全相关的依赖声明而通过fastapi.param_functions中的Depends()/Security()工厂函数fastapi/param_functions.py、fastapi/param_functions.py该参数会原样透传到params.Depends上。典型适用场景包括某个依赖会基于当前时刻或随机数产生结果你希望在同一个请求中每次依赖被解析时都拿到新鲜值而测试中也可能用它来精确断言依赖被调用的次数。小结依赖注入本质上是函数图抛开各种花哨的术语FastAPI 的依赖注入系统其实非常简单依赖就是普通函数写法和路径操作函数一模一样它们之间可以通过Depends自由嵌套组成任意深度、任意形状的依赖树/图FastAPI 负责递归求解整张图并对同一请求内重复出现的子依赖做默认缓存同时开放use_cacheFalse作为逃生舱门。虽然上面的例子看起来简单甚至有些小题大做但当进入安全Security相关章节时你会发现OAuth2、JWT 校验、权限范围scopes、数据库会话这类逻辑都适合被抽象成子依赖复用的收益会非常可观——大量重复代码会被一张张清晰的依赖图取代。延伸阅读如果你想继续深入这套依赖体系仓库中的同系列文档可供对照学习依赖注入入门docs/en/docs/tutorial/dependencies/index.md用类作为依赖docs/en/docs/tutorial/dependencies/classes-as-dependencies.md路径操作装饰器中的依赖docs/en/docs/tutorial/dependencies/dependencies-in-path-operation-decorators.md全局依赖docs/en/docs/tutorial/dependencies/global-dependencies.mdyield 型依赖与清理逻辑docs/en/docs/tutorial/dependencies/dependencies-with-yield.md依赖注入的完整源码实现递归构建依赖树、缓存求解fastapi/dependencies/utils.py【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表