ARTICLE DETAIL

资讯详情

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

FastAPI 依赖注入系统详解:从第一行 Depends 到分层依赖树与 OpenAPI 自动集成

FastAPI 依赖注入系统详解:从第一行 Depends 到分层依赖树与 OpenAPI 自动集成 FastAPI 依赖注入系统详解从第一行 Depends 到分层依赖树与 OpenAPI 自动集成【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi本文系统讲解 FastAPI 内置的 Dependency Injection依赖注入DI体系如何用一个普通函数定义 dependency、如何配合Annotated声明与共享依赖、async/同步函数如何混用以及 FastAPI 是如何在底层递归求解整个依赖树并将依赖中的参数声明自动整合进 OpenAPI 文档的。读完后你能够独立设计可复用的共享逻辑、数据库连接与权限校验依赖并理解其源码级运行原理。什么是“依赖注入”Dependency Injection在编程语境中Dependency Injection 指的是你的代码在 FastAPI 中即path operation functions路径操作函数拥有一种方式去声明它运行所需的资源这些资源被称为 dependencies依赖项然后由系统这里是 FastAPI负责提供这些依赖项把它们 inject注入进你的代码。这在以下场景中非常有用需要共享逻辑同一段代码逻辑被反复使用需要共享数据库连接需要强制实施安全、认证、角色权限等要求还有更多场景……以上一切都能把代码重复code repetition降到最少。第一步创建一个 dependencydependable先看一个最简单的例子。这个例子此刻看起来用处不大但它能让你集中关注 DI 系统的工作方式。一个 dependency 本质上就是一个普通函数它可以接受与path operation function相同的那类参数见 docs_src/dependencies/tutorial001_an_py310.pyasync def common_parameters(q: str | None None, skip: int 0, limit: int 100): return {q: q, skip: skip, limit: limit}就这么简单只有 2 行。它的形状和结构与所有path operation functions完全相同——你可以把它理解为一个没有装饰器的path operation function没有app.get(/some-path)。它可以按你的需要返回任何东西。在这个例子中该 dependency 期望一个可选的str类型查询参数q一个可选的int类型查询参数skip默认值0一个可选的int类型查询参数limit默认值100然后返回一个包含这些值的dict。注意FastAPI 从 version 0.95.0 开始支持并推荐使用Annotated风格。如果你的环境版本较旧使用Annotated时会报错。使用Annotated之前请确认已将 FastAPI 升级到至少 0.95.1。导入Depends并在 dependant 中声明from fastapi import Depends, FastAPI就像你在path operation function参数上使用Body、Query等一样用一个新的参数配合Depends来声明依赖完整可运行示例见 docs_src/dependencies/tutorial001_an_py310.pyapp.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虽然用法上Depends与Body、Query类似但它的行为有所不同你只给Depends传入一个参数这个参数应该是一个函数callable你不直接调用它末尾不加括号而是把它作为参数传给Depends()该函数接收参数的方式与path operation functions相同。每当有新请求到来时FastAPI 会负责用正确的参数调用你的 dependencydependable函数从该函数取回结果把结果赋值给path operation function中对应的参数。这样你只需把共享代码写一次FastAPI 就负责为你的各个path operations调用它。提示你不需要创建任何特殊类也不需要把它 register注册到 FastAPI 中。你只要把它传给DependsFastAPI 就知道接下来该怎么做。使用Annotated共享 dependency上面的示例中存在少量代码重复每次使用common_parameters()时都要写完整的类型注解和Depends()commons: Annotated[dict, Depends(common_parameters)]由于使用了Annotated可以把它存成一个变量类型别名type alias在多处复用见 docs_src/dependencies/tutorial001_02_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} CommonsDep Annotated[dict, Depends(common_parameters)] app.get(/items/) async def read_items(commons: CommonsDep): return commons app.get(/users/) async def read_users(commons: CommonsDep): return commons提示这只是标准 Python 的 type alias并非 FastAPI 特有的机制。但因为 FastAPI 建立在 Python 标准包括Annotated之上你可以在代码中放心使用这一技巧。dependencies 会如预期般继续工作而且最关键的好处是类型信息得以保留编辑器能继续提供autocompletion、inline errors等能力mypy等静态检查工具同样受益。当你在大型代码库中把同一批 dependencies 反复用于多个path operations时这一技巧尤为有用。async还是不asyncdependencies 和path operation functions一样由 FastAPI 调用因此定义函数时的规则同样适用你可以用async def或普通的def定义你可以在普通def的path operation function中声明async def的 dependency也可以在async def的path operation function中声明def的 dependency等等。这完全没关系。FastAPI 知道该怎么做。注意如果你不熟悉async和await可以参考官方文档中 Async:In a hurry? 章节对应仓库文件 docs/hi/docs/async.md来快速理解。源码视角FastAPI 如何解析与调用 dependency结合仓库源码可以印证上面文档描述的每一句FastAPI 会负责……背后的真实实现。Depends本身只是一个轻量数据类fastapi/params.py#L745-L749 中定义了Dependsdataclass(frozenTrue) class Depends: dependency: Callable[..., Any] | None None use_cache: bool True scope: Literal[function, request] | None None也就是说文档强调的不直接调用、只把函数传进去在源码层面就是Depends只是把 callable记录下来dependency字段并附带缓存与执行范围等元信息。它的子类Security在此基础上增加了scopes参数用于 OAuth2 权限声明。DependantFastAPI 内部对依赖项的统一建模fastapi/dependencies/models.py#L31-L51 中的Dependant数据类是 FastAPI 对每一个 dependency也包括path operation function本身的内部表示dataclass(slotsTrue) class Dependant: path_params: list[ModelField] field(default_factorylist) query_params: list[ModelField] field(default_factorylist) header_params: list[ModelField] field(default_factorylist) cookie_params: list[ModelField] field(default_factorylist) body_params: list[ModelField] field(default_factorylist) dependencies: list[Dependant] field(default_factorylist) name: str | None None call: Callable[..., Any] | None None # ... request_param_name、background_tasks_param_name 等注入点 use_cache: bool True path: str | None None scope: Literal[function, request] | None None注意dependencies: list[Dependant]字段——它是自引用的每个依赖项可以嵌套子依赖项这正是文档中hierarchical tree分层树说法在数据结构上的体现。solve_dependencies递归求解、缓存与线程池调度请求到来时真正执行注入的是 fastapi/dependencies/utils.py#L586 的solve_dependencies()其关键流程与文档描述一一对应递归求解子依赖对dependant.dependencies中的每个子依赖先递归调用solve_dependencies收集错误校验失败时收集错误并跳过后续注入缓存复用若sub_dependant.use_cache为True且缓存键_get_cache_key由调用对象、OAuth scopes、scope 组成已存在于dependency_cache则直接复用已有结果同一个请求内同一 dependency 不会被重复执行区分调用方式generator/async-generator 依赖含yield的依赖交由_solve_generator处理并挂到AsyncExitStack上管理资源生命周期async函数直接await call(...)同步函数则通过run_in_threadpool在线程池中执行——这就是def和async def混用都没关系的源码依据参数注入求解完结果后FastAPI 再调用request_params_to_argsfastapi/dependencies/utils.py#L780从请求中提取 path/query/header/cookie 参数做类型校验后填进values最终作为 kwargs 调用依赖函数把返回值赋给 dependant 的name对应参数。与 OpenAPI 自动集成你的 dependencies以及它们的 sub-dependencies中所有的请求声明、校验和约束都会整合进同一个 OpenAPI schema。因此交互式文档中会展示这些依赖项带来的全部参数信息——例如上图中由common_parameters()依赖贡献的q、skip、limit查询参数会出现在/items/和/users/两个端点的文档里。这一能力的实现可以在 fastapi/dependencies/utils.py#L169-L195 的get_flat_params()中找到印证它沿着Dependant树遍历所有层级path → query → header → cookie把整棵依赖树中的参数拍平成一个列表供 OpenAPI 生成使用——所以依赖声明的参数会自动出现在文档中你无需手写任何 schema。简单使用的本质与常见术语如果你仔细思考path operation functions本身也是被声明式使用的只要path和operation匹配它们就会被调用随后 FastAPI 负责用正确的参数调用函数并从请求中提取数据。事实上几乎所有或大多数web 框架都这样工作——你从不直接调用这些函数它们由框架这里是 FastAPI调用。依赖注入系统在此基础上更进一步你可以告诉 FastAPI你的path operation function还依赖其他需要先执行的组件FastAPI 会负责执行它们并把结果注入。表达同一个 dependency injection 概念的其他常见术语有resources资源providers提供者services服务injectables可注入项components组件FastAPI plug-ins插件生态各种集成和 plug-ins 都是基于Dependency Injection系统构建的。但实际上plug-ins 本身并没有存在的必要借助 dependencies你可以声明无数个可被path operation functions使用的集成与交互。而且编写 dependency 极其简单直接——你只需 import 所需的 Python 包就能用几行代码把它与你的 API 函数集成起来字面意义上的几行。在官方文档的后续章节中对应 docs_src/dependencies/ 目录下的更多示例源码可以看到针对关系型数据库、NoSQL 数据库、安全security等场景的具体 examples以及子依赖例如 docs_src/dependencies/tutorial002_an_py310.py 中依赖再依赖common_parameters的写法。FastAPI 的兼容性依赖注入系统的简洁性使 FastAPI 与以下组件天然兼容所有关系型数据库relational databasesNoSQL 数据库外部 Python 包外部 API认证与授权authentication 和 authorization系统API 用量监控系统响应数据注入系统等等。简单而强大分层hierarchical依赖树虽然定义和使用分层依赖注入系统非常简单但它非常强大。你可以定义这样的 dependencies它们本身又声明自己的 dependencies。最终会形成一棵依赖树Dependency Injection 系统会负责求解整棵树上的所有 dependencies及其 sub-dependencies并在每一步把结果注入到需要它的地方。例如假设有 4 个 API 端点path operations/items/public//items/private//users/{user_id}/activate/items/pro/你可以仅用 dependencies 和 sub-dependencies为每个端点附加不同的权限要求在这个结构中current_user是基础依赖active_user建立在current_user之上再向上分化出admin_user与paying_user。FastAPI 会为每个端点沿依赖链自底向上执行先解析current_user再解析上层依赖逐层把结果注入。小结声明即集成所有这些 dependencies 在声明 requirements 时同时也在你的path operations上附加了 parameters、validations 等。FastAPI 负责把这一切整合进 OpenAPI schema使其在交互式文档系统中可见。回顾整条链路层面位置作用用户 APIDepends()fastapi/params.py#L745-L749以参数形式声明依赖不直接调用内部建模Dependantfastapi/dependencies/models.py#L31-L51用自引用列表表达依赖树记录参数与注入点运行时求解solve_dependencies()fastapi/dependencies/utils.py#L586递归求解、错误收集、缓存复用、线程池调度、结果注入文档集成get_flat_params()fastapi/dependencies/utils.py#L169-L195拍平依赖树参数自动写入 OpenAPI schema适用前提本文示例基于当前仓库docs_src/dependencies/中的 Python 3.10 源码风格str | None联合类型与Annotated要求 FastAPI 0.95.1Depends的use_cache/scope等元信息对应仓库当前版本的Depends定义较旧版本 FastAPI 可能不具备全部字段。【免费下载链接】fastapiFastAPI framework, high performance, easy to learn, fast to code, ready for production项目地址: https://gitcode.com/GitHub_Trending/fa/fastapi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表