FastAPI 中的依赖注入Dependency Injection

什么是依赖注入

依赖注入(Dependency Injection,简称 DI)是一种软件设计模式,它的核心思想是将对象的依赖关系从对象本身的创建和管理中分离出来。简单来说,就是一个对象不自己去创建它所依赖的其他对象,而是由外部将这些依赖对象提供给它。

在 FastAPI 中,依赖注入通常用于以下场景:

  1. 共享逻辑:多个路由处理函数可能需要执行相同的逻辑,比如身份验证、权限检查等,通过依赖注入可以将这些逻辑封装成一个依赖项,在需要的地方进行复用。
  2. 资源管理:像数据库连接、文件句柄等资源的管理,通过依赖注入可以确保资源的正确创建和释放。
  3. 提高可测试性:依赖注入使得代码的各个部分更加独立,便于进行单元测试。

简单的依赖项函数

我们先来看一个简单的依赖项函数的例子。假设我们有一个 API,需要记录每次请求的日志,我们可以将日志记录的逻辑封装成一个依赖项函数。

from fastapi import FastAPI, Depends

app = FastAPI()

# 定义一个依赖项函数,用于记录请求日志
def log_request():
    print("收到一个新的请求")

# 在路由处理函数中使用依赖项
@app.get("/")
def read_root(dependency=Depends(log_request)):
    """
    处理根路径的 GET 请求,并使用 log_request 作为依赖项。

    Args:
        dependency: 依赖项函数的返回值,这里没有返回值,仅执行函数逻辑。

    Returns:
        dict: 包含欢迎信息的字典。
    """
    return {"message": "欢迎来到山海摸鱼人的 FastAPI 世界"}
代码解释
  • 依赖项函数 log_request :这是一个简单的函数,它的作用是打印一条日志信息,表示收到了一个新的请求。
  • Depends 函数Depends 是 FastAPI 中用于声明依赖项的函数。在路由处理函数 read_root 中,我们使用 Depends(log_request) 来声明 log_request 是一个依赖项。当客户端发送请求到 / 路径时,FastAPI 会先调用 log_request 函数,然后再执行 read_root 函数。
测试

在终端输入 uvicorn main:app --reload 启动 FastAPI 应用后,打开浏览器访问 http://127.0.0.1:8000/

输出结果

图片在终端中会看到如下输出:

收到一个新的请求
图片

带返回值的依赖项函数

依赖项函数也可以有返回值,并且这个返回值可以在路由处理函数中使用。

from fastapi import FastAPI, Depends

app = FastAPI()

# 定义一个带返回值的依赖项函数,返回一个问候语
def get_greeting():
    return "你好,欢迎访问!"

# 在路由处理函数中使用带返回值的依赖项
@app.get("/greet")
def get_greeting_message(greeting=Depends(get_greeting)):
    """
    处理 /greet 路径的 GET 请求,并使用 get_greeting 作为依赖项。

    Args:
        greeting (str): 依赖项函数 get_greeting 的返回值。

    Returns:
        dict: 包含问候信息的字典。
    """
    return {"message": greeting}

代码解释

  • 依赖项函数 get_greeting :这个函数返回一个问候语字符串。
  • 路由处理函数 get_greeting_message :在这个函数中,我们使用 Depends(get_greeting) 声明 get_greeting 是一个依赖项,并将其返回值赋给 greeting 参数。这样,在函数内部就可以使用这个问候语了。
测试

在终端输入 uvicorn main:app --reload 启动 FastAPI 应用后,打开浏览器访问 http://127.0.0.1:8000/greet

输出结果
{"message": "你好,欢迎访问!"}
图片

依赖项的嵌套使用

在实际应用中,依赖项可以嵌套使用,即一个依赖项可以依赖于另一个或多个依赖项。这在处理复杂业务逻辑时非常有用。例如,我们有一个需要进行身份验证并且需要获取用户信息的 API。身份验证可以作为一个依赖项,而获取用户信息又依赖于身份验证的结果。

from fastapi import FastAPI, Depends, HTTPException
from typing import Optional

app = FastAPI()

# 模拟用户数据
class User:
    def __init__(self, username: str, role: str):
        self.username = username
        self.role = role

# 模拟数据库
users_db = {
    "山海逍遥客": User(username="山海逍遥客", role="管理员"),
    "山海悠游人": User(username="山海悠游人", role="普通用户")
}

# 依赖项:验证用户身份
def authenticate_user(username: str, password: str) -> Optional[User]:
    # 增加对输入参数的验证
    ifnot isinstance(username, str) ornot isinstance(password, str):
        raise HTTPException(status_code=400, detail="用户名和密码必须为字符串类型")
    if username in users_db and users_db[username].role == "管理员":
        return users_db[username]
    returnNone

# 依赖项:获取当前用户信息,依赖于身份验证
def get_current_user(user: User = Depends(authenticate_user)) -> User:
    ifnot user:
        raise HTTPException(status_code=401, detail="用户未注册")
    return user

# 路由:仅管理员可访问
@app.get("/admin_dashboard")
def admin_dashboard(user: User = Depends(get_current_user)):
    if user.role != "管理员":
        raise HTTPException(status_code=403, detail="权限不足")
    return {"message": f"欢迎,管理员 {user.username}!"}
代码解释
  • 在 authenticate_user 函数中,增加了对输入参数 username 和 password 的类型验证。如果输入不是字符串类型,会抛出一个 HTTPException,状态码为 400,表示客户端请求有错误。
  • 在 get_current_user 函数中,当用户未认证时,抛出一个状态码为 401 的 HTTPException,表示需要进行身份验证。
  • 在 admin_dashboard 路由处理函数中,当用户角色不是“管理员”时,抛出一个状态码为 403 的 HTTPException,表示没有权限访问。
测试(模拟管理员登录):

在终端输入 uvicorn main:app --reload 启动 FastAPI 应用后,打开浏览器访问 http://127.0.0.1:8000/admin_dashboard?username=山海逍遥客&password=123456

输出结果
{"message": "欢迎,管理员 山海逍遥客!"}

图片模拟未注册用户
访问 http://127.0.0.1:8000/admin_dashboard?username=山海闲逛者&password=123456

输出结果

{"detail":"用户未注册"}
图片依赖注入在Web应用中具有广泛的应用场景,如身份验证与授权、数据库连接管理和缓存管理。

在身份验证方面,依赖注入可将验证逻辑封装为依赖项,在需要验证的路由中使用。数据库连接作为共享资源,可通过依赖注入在应用启动时创建,并在各路由中注入,避免资源泄漏。缓存管理则能提高系统性能,将缓存操作封装为依赖项,在需要缓存的路由中使用。

依赖注入的优势包括代码复用、提高可维护性和可测试性,使代码结构更清晰,逻辑更独立,便于单元测试和修改。

来源:山海摸鱼人

THE END