• 【Python开发】FastAPI 07:Depends 依赖注入


    FastAPI 中,Depends 是一个依赖注入系统,用于注入应用程序中所需的依赖项,通过 Depends,我们可以轻松地将依赖项注入到 FastAPI 路由函数中。简单来说,Depends 依赖注入的目的就是将代码重复最小!

    目录

    1 Depends 使用

    1.1 依赖注入介绍

    1.2 demo

    1.3 OpenAPI 集成

    2 不同依赖项

    2.1 类作为依赖项

    ① 可调用对象

    ② Python 类依赖注入

    2.2 嵌套依赖项

    2.3 dependencies 参数

    2.4 全局依赖项


    📌源码地址:

    FastAPI_Study_Yinyu: FastAPI学习路径,CSDN专栏:http://t.csdn.cn/JRtrk

    1 Depends 使用

    FastAPI 提供了简单易用,但功能强大的依赖注入系统,这可以让开发人员轻松地把组件集成至 FastAPI

    1.1 依赖注入介绍

    依赖注入是一种设计模式,用于降低程序组件之间的耦合度。它通过将组件之间的依赖关系从代码中分离出来,使得组件可以更加灵活地被替换、修改或重用。

    假设有一个类 需要使用类B的功能,如果在 类中直接实例化 B 类,那么 A 类和 B 类之间的依赖关系就会很紧密,难以达到解耦的目的。而使用依赖注入的方式,A 类不再直接实例化 B 类,而是将 B 类的实例通过构造函数、属性、或者接口等方式注入到 A 类中。

    依赖注入常用于以下场景:

    • 共享业务逻辑(复用相同的代码逻辑)
    • 共享数据库连接
    • 实现安全、验证、角色权限
    • 等……

    上述场景均可以使用依赖注入,将代码重复最小化。

    1.2 demo

    这是一个非常简单的例子,通过这个例子,您可以初步了解「依赖注入」的工作机制。

    1. from typing import Union
    2. from fastapi import Depends, FastAPI #2导入 Depends
    3. app = FastAPI()
    4. #1创建依赖项
    5. async def common_parameters(
    6. q: Union[str, None] = None, skip: int = 0, limit: int = 100
    7. ):
    8. return {"q": q, "skip": skip, "limit": limit}
    9. @app.get("/items/")
    10. async def read_items(commons: dict = Depends(common_parameters)): #3声明依赖项
    11. return commons
    12. @app.get("/users/")
    13. async def read_users(commons: dict = Depends(common_parameters)): #3声明依赖项
    14. return commons

    此时访问 127.0.0.1:8000/users/?q=yinyu&skip=1&limit=10 ,响应正常:

    📌依赖项

    依赖项 common_parameters 预期接收如下参数:

    • 类型为 str 的可选查询参数 q
    • 类型为 int 的可选查询参数 skip,默认值是 0
    • 类型为 int 的可选查询参数 limit,默认值是 100

    然后,依赖项函数将返回包含这些值的 dict

    声明依赖项时,这里只能传给 Depends 一个参数,且该参数必须是可调用对象。比如函数,该函数接收的参数等于路径操作函数的参数

    📌实际请求时,FastAPI 执行如下操作:

    • 用正确的参数调用依赖项函数(「可依赖项」
    • 获取函数返回的结果
    • 把函数返回的结果赋值给路径操作函数的请求参数,例如 commons

     如此,只编写一次代码,FastAPI 就可以为多个路径操作/请求接口共享这段代码 。

    注意,无需创建专门的类,并将之传递给 FastAPI 以进行「注册」或执行类似的操作。 只要把它传递给 DependsFastAPI 就知道该如何执行后续操作。

    1.3 OpenAPI 集成

    依赖项及子依赖项的所有请求声明、验证和需求都可以集成至同一个 OpenAPI 概图。

    因此,交互文档里也会显示依赖项的所有信息:

    依赖注入系统如此简洁的特性,让 FastAPI 可以与下列系统兼容:

    • 关系型数据库
    • NoSQL 数据库
    • 外部支持库
    • 外部 API
    • 认证和鉴权系统
    • API 使用监控系统
    • 响应数据注入系统
    • 等等……

    2 不同依赖项

    在前面的例子中, 我们从依赖项中返回了一个 dict,但是我们知道编辑器不能为 dict 提供很多支持(比如补全),因为编辑器不知道 dict 的键和值类型, 因此接下来将介绍更好用的方式~

    依赖项不只是函数,只要是 "可调用对象" 就可以,Python 中的 "可调用对象" 是指任何 Python 可以像函数一样 "调用" 的对象。 

    2.1 类作为依赖项

    ① 可调用对象

    创建一个 Python 类的实例,您可以使用和依赖注入相同的语法,比如:

    1. class Cat:
    2. def __init__(self, name: str):
    3. self.name = name
    4. fluffy = Cat(name="Mr Fluffy")

    在这个例子中,fluffy 是一个 Cat 类的实例。 为了创建 fluffy,调用了 Cat 。 所以,Python 类也是 可调用对象。

    因此,在 FastAPI 中,你可以使用一个 Python 类作为一个依赖项。

    ② Python 类依赖注入

    因此我们可以将 demo 里的依赖项  common_parameters 更改为类 CommonQueryParams:

    1. class CommonQueryParams:
    2. def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
    3. self.q = q
    4. self.skip = skip
    5. self.limit = limit

    它与我们 demo 里的 common_parameters 具有相同的参数。

    📌声明依赖项

    1. @app.get("/items2/")
    2. async def read_items(commons: CommonQueryParams = Depends(CommonQueryParams)):
    3. return commons

    FastAPI 调用 CommonQueryParams 类。这将创建该类的一个 "实例",该实例将作为参数 commons 被传递给你的函数。

    📌简化

    注意,我们在上面的代码中编写了两次 CommonQueryParams

    commons: CommonQueryParams = Depends(CommonQueryParams)

    FastAPI 对此做了简化,你可以写成:

    commons=Depends(CommonQueryParams) 或 commons: CommonQueryParams = Depends()

    📌完整代码

    1. from typing import Union
    2. from fastapi import Depends, FastAPI
    3. app = FastAPI()
    4. class CommonQueryParams:
    5. def __init__(self, q: Union[str, None] = None, skip: int = 0, limit: int = 100):
    6. self.q = q
    7. self.skip = skip
    8. self.limit = limit
    9. @app.get("/items2/")
    10. async def read_items(commons = Depends(CommonQueryParams)):
    11. return commons

    此时访问 127.0.0.1:8000/items2/?q=yinyu&skip=2&limit=10 ,响应正常:

    2.2 嵌套依赖项

    FastAPI 支持创建含子依赖项的依赖项,并且可以按需声明任意深度的子依赖项嵌套层级。

    📌 第一层依赖项

    1. def query_extractor(q: Union[str, None] = None):
    2. return q

    这段代码声明了类型为 str 的可选查询参数 q,然后返回这个查询参数, 这个函数很简单。

    📌 第二层依赖项

    接下来,创建另一个依赖项函数,并同时用该依赖项自身再声明一个依赖项:

    1. def query_or_cookie_extractor(
    2. q: str = Depends(query_extractor),
    3. last_query: Union[str, None] = Cookie(default=None),
    4. ):
    5. if not q:
    6. return last_query
    7. return q

    这里重点说明一下声明的参数:

    • 尽管该函数自身是依赖项,但还声明了另一个依赖项
      • 该函数依赖 query_extractor, 并把 query_extractor 的返回值赋给参数 q
    • 同时,该函数还声明了类型是 str 的可选 cookie
      • 用户未提供查询参数 q 时,则使用上次使用后保存在 cookie 中的查询

    📌 使用嵌套依赖项

    1. ...
    2. def query_extractor(q: Union[str, None] = None):
    3. return q
    4. def query_or_cookie_extractor(
    5. q: str = Depends(query_extractor),
    6. last_query: Union[str, None] = Cookie(default=None),
    7. ):
    8. if not q:
    9. return last_query
    10. return q
    11. @app.get("/items3/")
    12. async def read_query(query_or_default: str = Depends(query_or_cookie_extractor)):
    13. return {"q_or_cookie": query_or_default}

    注意,这里在路径操作函数中只声明了一个依赖项,即 query_or_cookie_extractor

    FastAPI 必须先处理 query_extractor,以便在调用 query_or_cookie_extractor 时使用 query_extractor 返回的结果。类似下边的路径图:

    此时访问 127.0.0.1:8000/items3/?q=yinyu ,响应正常:

    📌 依赖项缓存

    如果多次声明了同一个依赖项,例如多个依赖项共用一个子依赖项,那么 FastAPI 在处理同一请求时,只调用一次该子依赖项。

    FastAPI 默认不会为同一个请求多次调用同一个依赖项,而是把依赖项的返回值进行「缓存」,并把它传递给同一请求中所有需要使用该返回值的「依赖项」

    如果不想使用「缓存」值,而是为需要在同一请求的每一步操作(多次)中都实际调用依赖项,可以把 Depends 的参数 use_cache 的值设置为 False :

    1. async def needy_dependency(fresh_value: str = Depends(get_value, use_cache=False)):
    2. return {"fresh_value": fresh_value}

    2.3 dependencies 参数

    有时,我们并不需要在路径操作函数中使用依赖项的返回值,有些依赖项也不返回值,或者说接口根本用不上这些返回值~

    对于这种情况,不必在声明路径操作函数的参数时使用 Depends,而是可以在路径操作装饰器中添加一个由 dependencies 组成的 list

    路径操作装饰器支持可选参数 ~ dependencies, 该参数的值是由 Depends() 组成的 list

    1. from fastapi import Depends, FastAPI, Header, HTTPException
    2. app = FastAPI()
    3. #路径装饰器依赖项可以声明请求的需求项(比如响应头)或其他子依赖项
    4. async def verify_token(x_token: str = Header()):
    5. if x_token != "fake-super-secret-token":
    6. #路径装饰器依赖项与正常的依赖项一样,可以 raise 异常:
    7. raise HTTPException(status_code=400, detail="X-Token header invalid")
    8. async def verify_key(x_key: str = Header()):
    9. if x_key != "fake-super-secret-key":
    10. raise HTTPException(status_code=400, detail="X-Key header invalid")
    11. return x_key
    12. @app.get("/items4/", dependencies=[Depends(verify_token), Depends(verify_key)])
    13. async def read_items():
    14. return [{"item": "Foo"}, {"item": "Bar"}]

    路径操作装饰器依赖项(以下简称为“路径装饰器依赖项”)的执行或解析方式和普通依赖项一样,但就算这些依赖项会返回值,它们的值也不会传递给路径操作函数

    2.4 全局依赖项

    有时,我们要为整个应用添加依赖项。 通过与定义路径装饰器依赖项类似的方式,可以把依赖项添加至整个 FastAPI 应用:

    1. from fastapi import Depends, FastAPI, Header, HTTPException
    2. async def verify_token(x_token: str = Header()):
    3. if x_token != "fake-super-secret-token":
    4. raise HTTPException(status_code=400, detail="X-Token header invalid")
    5. async def verify_key(x_key: str = Header()):
    6. if x_key != "fake-super-secret-key":
    7. raise HTTPException(status_code=400, detail="X-Key header invalid")
    8. return x_key
    9. app1 = FastAPI(dependencies=[Depends(verify_token), Depends(verify_key)])
    10. @app1.get("/items5/")
    11. async def read_items():
    12. return [{"item": "Portal Gun"}, {"item": "Plumbus"}]

    那么所有 app 请求都将经过 verify_token 和 verify_key 函数,类似于过滤器。

  • 相关阅读:
    弹性云端新算力,驱动沉浸新交互 |2022阿里云金融创新峰会
    Linux磁盘管理
    59页数字乡镇综合解决方案
    MySQL数据库二:MySQL索引
    2022-8-3 第七组 潘堂智 锁、多线程
    【C++】BMI身体质量指数计算工具
    [框架设计之道(二)]设备、任务设置及业务流程
    MinIO 图片转文件的分界线RELEASE.2022-05-26T05-48-41Z
    【AI】深度学习——前馈神经网络——全连接前馈神经网络
    SSL Pinning 绕过、Web 应用程序黑客攻击、漏洞查找和执行 VAPT | 详细 VAPT 方法、在渗透测试中绕过CSP、账户接管漏洞
  • 原文地址:https://blog.csdn.net/weixin_51407397/article/details/131029611