FastAPI系列08:Depends函数与依赖注入(DI)


FastAPI 提供了一种简化版的依赖注入系统。它允许在FastAPI程序运行时将外部组件(依赖项,包括函数、类、对象)注入到程序内部的函数或类中,而不是在函数内部直接创建或管理它们。

区别于传统的依赖注入模式,FastAPI 的依赖注入采用的是函数式依赖注入,基于 Python 的类型提示和 Depends()。它没有传统的 IoC 容器,而是通过函数或类直接声明依赖,框架在运行时自动解析和注入。

1、注入的方式

FastAPI 允许在路由函数、路由组、以及应用全局注入依赖项。现在我们希望在每个路由中可以根据token获得当前用户信息:
dependence.py文件,用于获取当前用户信息

# 外部组件,用于获取当前用户信息
async def get_current_user(token:str=None):
	if token: # 可以根据实际业务补充具体逻辑
	    return {"username": "faker"}
# 外部组件,用于检查用户是否登录,如果没有登录直接返回HTTP 401
async def check_login(token: str = None):  
    if not token:  
        raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="未登录")

1)没有使用注入的情形

from fastapi import FastAPI  
from dependence import *

app = FastAPI()

@app.get("/")  
async def hello_world():  
    user = await get_current_user() # 需要手动调用依赖项
    return {"user": user, "message": "Hello World!"}

2)路由函数注入

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()

@app.get("/")  
async def hello_world(user=Depends(get_current_user)): # 路由函数注入点 
    return {"user": user, "message": "Hello World!"}

运行代码后:

  • 当访问 http://127.0.0.1:8000/?token=geekabc 时就会返回用户信息

2)路由路径注入

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()

@app.get("/",dependencies=[Depends(check_login)])  # 路由路径注入点
async def hello_world(user=Depends(get_current_user)): # 路由函数注入点 
    return {"user": user, "message": "Hello World!"}

运行代码后:

  • 当访问 http://127.0.0.1:8000/?token=geekabc 时就会返回用户信息

3)路由组注入

如果我们希望整个/user路由段可以先检查用户登录状态,然后返回当前用户信息时,可以为路由组注入了check_login,为路由注入了get_current_user

from fastapi import FastAPI, Depends, APIRouter
from dependence import *

app = FastAPI()
user_routers = APIRouter(prefix="/user", dependencies=[Depends(check_login)])# 路由组注入点

@user_routers.get("/")  
async def user_home(user=Depends(get_current_user)):   # 路由函数注入点    
	return {"user": user, "message": "User Home。"}

app.include_router(user_routers)

运行代码后:

  • 当访问 http://127.0.0.1:8000/user/ 时就会出现HTTP 401错误
  • 当访问 http://127.0.0.1:8000/user/?token=geekabc 时就会返回用户信息

4)全局注入

如果我们希望整个站点全部路由都先检查用户登录状态,然后返回当前用户信息时,可以在FastAPI实例创建时注入check_login

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI(dependencies=[Depends(check_login)])  # 全局注入点  
  
@app.get("/")  
async def hello_world(user=Depends(get_current_user)):# 路由函数注入点  
    return {"user": user, "message": "Hello World!"}

运行代码后:

  • 当访问 http://127.0.0.1:8000/ 时就会出现HTTP 401错误
  • 当访问 http://127.0.0.1:8000/?token=geekabc 时就会返回用户信息

在使用全局依赖项时,往往与FastAPI中的另一个概念——中间件(middleware) 相似,事实上两种方式可以在低耦合中实现上述功能,下面是采用了中间件的实现方式:

from fastapi import FastAPI
from dependence import *

app = FastAPI()

@app.middleware("http")  
async def check_login_middleware(request: Request, call_next):  
    token = request.query_params.get("token")  
    if not token:  
        raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="未登录")  
    else:  
        return await call_next(request)  
  
@app.get("/")  
async def hello_world(req: Request):  
    user = await get_current_user(req.query_params.get("token"))  
    return {"user": user, "message": "Hello World!"}

既然两种方式都可以,那就需要一定的取舍原则来使责权更加清晰,以下是一些使用参考:

  • 中间件:会在所有请求和响应中执行相同的逻辑,更适用于全局的、横切面的任务,如日志、监控、错误处理。
  • 依赖注入:可以有针对地对不同的路由分组、路由执行相同的逻辑,更适用于局部的、路由级的任务,如数据库操作、业务逻辑封装、用户验证,以及一些变化可能性较高的功能逻辑。

5)依赖覆盖

在使用全局依赖项时,可以通过FastAPI实例的dependency_overrides来覆盖原来的依赖项设定,如在上例中我们现在希望通过用户名和密码来检验用户时,可以这样做:

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI(dependencies=[Depends(check_login)])  # 全局注入点  

# 外部组件,通过用户名和密码自动登录,如果没有登录直接返回HTTP 403  
async def check_login_by_user(user: str = None, password: str = None):  
    if not user or not password:  
        raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="未登录")  
  
app.dependency_overrides[check_login] = check_login_by_user #覆盖原来的依赖项

@app.get("/")  
async def hello_world(user=Depends(get_current_user)):# 路由函数注入点  
    return {"user": user, "message": "Hello World!"}

运行代码后:

  • 当访问 http://127.0.0.1:8000/ 时就会出现HTTP 401错误
  • 当访问 http://127.0.0.1:8000/?user=geek&password=abc 时就会返回用户信息

6)参数化依赖

如果我们一开始就希望程序既可以通过token检验用户,也可以通过用户名和密码来检验用户,可以这样做:
dependence.py文件

# 外部组件,用于通过token获取当前用户信息  
def get_current_user_by_token(token: str = None):  
    if token:  # 可以根据实际业务补充具体逻辑  
        return {"username": "faker"}  
  
# 外部组件,用于通过用户名密码获取当前用户信息  
def get_current_user_by_user(user: str = None, password: str = None):  
    if user and password:  # 可以根据实际业务补充具体逻辑  
        return {"username": user}

main.py文件

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()

def get_current_user(check_type: str = "token"):  
    def dependency(request: Request):  
        if check_type == "token":  
            token = request.query_params.get("token")  
            return get_current_user_by_token(token)  
        else:  
            user = request.query_params.get("user")  
            password = request.query_params.get("password")  
            return get_current_user_by_user(user, password)  
  
    return dependency

@app.get("/")  
async def hello_world(user=Depends(get_current_user('user'))):  
    return {"user": user, "message": "Hello World!"}
    

运行代码后:

  • 当访问 http://127.0.0.1:8000/?user=geek&password=abc 时就会返回用户信息

7)嵌套依赖

如果我们希望在获得当前用户前,也检查一下token是否有效,那么可以这样做:
dependence.py文件

# 外部组件,用于检查token是否有效  
async def check_token(token: str = None):  
    if not token:  
        return False  
  
    else:  
        return True  
  
  
# 外部组件,用于通过token获取当前用户信息  
async def get_current_user(checked_token=Depends(check_token)):  
    if checked_token:  # 可以根据实际业务补充具体逻辑  
        return {"username": "faker"}  
    else:  
        raise HTTPException(status_code=HTTPStatus.UNAUTHORIZED, detail="未登录")

main.py文件

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()

@app.get("/")  
async def hello_world(user=Depends(get_current_user('user'))):  
    return {"user": user, "message": "Hello World!"}
    

运行代码后:

  • 当访问 http://127.0.0.1:8000/ 时就会出现HTTP 401错误
  • 当访问 http://127.0.0.1:8000/?token=geekabc 时就会返回用户信息

8)生成器依赖

yield生成器依赖特别适用于需要设置和清理操作的场景,如数据库会话管理:

from fastapi import Depends
from sqlalchemy.orm import Session

def get_db():
    db = SessionLocal()
    try:
        yield db
    finally:
        db.close()

@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user
    

2、依赖项类型

1)、函数依赖,注入一个外部函数

函数依赖是FastAPI中最常见的依赖注入方式。在“注入的方式”一节中,我们主要使用了函数依赖,在此不再赘述。值得注意的是,FastAPI还支持注入lambda函数,如:

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()  

current_user = {"username": "faker"}  
@app.get("/")  
async def hello_world(user=Depends(lambda: current_user)): 
    return {"user": user, "message": "Hello World!"}

2)、类依赖,注入一个外部类

类依赖允许你将依赖项封装在一个类中,并通过类的实例化来提供依赖。
dependence.py文件

from abc import ABC, abstractmethod  
  
class IUserService(ABC):  
    @abstractmethod  
    def get_user_by_token(self, token: str = None):  
        pass  
  
class UserService(IUserService):  
  
    def get_user_by_token(self, token: str = None):  
        if token:  
            return {"username": "faker"}

main.py文件

from fastapi import FastAPI, Depends
from dependence import *

app = FastAPI()  

@app.get("/")  
async def hello_world(token: str, user_service: IUserService = Depends(UserService)):  # 路由函数注入点  
    return {"user": user_service.get_user_by_token(token), "message": "Hello World!"}

3、Depends() 函数原理

FastAPI 中,Depends() 是一个核心函数,用于实现依赖注入。它的原理主要依赖于 Python 的 类型提示函数调用的动态解析

Depends() 的实现原理解析

在 FastAPI 中,Depends() 的核心实现依赖于以下几个组件:
1) 依赖解析器
FastAPI 通过 Depends() 标记依赖,并在请求处理时识别它。
使用 Python 的类型提示inspect 模块 来分析依赖项的函数签名。

from inspect import signature

def example_dependency(name: str):
    return f"Hello {name}"

print(signature(example_dependency))
# 输出: (name: str)

FastAPI 利用这种方法分析依赖项函数的参数,判断它需要什么数据。

2) 执行依赖项
FastAPI 使用 依赖注入解析器 来执行依赖项。 它会:
• 判断依赖项是否是一个协程(async)或普通函数。
• 提取参数值并传入依赖项。
• 解析 yield 语句管理依赖项的生命周期。

import asyncio

async def async_dependency():
    await asyncio.sleep(1)
    return "Async Dependency Finished"

async def main():
    result = await async_dependency()
    print(result)

asyncio.run(main())

如果依赖项是异步的,FastAPI 会使用 await 进行处理。

3) 依赖嵌套与上下文管理
FastAPI 支持依赖项中调用其他依赖项(依赖嵌套)。
通过上下文管理(yield)处理资源清理。

from contextlib import asynccontextmanager

@asynccontextmanager
async def db_session():
    print("Connecting to database...")
    yield {"db": "Connected"}
    print("Closing database connection...")

async def get_db(session=Depends(db_session)):
    print(session)

使用 yield 提供依赖项,并在请求结束后执行清理任务。

FastAPI 的依赖注入解析过程

假设我们有以下代码:

@app.get("/users/{user_id}")
def get_user(user_id: int, db: Session = Depends(get_db)):
    user = db.query(User).filter(User.id == user_id).first()
    return user

FastAPI 的解析过程如下:

  1. 扫描路由函数:检测 Depends() 标记的依赖项。
  2. 解析依赖关系:识别所有需要的依赖项,包括嵌套依赖。
  3. 执行依赖项:根据依赖项的函数类型(同步或异步)执行它。
  4. 注入结果:将执行结果传递给视图函数。
  5. 生命周期管理:如果依赖项使用 yield,则在响应发送后执行清理逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值