FastAPI 子依赖(Sub-dependencies)实战:依赖嵌套、自动解析与依赖缓存机制
FastAPI 子依赖Sub-dependencies实战依赖嵌套、自动解析与依赖缓存机制【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapiFastAPI 的依赖注入系统允许依赖函数嵌套出任意深度的“依赖树”。本篇基于官方文档 Sub-dependencies 的核心内容结合仓库中的示例代码 tutorial005_an_py310.py 与底层实现 dependencies/utils.py讲解如何声明相互依赖的依赖函数、FastAPI 如何自动解析依赖链以及同一依赖在同一请求中只执行一次的缓存机制与use_cacheFalse的关闭方式。什么是“Dependable”与“Dependant”在 FastAPI 的术语中Dependable被依赖者其他代码通过Depends声明所依赖的函数例如某条路径操作函数依赖它。Dependant依赖者自身又依赖别的依赖的函数即它自己也声明了Depends。一个函数完全可以同时既是 Dependable 又是 Dependant——这正是“子依赖”Unterabhängigkeiten的核心依赖可以像树更准确说是图一样任意深地嵌套FastAPI会自动负责解析整条依赖链你只需要声明最顶层的那一个依赖。第一个依赖Dependable先创建一个简单的底层依赖声明一个可选的 Query 参数qstr类型默认None然后直接把它返回from typing import Annotated from fastapi import Cookie, Depends, FastAPI app FastAPI() def query_extractor(q: str | None None): return q对应 tutorial005_an_py310.py 第 8–9 行。这个函数非常简单本身几乎没什么用途但它是后续示例的基础它把“从 Query 里提取q”这一件事封装成了一个可被依赖、可被复用的单元。如果不喜欢Annotated风格等价的非标注写法是def query_extractor(q: str | None None): return q两者在这里没有区别因为q只是普通 Query 参数差异主要体现在下一层的依赖声明上。第二个依赖既是 Dependable也是 Dependant接下来创建一个“上层”依赖函数它自身声明了子依赖def 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对应 tutorial005_an_py310.py 第 12–18 行。逐一分析它声明的参数虽然这个函数本身是一个依赖Dependable路径操作函数依赖它但它又声明了另一个依赖Dependant它依赖query_extractor它通过q: Annotated[str, Depends(query_extractor)]依赖query_extractor并把后者返回的值赋给自己的参数q它还声明了一个可选的last_queryCookie类型为strCookie()默认值为None。业务意图是如果用户本次没有传 Query 参数q就回退使用上一次请求时保存在 Cookie 里的查询值。非Annotated的等价写法见 tutorial005_py310.pydef query_or_cookie_extractor( q: str Depends(query_extractor), last_query: str | None Cookie(defaultNone) ): if not q: return last_query return q官方建议在可以使用Annotated时优先使用Annotated版本。在路径操作函数中使用该依赖现在把这条“依赖链”接入一个真实的端点app.get(/items/) async def read_query( query_or_default: Annotated[str, Depends(query_or_cookie_extractor)], ): return {q_or_cookie: query_or_default}对应 tutorial005_an_py310.py 第 21–25 行。注意这里的关键点在路径操作函数中我们只声明了一个依赖——query_or_cookie_extractor。但FastAPI会知道在调用query_or_cookie_extractor之前必须先解析并调用它的子依赖query_extractor再把它返回的结果作为参数传进去。整个解析过程对开发者是透明的。用一张图表示这条依赖链测试用例验证实际行为仓库中的测试 test_tutorial005.py 对上述示例做了行为验证参数化覆盖了三种场景请求Cookielast_query期望响应GET /itemsfrom_cookie{q_or_cookie: from_cookie}回退到 CookieGET /items?qfoofrom_cookie{q_or_cookie: foo}Query 优先GET /items无{q_or_cookie: null}两者都没有时为None同一测试还断言了 OpenAPI Schema 的形态路径操作/items/的parameters中会同时出现 Query 参数q与 Cookie 参数last_query。这印证了一个重要事实——依赖声明的参数会向上“摊平”到路径操作的 OpenAPI 参数列表中客户端看到的仍是端点自己的参数而不是“依赖的参数”。这一摊平逻辑由 get_flat_params() 实现它遍历Dependant树的每一层把各层的path_params、query_params、header_params、cookie_params收集合并并对重复出现的依赖用缓存键去重。同一依赖被多次使用时依赖缓存Dependency Cache当一个依赖对同一条路径操作被声明多次时例如多个依赖共享同一个子依赖FastAPI 只会在每个请求中调用该子依赖一次。具体来说FastAPI 会把依赖函数的返回值存入一个每请求级别的“Cache”dependency_cache字典之后所有在同一个请求内需要这个依赖的“Dependant”都会直接拿到缓存值而不会再次执行该依赖函数。从源码可以确认这一机制的完整实现位于 solve_dependencies()每个Dependant通过_get_cache_key()见 dependencies/models.py生成缓存键键由依赖函数本身、相关 OAuth scopes、以及计算出的 scope 组成递归解析子依赖后先检查if sub_dependant.use_cache and sub_dependant_cache_key in dependency_cache:命中则直接取缓存值跳过调用未命中才真正调用依赖协程直接await、同步函数放入线程池执行并把结果写回dependency_cache。该字典以dependency_cache参数在solve_dependencies()的递归调用间逐层传递保证整条依赖树共享同一份缓存且缓存的生命周期与单次请求一致——请求结束后即失效。关闭缓存use_cacheFalse在某些进阶场景下你明确知道某个依赖需要在同一个请求内每一步可能多次都重新执行而不是复用缓存值。这时可以在声明Depends时传入use_cacheFalseasync 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}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即默认启用缓存Dependant数据类dependencies/models.py中同样保存了use_cache: bool True字段get_dependant()在递归构建依赖树时会把Depends上的use_cache原样传递到每个子Dependant。缓存与禁用缓存的可验证证据在测试 test_dependency_cache.py 中它用一个带自增计数器的依赖dep_counter来观察调用次数——/sub-counter/端点同时声明了super_dep内部依赖dep_counter和直接依赖dep_counter单次请求返回{counter: 1, subcounter: 1}证明dep_counter只执行了一次两处拿到的是同一个缓存值/sub-counter-no-cache/同一结构但对直接依赖声明Depends(dep_counter, use_cacheFalse)单次请求返回{counter: 2, subcounter: 1}——counter拿到 2说明它又执行了一次subcounter仍来自缓存与use_cacheFalse的语义完全吻合。总结简单但强大的依赖图抛开 “Dependable”“Dependant” 这些术语FastAPI 的依赖注入系统本质上很简单依赖就是看起来和路径操作函数一模一样的普通函数——同样的类型标注、同样的 Query/Cookie/Header/Body 参数声明、同样的校验与 OpenAPI 生成。同时它又非常强大你可以声明任意深度嵌套的依赖“图”树FastAPI 会自动完成递归解析get_dependant() 在应用启动时递归构建Dependant树、请求期的依赖求解与结果注入solve_dependencies() 递归求解并共享缓存、以及同请求内的去重缓存。官方文档最后给出一条实践提示单看这个“Query 回退到 Cookie”的例子这种能力似乎用不太上但在后续的**安全Security**章节中你会看到同一套机制如何被Security/OAuth2复用——例如统一的get_current_user依赖被数十个端点共享时每个请求内用户认证逻辑只执行一次——这既是依赖缓存发挥价值的典型场景也能省下大量重复代码。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考