资讯详情

资讯详情

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

FastAPI 在 Path Operation 装饰器中声明依赖dependencies参数实战指南【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi导读在 FastAPI 的依赖注入体系中大部分场景通过Depends()在path operation 函数的参数上注入依赖并把返回值交给业务逻辑使用。但有一种常见诉求是某个依赖只需要被执行/被解析而它的返回值并不需要进入你的业务函数——例如仅用于校验请求头、执行鉴权、记录日志的副作用型依赖。本篇文章将聚焦 FastAPI 教程中 dependencies-in-path-operation-decorators 章节 讲解的解决方案把dependencies作为一个list传给path operation 装饰器。读完后你将掌握它的正确写法、错误处理与返回值语义、底层执行原理以及它与 Router 级、全局级依赖的延伸关系。为什么需要装饰器级依赖而非函数参数在某些情况下你并不真的需要某个依赖在path operation function里的返回值依赖本身不返回任何值例如一个只负责验证请求头合法性的校验器依赖会返回一个值但该值在你的路径函数中根本没有用武之地比如其它地方已能取得同样的信息。如果在这种场景下仍然把依赖声明为path operation function的普通参数就会产生副作用一些编辑器/IDE 会检查未使用的函数参数并将其高亮为错误或警告团队中的新开发者看到代码里有一个从未被使用的参数可能误以为它是多余的、可以删除——而一旦删除依赖也就不会执行了很可能破坏掉隐含的校验逻辑。为了解决这些场景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.pyfrom 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_code400, detailX-Token header invalid) async def verify_key(x_key: Annotated[str, Header()]): if x_key ! fake-super-secret-key: raise HTTPException(status_code400, detailX-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-Key与X-Token来演示思路。但请注意在生产项目中做真实的鉴权/安全控制应优先使用下一章将要讲解的内置安全工具FastAPI 内置安全工具例如OAuth2、HTTPBearer、APIKey等。这里的自定义头示例仅用于说明装饰器级依赖这个通用机制。 :::依赖中声明的请求要求依赖的依赖放在装饰器dependencies列表里的依赖函数与放在函数参数里的依赖在能力上没有任何差别——它们可以正常声明对请求的要求例如 header 参数也可以继续声明自己的子依赖async def verify_token(x_token: Annotated[str, Header()]): # 声明了一个必填请求头 x_token if x_token ! fake-super-secret-token: raise HTTPException(status_code400, detailX-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-token、x-key两个字段缺失的错误详情而test_openapi_schema则断言/openapi.json中/items/路径下的 GET 操作确实把x-token、x-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_code400, detailX-Token header invalid)对应测试test_get_invalid_one_header与test_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_code400, detailX-Key header invalid) return x_key # 返回值存在但在装饰器级依赖场景下不会被使用对比同一目录下教程中的其它示例即可理解这种复用价值例如 docs_src/dependencies/tutorial012_an_py310.py全局依赖示例复用了完全相同的依赖函数写法。依赖本身是普通函数它是否被消费返回值由使用方式决定而不是写死在函数里。底层原理FastAPI 是如何无参执行这些依赖的从源码层面可以更清楚地看到装饰器级依赖的运行机制。在 fastapi/routing.py 中APIRoute在构造阶段会调用_build_dependant_with_parameterless_dependenciesdef _build_dependant_with_parameterless_dependencies( *, path: str, call: Callable[..., Any], dependencies: Sequence[params.Depends], ) - tuple[Dependant, list[ModelField], bool]: dependant get_dependant(pathpath, callcall, scopefunction) for depends in dependencies[::-1]: dependant.dependencies.insert( 0, get_parameterless_sub_dependant(dependsdepends, pathpath), ) ...也就是说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( pathpath, calldepends.dependency, scopedepends.scope, own_oauth_scopesown_oauth_scopes, )要点解读depends.dependency必须可调用否则会触发断言错误A parameter-less dependency must have a callable dependency——这意味着不能把无法直接调用的对象塞进装饰器dependencies它同样支持Security及OAuth2scopes 等高级场景代码中单独处理了depends.scopes由于这些依赖会被并入同一套solve_dependencies解析管线它们天然支持子依赖、缓存同一请求内对同一依赖只解析一次、yield依赖的清理逻辑等全部 FastAPI 依赖注入特性。验证该行为的一手测试用例仓库为本章提供了完备的自动化测试见 tests/test_tutorial/test_dependencies/test_tutorial006.py。该测试通过pytestfixture 对tutorial006_py310与tutorial006_an_py310两个示例分别创建TestClient并断言了四条核心行为场景请求构造期望结果不带任何请求头client.get(/items/)422缺失x-token与x-key两个字段只带错误的X-Token仅X-Token: invalid400{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.py 中TestClient的使用约定结合uvicorn运行示例文件后按上表构造请求即可复现。为一组path operation声明依赖路由/子应用层面装饰器级dependencies的作用域是单条path operation。当你面对更大的应用、需要把路由拆分到多个文件时就会希望对一组路径统一声明依赖而不是在每一条路径上重复粘贴同一个dependencies[...]。这正是 FastAPI 教程后续章节「更大的应用 - 多文件结构」要解决的问题APIRouter同样接收dependencies参数并在include_router时与路由自身的依赖合并。从 fastapi/routing.py 的源码可以看到依赖合并的传递路径Router构造时保存自己的dependenciesAPIRouter.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 operationapp 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 的依赖注入引擎统一处理。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →