Python FastAPI系列: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 的解析过程如下:
- 扫描路由函数:检测 Depends() 标记的依赖项。
- 解析依赖关系:识别所有需要的依赖项,包括嵌套依赖。
- 执行依赖项:根据依赖项的函数类型(同步或异步)执行它。
- 注入结果:将执行结果传递给视图函数。
- 生命周期管理:如果依赖项使用 yield,则在响应发送后执行清理逻辑。