6 依赖

FastAPI 非常出色的设计特性之一是 “依赖注入 ”的技术。这个术语听起来既专业又深奥,但它却是 FastAPI 的一个关键方面,而且在很多层面上都有惊人的作用。本章将介绍 FastAPI 的内置功能以及如何编写自己的功能。

6.1 什么是依赖

依赖项是某一时刻需要的特定信息。获取这些信息的通常方法是编写代码,在需要时获取这些信息。

在编写网络服务时,您可能需要执行以下操作:

  • 从HTTP请求中收集输入参数

  • 验证输入

  • 检查用户身份验证和授权

  • 从数据源(通常是数据库)中查找数据

  • 输出指标、日志或跟踪信息

  • 网络框架会将 HTTP 请求字节转换为数据结构,然后在网络层函数中提取所需的内容。

6.2 依赖关系的问题

在需要的时候得到想要的东西,而且不需要外部代码知道你是如何得到它的,这似乎非常合理。但事实证明,后果是存在的:

  • 测试

你无法测试可能以不同方式查找依赖关系的函数变体。

  • 隐藏依赖关系

隐藏细节意味着当外部代码发生变化时,您的函数所需的代码可能会被破坏。

  • 代码重复

如果您的依赖关系是常见的依赖关系(如在数据库中查找用户或合并HTTP请求中的值),您可能会在多个函数中重复查找代码。

  • OpenAPI 可见性

FastAPI 为您制作的自动测试页面需要依赖注入机制提供的信息。

6.3 依赖注入

依赖注入这个术语听起来很简单:将函数所需的任何特定信息传递到函数中。传统的方法是传递一个辅助函数,然后调用该函数来获取特定数据。

6.4 FastAPI依赖关系

FastAPI 更进一步:您可以将依赖项定义为函数的参数,FastAPI会自动调用这些依赖项,并传递它们返回的值。例如,user_dep依赖项可以从HTTP参数中获取用户的姓名和密码,然后在数据库中查找,并返回一个令牌,之后您就可以用它来跟踪该用户。网络处理函数不会直接调用这个函数,而是在函数调用时进行处理。

你已经看到了一些依赖关系,但没有看到它们被称为依赖关系: HTTP数据源,如Path、Query、Body 和Header。这些函数或Python类从HTTP请求的不同区域挖掘所请求的数据。它们隐藏了细节,如有效性检查和数据格式。

为什么不编写自己的函数来做到这一点呢?你可以这样做,但你不会有这些功能:

  • 数据有效性检查
  • 格式转换
  • 自动文档

在许多其他Web框架中,您可以在自己的函数中进行这些检查。但在 FastAPI 中,您可以处理自己的依赖关系,就像内置的依赖关系一样。

参考资料

6.5编写依赖关系

在FastAPI中,依赖关系是被执行的,因此依赖关系对象需要是Callable类型,包括函数和类--你调用的东西,带有括号和可选参数。

下例展示了一个user_dep()依赖关系函数,它接受姓名和密码字符串参数,如果用户有效,则返回True。在第一个版本中,让函数返回True。

实例:依赖函数

from fastapi import FastAPI, Depends, params

app = FastAPI()

# the dependency function:
def user_dep(name: str = params, password: str = params):
    return {"name": name, "valid": True}

# the path function / web endpoint:
@app.get("/user")
def get_user(user: dict = Depends(user_dep)) -> dict:
    return user

    
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("hello:app", reload=True)

user_dep()是一个依赖函数。它的作用类似于FastAPI路径函数(它知道params等信息),但上面没有路径装饰器。它是辅助函数,而不是网络端点本身。

路径函数get_user()表示它需要user的参数变量,该变量将从依赖函数user_dep()中获取值。

注意:在 get_user()的参数中,我们不能说user = user_dep,因为user_dep是一个 Python 函数对象。我们也不能说user = user_dep(),因为这会在定义get_user()时调用 user_dep(),而不是在使用它时。因此,我们需要额外的辅助FastAPI Depends()函数,以便在需要时调用 user_dep()。

在路径函数参数列表中可以有多个依赖项。

6.5依赖范围

您可以将依赖关系定义为单个路径函数、一组路径函数或整个网络应用程序。

6.5.1单路径

在您的路径函数中,包含一个类似下面这样的参数:def pathfunc(name: depfunc = Depends(depfunc)):或直接这样:def pathfunc(name: depfunc = Depends()):

name是你想调用depfunc返回的值。在前面的例子中

  • pathfunc 是 get_user()。
  • depfunc 是 user_dep()。
  • name 是 user。

下例使用此路径和依赖关系返回一个固定的用户名和一个有效的布尔值。

from fastapi import FastAPI, Depends, params

app = FastAPI()

# the dependency function:
def user_dep(name: str = params, password: str = params):
    return {"name": name, "valid": True}

# the path function / web endpoint:
@app.get("/user")
def get_user(user: dict = Depends(user_dep)) -> dict:
    return user

    
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("hello:app", reload=True)

如果您的依赖关系函数只是检查某些内容而不返回任何值,您也可以在路径装饰器中定义依赖关系(前一行,以 @ 开头):@app.method(url, dependencies=[Depends(depfunc)])

实例定义用户检查依赖项

from fastapi import FastAPI, Depends, params

app = FastAPI()

# the dependency function:
def user_dep(name: str = params, password: str = params):
    return {"name": name, "valid": True}

# the path function / web endpoint:
@app.get("/user")
def get_user(user: dict = Depends(user_dep)) -> dict:
    return user

    
if __name__ == "__main__":
    import uvicorn
    uvicorn.run("hello:app", reload=True)

6.5.2多路径

from fastapi import FastAPI, Depends, APIRouter

router = APIRouter(..., dependencies=[Depends(depfunc)])

这样,router下的所有路径函数都将调用 depfunc()。

6.5.3全局

在定义顶层 FastAPI 应用程序对象时,可以为其添加适用于所有路径函数的依赖关系。

from fastapi import FastAPI, Depends

def depfunc1():
    pass

def depfunc2():
    pass

app = FastAPI(dependencies=[Depends(depfunc1), Depends(depfunc2)])

@app.get("/main")
def get_main():
    pass

6.6 小结

本章讨论了依赖关系和依赖注入--在需要时以直接的方式获取所需数据的方法。