ARTICLE DETAIL

资讯详情

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

FastAPI 依赖注入进阶:使用 Python 类作为依赖(Classes as Dependencies)完整指南

FastAPI 依赖注入进阶:使用 Python 类作为依赖(Classes as Dependencies)完整指南 FastAPI 依赖注入进阶使用 Python 类作为依赖Classes as Dependencies完整指南【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi在 FastAPI 的依赖注入体系中依赖并不局限于函数——任何可调用callable对象都可以充当依赖其中最常见的形态就是把一个 Python 类直接作为依赖使用。本文将基于官方教程讲解如何用类替代函数型依赖、__init__参数如何被自动解析、Depends()无参快捷写法背后的原理并结合当前仓库的源码与测试深入剖析类即依赖的实现机制。从dict依赖说起为什么我们可以做得更好在引入类依赖之前教程中的依赖通常是一个返回dict的异步函数例如 docs_src/dependencies/tutorial001_an_py310.pyfrom 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 app.get(/users/) async def read_users(commons: Annotated[dict, Depends(common_parameters)]): return commons这样做的结果是路径操作函数的commons参数拿到的只是一个普通的dict。问题随之而来IDE 无法对dict提供自动补全与类型检查——编辑器不知道字典里有哪些键、每个键是什么类型。于是我们自然想到能不能让依赖返回一个有类型的对象什么才是一个依赖核心判定标准是 callable到目前为止我们见过的依赖都是函数但函数并不是唯一的依赖形式。判断一个对象能否成为 FastAPI 依赖的关键只有一条依赖必须是可调用callable的。在 Python 中可调用指任何可以像函数一样被调用执行的对象例如something()something(some_argument, some_keyword_argumentfoo)只要满足这种调用语法无论它是一个函数、一个类还是实现了__call__的实例都属于 callable。而创建类的实例恰恰使用的就是这套语法class Cat: def __init__(self, name: str): self.name name fluffy Cat(nameMr Fluffy)这里fluffy是Cat的实例而创建它的过程本质上是调用了Cat这个类。类因此天然是 callable也就天然具备成为 FastAPI 依赖的资格。FastAPI 实际校验的就是依赖是否为一个 callable以及它所声明的参数参见 fastapi/dependencies/utils.py 中get_parameterless_sub_dependant对callable(depends.dependency)的断言。只要传入的是 callable框架就会分析其参数并按照与路径操作函数参数完全相同的方式处理——包括类型转换、校验、OpenAPI 文档生成以及子依赖的解析。用类重写依赖CommonQueryParams现在我们把函数型依赖common_parameters改写成类CommonQueryParams完整示例见 docs_src/dependencies/tutorial002_an_py310.pyfrom typing import Annotated from fastapi import Depends, FastAPI app FastAPI() fake_items_db [{item_name: Foo}, {item_name: Bar}, {item_name: Baz}] class CommonQueryParams: def __init__(self, q: str | None None, skip: int 0, limit: int 100): self.q q self.skip skip self.limit limit app.get(/items/) async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]): response {} if commons.q: response.update({q: commons.q}) items fake_items_db[commons.skip : commons.skip commons.limit] response.update({items: items}) return response请注意__init__方法的签名——它与之前的common_parameters函数参数完全一致def __init__(self, q: str | None None, skip: int 0, limit: int 100):FastAPI 正是依靠__init__的参数来求解solve依赖它会把每个参数当作查询参数来处理self除外。在函数版与类版两种写法下最终效果完全相同查询参数类型默认值是否必填qstrNone可选skipint0可选limitint100可选无论是函数版还是类版这些数据都会被转换类型、校验、并写入 OpenAPI 架构。这一点在仓库测试中有直接证据tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py 中的test_openapi_schema断言了/openapi.json中q、skip、limit三个查询参数含title、default、type等字段与默认值0、100的完整生成结果。使用类依赖实例注入而非类本身声明好类依赖后在路径操作函数中这样使用app.get(/items/) async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):当请求到达时FastAPI 会调用CommonQueryParams这个类创建它的实例并把实例作为commons的值传入你的函数。所以函数体里访问的是commons.q、commons.skip、commons.limit这些实例属性而不是字典键。路由函数中基于实例属性实现的分页逻辑items fake_items_db[commons.skip : commons.skip commons.limit]依赖注入与校验、分页、响应组装协同工作最终返回如下的 JSON{items: [{item_name: Foo}, {item_name: Bar}, {item_name: Baz}]}带查询参数请求/items?qbarskip1limit1时则返回{items: [{item_name: Bar}], q: bar}——这些行为均被上述测试文件的test_get用例逐一验证。类型注解 vsDepends同一个类写了两次细心的读者会发现CommonQueryParams在参数声明中出现了两次commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]第二次出现Depends(CommonQueryParams)内部的那个才是 FastAPI 真正使用的依赖定义——框架从这里提取依赖参数并实际调用它。第一次出现类型注解位置的CommonQueryParams对 FastAPI 本身没有特殊意义它不参与数据转换与校验。也就是说你甚至可以写成这样见 docs_src/dependencies/tutorial003_an_py310.pyfrom typing import Annotated, Any # ... app.get(/items/) async def read_items(commons: Annotated[Any, Depends(CommonQueryParams)]): response {} if commons.q: response.update({q: commons.q}) items fake_items_db[commons.skip : commons.skip commons.limit] response.update({items: items}) return response虽然功能上完全等价但强烈建议保留类型注解它是你的编辑器判断commons具体类型、提供自动补全与类型检查的唯一依据。下图中可以看到当代码写作commons: CommonQueryParams Depends(CommonQueryParams)时IDE 能识别出commons.skip的类型为int并给出提示快捷写法Depends()无参调用上面的写法中CommonQueryParams被写了两次存在明显的代码重复。针对**依赖特指某个类、且 FastAPI 将调用该类创建实例**这种场景FastAPI 提供了快捷写法把类写在类型注解位置Depends()括号内留空完整示例见 docs_src/dependencies/tutorial004_an_py310.pyapp.get(/items/) async def read_items(commons: Annotated[CommonQueryParams, Depends()]): response {} if commons.q: response.update({q: commons.q}) items fake_items_db[commons.skip : commons.skip commons.limit] response.update({items: items}) return responseFastAPI 会从类型注解中读出CommonQueryParams然后自动完成依赖的解析与调用。这个能力从源码结构看源于 fastapi/params.py 中Depends的定义——它的dependency字段默认为Nonedataclass(frozenTrue) class Depends: dependency: Callable[..., Any] | None None use_cache: bool True scope: Literal[function, request] | None None当dependency为空时框架会回退到参数的类型注解来定位实际的依赖类即上文get_parameterless_sub_dependant所处理的无参依赖路径。use_cache与scope字段则分别控制依赖结果的缓存行为与作用域function或request。提示如果这种快捷写法让你觉得比之前更困惑完全可以不用——它只是一个减少代码重复的语法糖并不是必需的。两种写法与 Python 版本适配本文示例均基于 Python 3.10 的Annotated语法。若你的环境不便使用Annotated等价写法如下commons: CommonQueryParams Depends(CommonQueryParams)commons Depends(CommonQueryParams)commons: CommonQueryParams Depends()官方建议优先使用Annotated版本因为变量默认值位置用于表达依赖语义更加清晰且未来兼容性更好。总结依赖的本质是callable函数可以类可以任何可调用对象都可以。把类作为依赖时FastAPI 分析__init__的参数self除外并按路径操作函数参数的规则完成转换、校验与 OpenAPI 文档生成。依赖的解析结果是类的实例类型注解让 IDE 能提供完整的自动补全与类型检查。Depends()无参写法是类依赖的快捷语法糖能消除重复书写类名的冗余。后续教程将进一步展示如何把类依赖与子依赖、yield依赖、Security等机制组合使用构建更复杂的依赖注入图。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表