尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

FastAPI 教程:将 Python 类用作依赖(Class as Dependencies)——从 dict 到类型安全的依赖注入实践

FastAPI 教程:将 Python 类用作依赖(Class as Dependencies)——从 dict 到类型安全的依赖注入实践 FastAPI 教程将 Python 类用作依赖Class as Dependencies——从 dict 到类型安全的依赖注入实践【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi依赖不一定非是函数不可只要是一个 Python 可调用对象callableFastAPI 就会像解析路径操作函数的参数一样去解析它。本指南以本仓库官方教程文档 classes-as-dependencies.md 为主线演示如何把前文依赖示例中的dict返回改为一个携带__init__参数的类从而让编辑器获得完整的代码补全与类型检查能力并讲解Depends()无参简写背后的解析规则。读完你将掌握类即依赖的声明方式、类型注解与Depends各自承担的职责以及 FastAPI 底层对 callable 的统一处理机制。一个dict能做什么从上一个示例说起在依赖注入教程的上一部分对应 tutorial001_an_py310.py我们的依赖函数dependable返回的是一个dictfrom 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于是read_items/read_users路径操作函数参数commons中拿到的就是一个dict。问题随之而来编辑器无法给dict提供太多支持比如补全因为它根本不知道这个字典里有哪些键、每个键的值是什么类型。commons[q]这种写法既没有补全提示也无法做静态类型检查。我们可以做得更好——用一个真正的 Python 类来替代。什么构成一个依赖关键在可调用对象到目前为止教程中出现的依赖都是用函数声明的async def common_parameters(...): ...但函数不是声明依赖的唯一方式虽然它可能是最常见的一种。决定性的因素只有一个依赖应当是一个可调用对象callable。在 Python 里callable指任何可以像函数一样被调用的东西。只要存在一个对象something它可能不是函数而你能够这样执行它something()或者something(some_argument, some_keyword_argumentfoo)那么something就是一个可调用对象。类也是可调用对象细想一下创建一个 Python 类的实例用的正是同一套调用语法class Cat: def __init__(self, name: str): self.name name fluffy Cat(nameMr Fluffy)这里fluffy是Cat类的一个实例而要制造出fluffy你正是在调用Cat。也就是说Python 类天然就是一个可调用对象。因此在 FastAPI 中可以直接把 Python 类当作依赖来用。FastAPI 实际校验的只有两件事传进来的东西是否是一个callable函数、类或任何其它可调用对象它声明了哪些参数。如果你把一个 callable 作为依赖传给 FastAPIFastAPI 会分析该 callable 的参数并按与路径操作函数参数完全相同的方式去处理它们——包括嵌套子依赖。这个规则同样适用于完全没有参数的 callable正如无参数的路径操作函数一样。从源码看FastAPI 在 fastapi/dependencies/utils.py 中的get_dependant()第 271 行起与analyze_param()第 381 行起完成这套解析它们对 dependency 的call统一分析签名、提取子依赖并逐参数处理而并不关心这个 callable 到底是def函数、class还是别的可调用形式。get_dependant中还会通过_is_async_gen_callable、_is_gen_callable等工具判定依赖属于普通协程、同步函数还是生成器变体见 fastapi/dependencies/utils.py 对parameter-less dependency must have a callable dependency的断言。用类声明依赖__init__即依赖的参数现在把上面的依赖common_parameters改写成类CommonQueryParams。完整示例见 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__方法def __init__(self, q: str | None None, skip: int 0, limit: int 100):它的参数与之前的函数common_parameters完全一致。这些参数正是 FastAPI 用来求解依赖的输入也就是说FastAPI 会从__init__的签名中提取查询参数的声明。两种写法最终得到的是完全相同的三件事一个可选的q查询参数类型为str一个skip查询参数类型为int默认值0一个limit查询参数类型为int默认值100。两种写法下这些数据都会被同样地转换类型、校验、写入 OpenAPI schema 文档等等。唯一区别在于函数版本把它们塞进一个dict类版本则把它们绑定为实例属性进而在路径操作函数里可以直接通过commons.q、commons.skip访问。使用这个类依赖现在即可用这个类声明依赖app.get(/items/) async def read_items(commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]):FastAPI 会调用CommonQueryParams类、创建一个该类的实例并把实例作为参数commons传入你的函数。__init__中写明的每个参数都会从请求中解析——q、skip、limit从查询字符串而来并在实例化前完成类型转换与校验。这样路由处理函数拿到的是一个具有真实类型CommonQueryParams的对象编辑器能基于属性类型提供commons.q、commons.skip、commons.limit的补全拼错属性名也会被静态检查捕获。类型注解与Depends各司其职请观察上面代码里CommonQueryParams出现了两次commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]Python 3.10 非Annotated写法为commons: CommonQueryParams Depends(CommonQueryParams)教程的 tip 建议尽可能使用Annotated版本。第二个CommonQueryParams即Depends(CommonQueryParams)里的那个才是 FastAPI 真正用来识别依赖的它会从这一个里提取声明的参数最终也是调用它。第一个CommonQueryParams类型注解位置的那个对 FastAPI没有任何特殊含义。FastAPI 不会用它做数据转换、校验等工作那由Depends(CommonQueryParams)负责。因此你甚至可以只写类型为Any的版本见 tutorial003_an_py310.pyfrom typing import Annotated, Any 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[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非Annotated版本对应commons Depends(CommonQueryParams)。但仍然强烈建议声明类型——这样做你的编辑器才能知道传入参数commons的是什么进而给你提供代码补全与类型检查快捷方式Depends()免重复声明上面的写法仍然有重复代码CommonQueryParams要手写两遍。FastAPI 为这类依赖特指某个将被调用并创建实例的类的情况提供了一种快捷方式。与其写commons: Annotated[CommonQueryParams, Depends(CommonQueryParams)]不如写commons: Annotated[CommonQueryParams, Depends()]即把依赖声明为参数类型本身并用不带任何参数的Depends()FastAPI 会从类型注解推断出该实例化哪个类。非Annotated版本对应commons: CommonQueryParams Depends()。完整示例见 tutorial004_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()]): 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如果这个技巧让你觉得反而更绕那完全可以忽略它——它不是必须的只是一种用来减少代码重复的简写。需要注意它的适用边界只有当依赖是FastAPI 将会调用它来创建该类实例本身的具体类时Depends()无参写法才成立。若依赖是函数、或者需要在Depends(...)里传入其它对象/参数仍然需要显式写出依赖体。运行验证三种写法行为完全一致本仓库的官方测试覆盖了这三种写法的等价性。在 tests/test_tutorial/test_dependencies/test_tutorial002_tutorial003_tutorial004.py 中tutorial002、tutorial003与tutorial004含_py310与_an_py310变体被统一收集为一个参数化的clientfixture然后对同一组请求断言完全相同的响应。例如请求/items无参数返回完整列表Foo、Bar、Baz请求/items?qfoo在响应中附带q: foo请求/items?qfooskip1从第二项开始返回切片请求/items?qbarlimit2只取前两条可在该文件后续部分看到。测试文件第 10-25 行通过pytest的参数化 fixture 把这 6 个模块tutorial002_py310、tutorial002_an_py310、tutorial003_py310、tutorial003_an_py310、tutorial004_py310、tutorial004_an_py310放进同一套断言从测试层面直接印证函数式 dict 依赖、Annotated[Any, Depends(类)]以及Depends()简写解析出的查询参数与响应结构完全一致。小结围绕类作为依赖这一主题可以提炼出四条可直接使用的经验依赖的本质是 callable。FastAPI 只关心能不能调用以及参数是什么函数、类乃至任意实现了__call__的对象都能成为依赖。类的__init__参数就是依赖参数。把公共查询参数或其它注入数据放进__init__FastAPI 会在每次请求时实例化该类并完成参数解析、转换、校验与 OpenAPI 文档化。类型注解 ≠ 依赖体。Depends(...)中传入的对象才决定依赖逻辑类型注解仅供编辑器与静态检查使用因此应尽量声明真实类型而非Any。Depends()是类即依赖的简写。当依赖正是那个要被调用实例化的类时可以省去在Depends里重复类名但若依赖是函数等其它 callable仍需显式书写。相关文档与源码可继续查阅依赖注入教程目录、本页依赖的核心解析实现 fastapi/dependencies/utils.py 以及等价性测试 test_tutorial002_tutorial003_tutorial004.py。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表