1. 依赖注入(Dependency Injection)
1.1 什么是依赖注入
依赖注入(Dependency Injection, DI) 是一种设计模式,用于实现组件之间的解耦。它通过将依赖项(如数据库连接、配置、认证等)注入到需要它们的组件中,而不是组件自行创建或管理这些依赖项,从而提高代码的模块化、可测试性和可维护性。
1.2 FastAPI 中的依赖注入
FastAPI 内置了强大的依赖注入系统,允许您轻松地声明和管理依赖。通过 Depends
,您可以将共享的功能(如数据库连接、用户认证)注入到路由处理函数中。
1.3 基本使用示例
示例:简单的依赖注入
from fastapi import FastAPI, Depends
app = FastAPI()
# 定义一个依赖函数
def get_query(q: str = None):
return q
# 在路由中使用依赖
@app.get("/items/")
async def read_items(q: str = Depends(get_query)):
return {"q": q}
解释:
get_query
是一个依赖函数,它可以接收请求参数并返回处理后的结果。- 在
read_items
路由中,通过Depends(get_query)
注入了q
参数。
访问 /items/?q=fastapi
将返回 {"q": "fastapi"}
。
1.4 共享依赖
当多个路由需要相同的依赖时,可以将其封装在一个函数中,并在多个路由中使用 Depends
注入。
示例:共享依赖
from fastapi import FastAPI, Depends
app = FastAPI()
def common_parameters(q: str = None, skip: int = 0, limit: int = 10):
return {"q": q, "skip": skip, "limit": limit}
@app.get("/items/")
async def read_items(commons: dict = Depends(common_parameters)):
return commons
@app.get("/users/")
async def read_users(commons: dict = Depends(common_parameters)):
return commons
解释:
common_parameters
定义了一组通用的查询参数。read_items
和read_users
路由都依赖于common_parameters
,实现参数共享。
访问 /items/?q=fastapi&skip=5&limit=15
和 /users/?q=admin&skip=2&limit=5
将分别返回相应的参数。
1.5 依赖的嵌套
依赖可以嵌套,即一个依赖函数可以依赖于另一个依赖函数。
示例:依赖的嵌套
from fastapi import FastAPI, Depends
app = FastAPI()
def get_db():
db = "database_connection"
try:
yield db
finally:
db = None # 关闭数据库连接
def get_current_user(db = Depends(get_db)):
return {"user": "current_user", "db": db}
@app.get("/users/me")
async def read_current_user(current_user: dict = Depends(get_current_user)):
return current_user
解释:
get_db
是一个生成器函数,模拟数据库连接的获取和关闭。get_current_user
依赖于get_db
,获取当前用户的信息。read_current_user
路由依赖于get_current_user
,最终返回当前用户的信息。
访问 /users/me
将返回 {"user": "current_user", "db": "database_connection"}
。
1.6 类的依赖注入
可以使用类来组织依赖,使依赖更加结构化和可扩展。
示例:类的依赖注入
from fastapi import FastAPI, Depends
app = FastAPI()
class CommonQueryParams:
def __init__(self, q: str = None, skip: int = 0, limit: int = 10):
self.q = q
self.skip = skip
self.limit = limit
@app.get("/items/")
async def read_items(commons: CommonQueryParams = Depends()):
return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}
@app.get("/users/")
async def read_users(commons: CommonQueryParams = Depends()):
return {"q": commons.q, "skip": commons.skip, "limit": commons.limit}
解释:
CommonQueryParams
类封装了通用的查询参数。- 在路由中,通过
Depends()
自动实例化并注入CommonQueryParams
实例。
访问 /items/?q=fastapi&skip=5&limit=15
和 /users/?q=admin&skip=2&limit=5
将分别返回相应的参数。
1.7 实践示例:数据库连接
让我们通过一个更实际的示例,展示如何在 FastAPI 中使用依赖注入管理数据库连接。假设我们使用 SQLAlchemy 作为 ORM。
步骤:
-
安装依赖
pip install fastapi sqlalchemy databases pydantic uvicorn
-
项目结构
project/ ├── main.py ├── models.py ├── schemas.py └── database.py
-
定义数据库配置和依赖
database.py
from sqlalchemy import create_engine, MetaData from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import sessionmaker from contextlib import contextmanager DATABASE_URL = "sqlite:///./test.db" # 使用 SQLite 数据库 engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False}) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) Base = declarative_base() metadata = MetaData() @contextmanager def get_db(): db = SessionLocal() try: yield db finally: db.close()
-
定义数据模型
models.py
from sqlalchemy import Column, Integer, String from database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) name = Column(String, index=True) email = Column(String, unique=True, index=True)
-
定义 Pydantic 模型
schemas.py
from pydantic import BaseModel class UserBase(BaseModel): name: str email: str class UserCreate(UserBase): pass class UserResponse(UserBase): id: int class Config: orm_mode = True
-
创建 FastAPI 应用并定义路由
main.py
from fastapi import FastAPI, Depends, HTTPException, status from sqlalchemy.orm import Session from typing import List from database import engine, Base, get_db from models import User from schemas import UserCreate, UserResponse app = FastAPI() # 创建数据库表 Base.metadata.create_all(bind=engine) @app.post("/users/", response_model=UserResponse) async def create_user(user: UserCreate, db: Session = Depends(get_db)): # 检查用户是否已存在 db_user = db.query(User).filter(User.email == user.email).first() if db_user: raise HTTPException(status_code=400, detail="Email already registered") new_user = User(name=user.name, email=user.email) db.add(new_user) db.commit() db.refresh(new_user) return new_user @app.get("/users/", response_model=List[UserResponse]) async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)): users = db.query(User).offset(skip).limit(limit).all() return users @app.get("/users/{user_id}", response_model=UserResponse) async def read_user(user_id: int, db: Session = Depends(get_db)): user = db.query(User).filter(User.id == user_id).first() if user is None: raise HTTPException(status_code=404, detail="User not found") return user
-
启动应用
在终端中运行:
uvicorn main:app --reload
-
测试 API
-
创建用户
curl -X POST "http://127.0.0.1:8000/users/" -H "Content-Type: application/json" -d '{"name": "Alice", "email": "alice@example.com"}'
响应:
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
-
获取用户列表
curl "http://127.0.0.1:8000/users/"
响应:
[ { "id": 1, "name": "Alice", "email": "alice@example.com" } ]
-
获取单个用户
curl "http://127.0.0.1:8000/users/1"
响应:
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
-
学习建议
- 理解
Depends
的工作原理:深入理解 FastAPI 的依赖注入系统,了解如何声明和管理依赖。 - 实践共享和嵌套依赖:在不同的项目中实践共享和嵌套依赖,提高代码的复用性和模块化。
- 依赖的生命周期管理:了解依赖的生命周期,特别是在使用生成器函数(如数据库连接)时的资源管理。
2. 回调机制(Callback Mechanism)
2.1 什么是回调机制
回调机制 通常用于异步任务的通知和状态更新。当一个任务在后台执行时,完成后需要通知客户端或触发某些操作。回调机制通过提供一个 URL(回调 URL),让服务器在任务完成后发送请求到该 URL,以通知任务的结果。
2.2 使用 FastAPI 的 BackgroundTasks
FastAPI 提供了 BackgroundTasks
,允许您在响应返回后在后台执行任务。这适用于简单的异步任务,但不适合需要复杂任务队列或分布式处理的场景。
示例:使用 BackgroundTasks 实现回调
from fastapi import FastAPI, BackgroundTasks, HTTPException
from pydantic import BaseModel
import requests
import time
app = FastAPI()
class TaskRequest(BaseModel):
data: str
callback_url: str
def process_task(data: str, callback_url: str):
try:
# 模拟任务处理
time.sleep(5) # 假设任务需要5秒
result = f"Processed data: {data}"
# 发送回调请求
payload = {"result": result}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
except Exception as e:
print(f"Failed to send callback: {e}")
@app.post("/start-task/")
async def start_task(task: TaskRequest, background_tasks: BackgroundTasks):
background_tasks.add_task(process_task, task.data, task.callback_url)
return {"message": "Task started"}
解释:
TaskRequest
定义了请求体,包括data
和callback_url
。process_task
是在后台执行的任务函数,处理数据并发送回调请求。- 在
start_task
路由中,通过BackgroundTasks
添加process_task
任务。
注意:
BackgroundTasks
适用于简单的后台任务,但无法处理复杂的任务队列或分布式任务处理。- 若需要更强大的异步任务处理,建议使用 Celery。
2.3 使用 Celery 实现异步任务和回调
Celery 是一个强大的分布式任务队列,适用于需要复杂任务调度、分布式处理和可靠性的场景。
步骤:
-
安装 Celery 和 Redis
pip install celery redis
-
项目结构
project/ ├── main.py ├── celery_app.py ├── tasks.py ├── schemas.py └── dependencies.py
-
配置 Celery
celery_app.py
from celery import Celery celery = Celery( "tasks", broker="redis://localhost:6379/0", backend="redis://localhost:6379/0" )
-
定义 Celery 任务
tasks.py
from celery_app import celery import requests import time @celery.task def process_task(data: str, callback_url: str): try: # 模拟任务处理 time.sleep(10) # 假设任务需要10秒 result = f"Processed data: {data}" # 发送回调请求 payload = {"result": result} response = requests.post(callback_url, json=payload, timeout=5) response.raise_for_status() except Exception as e: print(f"Failed to send callback: {e}") raise e
-
定义 FastAPI 路由
main.py
from fastapi import FastAPI, HTTPException from pydantic import BaseModel from tasks import process_task from typing import Dict app = FastAPI() class TaskRequest(BaseModel): data: str callback_url: str @app.post("/start-task/") async def start_task(task: TaskRequest): """ 启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。 """ task_instance = process_task.delay(task.data, task.callback_url) return {"task_id": task_instance.id, "message": "Task started"} @app.post("/task-callback/") async def task_callback(payload: Dict[str, str]): """ 处理来自异步任务的回调请求。 """ result = payload.get("result") if not result: raise HTTPException(status_code=400, detail="Missing result") # 处理回调结果,例如更新数据库状态 print(f"Received callback with result: {result}") return {"message": "Callback received"}
-
启动 Celery Worker 和 FastAPI
-
启动 Celery Worker
在终端中运行:
celery -A tasks worker --loglevel=info
-
启动 FastAPI 应用
在另一个终端中运行:
uvicorn main:app --reload
-
-
测试流程
-
启动任务
发送一个 POST 请求到
/start-task/
,包含data
和callback_url
。示例请求:
curl -X POST "http://127.0.0.1:8000/start-task/" \ -H "Content-Type: application/json" \ -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
响应:
{ "task_id": "some_unique_id", "message": "Task started" }
-
任务处理与回调
- Celery Worker 接收到任务,开始处理。
- 处理完成后,Worker 通过
callback_url
发送 POST 请求,携带结果。 - FastAPI 应用接收到回调请求,处理结果。
回调请求示例:
{ "result": "Processed data: Sample Data" }
回调响应:
{ "message": "Callback received" }
-
2.4 实践示例:异步任务与回调
以下是一个完整的示例,展示如何使用 FastAPI、Celery 和 Redis 实现异步任务处理和回调机制。
项目结构:
project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
└── dependencies.py
1. 安装依赖
pip install fastapi uvicorn celery redis pydantic requests
2. 配置 Celery
celery_app.py
from celery import Celery
celery = Celery(
"tasks",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/0"
)
3. 定义 Celery 任务
tasks.py
from celery_app import celery
import requests
import time
@celery.task
def process_task(data: str, callback_url: str):
try:
# 模拟任务处理
print(f"Processing data: {data}")
time.sleep(10) # 模拟耗时任务
result = f"Processed data: {data}"
# 发送回调请求
payload = {"result": result}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
print("Callback sent successfully.")
except Exception as e:
print(f"Failed to send callback: {e}")
raise e
4. 定义 Pydantic 模型
schemas.py
from pydantic import BaseModel
class TaskRequest(BaseModel):
data: str
callback_url: str
class TaskResponse(BaseModel):
task_id: str
message: str
5. 定义依赖
dependencies.py
from fastapi import Depends, HTTPException, status
from typing import Generator
def get_db():
# 模拟数据库连接
db = "database_connection"
try:
yield db
finally:
db = None # 关闭数据库连接
def verify_token(token: str = None):
# 简单的 token 验证
if token != "expected_token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or missing token"
)
return token
6. 定义 FastAPI 路由
main.py
from fastapi import FastAPI, Depends, HTTPException, status
from pydantic import BaseModel
from typing import Dict
from tasks import process_task
from schemas import TaskRequest, TaskResponse
from dependencies import get_db, verify_token
app = FastAPI()
@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: str = Depends(get_db)):
"""
启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
"""
# 启动 Celery 任务
task_instance = process_task.delay(task.data, task.callback_url)
return {"task_id": task_instance.id, "message": "Task started"}
@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
"""
处理来自异步任务的回调请求。
"""
result = payload.get("result")
if not result:
raise HTTPException(status_code=400, detail="Missing result")
# 处理回调结果,例如更新数据库状态
print(f"Received callback with result: {result}")
return {"message": "Callback received"}
7. 启动 Celery Worker 和 FastAPI
-
启动 Redis
确保 Redis 服务已启动。如果未安装 Redis,可以参考以下安装步骤:
在 macOS 上使用 Homebrew 安装:
brew install redis brew services start redis
在 Ubuntu 上安装:
sudo apt update sudo apt install redis-server sudo systemctl enable redis-server.service sudo systemctl start redis-server.service
-
启动 Celery Worker
在项目根目录中运行:
celery -A tasks worker --loglevel=info
您应该会看到 Celery Worker 启动并等待任务。
-
启动 FastAPI 应用
在另一个终端中运行:
uvicorn main:app --reload
FastAPI 应用将启动在
http://127.0.0.1:8000
。
8. 测试流程
-
发送任务请求
使用
curl
或 Postman 发送一个 POST 请求到/start-task/
,包含data
和callback_url
,并添加正确的token
。示例请求:
curl -X POST "http://127.0.0.1:8000/start-task/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer expected_token" \ -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
响应:
{ "task_id": "some_unique_id", "message": "Task started" }
-
任务处理
- Celery Worker 接收到任务,开始处理数据。
- 处理完成后,Worker 向指定的
callback_url
发送 POST 请求,包含处理结果。
-
回调处理
FastAPI 应用接收到回调请求后,处理结果并返回确认消息。
回调请求示例:
{ "result": "Processed data: Sample Data" }
回调响应:
{ "message": "Callback received" }
-
查看 Celery Worker 日志
Celery Worker 终端将显示任务处理和回调发送的日志信息。
完整测试流程:
- 启动 Celery Worker 和 FastAPI 应用
- 发送任务请求
- 等待任务处理完成
- 查看回调处理结果
2.4 实践示例:异步任务与回调
为了帮助您更好地理解和实践,以下是一个完整的示例项目,展示了如何使用 FastAPI、Celery 和 Redis 实现异步任务处理和回调机制。
项目结构:
project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
└── requirements.txt
1. 创建 requirements.txt
fastapi
uvicorn
celery
redis
pydantic
requests
2. 安装依赖
在项目根目录中运行:
pip install -r requirements.txt
3. 配置 Celery
celery_app.py
from celery import Celery
celery = Celery(
"tasks",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/0"
)
4. 定义 Celery 任务
tasks.py
from celery_app import celery
import requests
import time
@celery.task
def process_task(data: str, callback_url: str):
try:
# 模拟任务处理
print(f"Processing data: {data}")
time.sleep(10) # 模拟耗时任务
result = f"Processed data: {data}"
# 发送回调请求
payload = {"result": result}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
print("Callback sent successfully.")
except Exception as e:
print(f"Failed to send callback: {e}")
raise e
5. 定义 Pydantic 模型
schemas.py
from pydantic import BaseModel
class TaskRequest(BaseModel):
data: str
callback_url: str
class TaskResponse(BaseModel):
task_id: str
message: str
6. 定义依赖
dependencies.py
from fastapi import Depends, HTTPException, status
from typing import Generator
def get_db():
# 模拟数据库连接
db = "database_connection"
try:
yield db
finally:
db = None # 关闭数据库连接
def verify_token(token: str = None):
# 简单的 token 验证
if token != "expected_token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or missing token"
)
return token
7. 定义 FastAPI 路由
main.py
from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict
from tasks import process_task
from schemas import TaskRequest, TaskResponse
from dependencies import get_db, verify_token
app = FastAPI()
@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: str = Depends(get_db)):
"""
启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
"""
# 启动 Celery 任务
task_instance = process_task.delay(task.data, task.callback_url)
return {"task_id": task_instance.id, "message": "Task started"}
@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
"""
处理来自异步任务的回调请求。
"""
result = payload.get("result")
if not result:
raise HTTPException(status_code=400, detail="Missing result")
# 处理回调结果,例如更新数据库状态
print(f"Received callback with result: {result}")
return {"message": "Callback received"}
8. 启动服务
-
启动 Redis
确保 Redis 服务正在运行。如果未安装 Redis,请参考前面的安装步骤。
-
启动 Celery Worker
在项目根目录中运行:
celery -A tasks worker --loglevel=info
您将看到类似如下的输出,表示 Celery Worker 已启动并在等待任务:
[2023-XX-XX 12:34:56,789: INFO/MainProcess] Connected to redis://localhost:6379/0 [2023-XX-XX 12:34:56,790: INFO/MainProcess] mingle: searching for neighbors [2023-XX-XX 12:34:57,800: INFO/MainProcess] mingle: sync with 1 nodes [2023-XX-XX 12:34:57,900: INFO/MainProcess] celery@hostname ready.
-
启动 FastAPI 应用
在另一个终端中运行:
uvicorn main:app --reload
您将看到类似如下的输出,表示 FastAPI 应用已启动:
INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) INFO: Started reloader process [28716] using statreload INFO: Started server process [28718] INFO: Waiting for application startup. INFO: Application startup complete.
9. 测试综合流程
-
发送任务请求
使用
curl
、Postman 或任何 HTTP 客户端发送一个 POST 请求到/start-task/
,包含data
和callback_url
,并提供正确的token
。示例请求:
curl -X POST "http://127.0.0.1:8000/start-task/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer expected_token" \ -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
响应:
{ "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d", "message": "Task started" }
-
等待任务处理
Celery Worker 将接收到任务并开始处理。您可以在 Celery Worker 的终端中看到任务的处理日志。
Celery Worker 输出示例:
[2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
-
回调处理
当任务完成后,Celery Worker 会向指定的
callback_url
发送一个 POST 请求,包含任务的结果。回调请求示例:
{ "result": "Processed data: Sample Data" }
FastAPI 应用接收到回调请求后,执行
task_callback
路由处理函数,并返回确认消息。回调响应:
{ "message": "Callback received" }
FastAPI 应用控制台输出示例:
Received callback with result: Processed data: Sample Data
学习建议
- 理解 Celery 的工作原理:了解 Celery 的任务队列、消息代理(如 Redis)、任务执行和结果存储机制。
- 学习任务调度:掌握如何调度定时任务、周期性任务以及处理任务失败的策略。
- 深入研究回调机制:了解如何设计和实现更复杂的回调逻辑,如重试机制、回调验证等。
- 监控与管理:学习如何监控 Celery Worker 的状态,使用工具如 Flower 进行任务监控和管理。
3. 综合示例
为了更好地理解依赖注入和回调机制,以下是一个综合示例项目,展示如何将这些概念结合起来构建一个功能完整的 FastAPI 应用。
3.1 项目结构
project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
├── models.py
├── database.py
└── requirements.txt
3.2 定义 Pydantic 模型
schemas.py
from pydantic import BaseModel
from typing import Optional
class TaskRequest(BaseModel):
data: str
callback_url: str
class TaskResponse(BaseModel):
task_id: str
message: str
class UserBase(BaseModel):
name: str
email: str
class UserCreate(UserBase):
pass
class UserResponse(UserBase):
id: int
class Config:
orm_mode = True
3.3 配置依赖
dependencies.py
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Generator
from database import SessionLocal
from models import User
def get_db() -> Generator:
db = SessionLocal()
try:
yield db
finally:
db.close()
def verify_token(token: str = None):
# 简单的 token 验证
if token != "expected_token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or missing token"
)
return token
3.4 定义数据库模型
database.py
from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test.db" # 使用 SQLite 数据库
engine = create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
metadata = MetaData()
models.py
from sqlalchemy import Column, Integer, String
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
3.5 定义 Celery 任务
tasks.py
from celery_app import celery
import requests
import time
@celery.task
def process_task(data: str, callback_url: str):
try:
# 模拟任务处理
print(f"Processing data: {data}")
time.sleep(10) # 模拟耗时任务
result = f"Processed data: {data}"
# 发送回调请求
payload = {"result": result}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
print("Callback sent successfully.")
except Exception as e:
print(f"Failed to send callback: {e}")
raise e
3.6 定义 FastAPI 路由
main.py
from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict, List
from tasks import process_task
from schemas import TaskRequest, TaskResponse, UserCreate, UserResponse
from dependencies import get_db, verify_token
from models import User
from database import engine, Base
from sqlalchemy.orm import Session
app = FastAPI()
# 创建数据库表
Base.metadata.create_all(bind=engine)
@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)):
"""
启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
"""
# 启动 Celery 任务
task_instance = process_task.delay(task.data, task.callback_url)
return {"task_id": task_instance.id, "message": "Task started"}
@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
"""
处理来自异步任务的回调请求。
"""
result = payload.get("result")
if not result:
raise HTTPException(status_code=400, detail="Missing result")
# 处理回调结果,例如更新数据库状态
print(f"Received callback with result: {result}")
return {"message": "Callback received"}
@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
"""
创建一个新用户。
"""
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
new_user = User(name=user.name, email=user.email)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
@app.get("/users/", response_model=List[UserResponse])
async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
"""
获取用户列表。
"""
users = db.query(User).offset(skip).limit(limit).all()
return users
@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
"""
获取指定用户的信息。
"""
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
3.7 启动 Celery Worker 和 FastAPI
-
启动 Celery Worker
在项目根目录中运行:
celery -A tasks worker --loglevel=info
-
启动 FastAPI 应用
在另一个终端中运行:
uvicorn main:app --reload
3.8 测试综合流程
-
创建用户
发送一个 POST 请求到
/users/
,创建一个新用户。示例请求:
curl -X POST "http://127.0.0.1:8000/users/" \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'
响应:
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
-
启动异步任务
发送一个 POST 请求到
/start-task/
,启动一个异步任务,并提供回调 URL。示例请求:
curl -X POST "http://127.0.0.1:8000/start-task/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer expected_token" \ -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/"}'
响应:
{ "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d", "message": "Task started" }
-
任务处理与回调
-
Celery Worker 将接收到任务并开始处理。您可以在 Celery Worker 终端中看到任务的日志:
[2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
-
处理完成后,Celery Worker 会向指定的
callback_url
发送 POST 请求,包含任务结果。 -
FastAPI 应用接收到回调请求后,执行
task_callback
路由处理函数,并返回确认消息。回调请求示例:
{ "result": "Processed data: Sample Data" }
回调响应:
{ "message": "Callback received" }
-
FastAPI 应用控制台将显示:
Received callback with result: Processed data: Sample Data
-
3.9 优化与扩展
3.9.1 添加任务状态查询
为了让客户端能够查询任务状态,可以添加一个路由来获取任务状态。
修改 main.py
from celery.result import AsyncResult
@app.get("/task-status/{task_id}")
async def get_task_status(task_id: str):
"""
获取任务状态。
"""
task_result = AsyncResult(task_id)
if task_result.state == 'PENDING':
response = {"status": "Pending"}
elif task_result.state == 'SUCCESS':
response = {"status": "Success", "result": task_result.result}
elif task_result.state == 'FAILURE':
response = {"status": "Failure", "reason": str(task_result.info)}
else:
response = {"status": task_result.state}
return response
测试任务状态查询
-
启动任务
同上。
-
查询任务状态
使用任务 ID 查询任务状态。
示例请求:
curl "http://127.0.0.1:8000/task-status/e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d"
响应:
-
当任务在处理中:
{ "status": "Pending" }
-
当任务完成:
{ "status": "Success", "result": "Processed data: Sample Data" }
-
当任务失败:
{ "status": "Failure", "reason": "Error message" }
-
3.9.2 实现重试机制
为了提高系统的鲁棒性,可以在 Celery 任务中实现重试机制。
修改 tasks.py
from celery import Celery, shared_task
from celery_app import celery
import requests
import time
@celery.task(bind=True, max_retries=3)
def process_task(self, data: str, callback_url: str):
try:
# 模拟任务处理
print(f"Processing data: {data}")
time.sleep(10) # 模拟耗时任务
result = f"Processed data: {data}"
# 发送回调请求
payload = {"result": result}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
print("Callback sent successfully.")
except requests.exceptions.RequestException as e:
print(f"Failed to send callback: {e}, retrying...")
try:
self.retry(exc=e, countdown=5)
except MaxRetriesExceededError:
print("Max retries exceeded. Failed to send callback.")
raise e
except Exception as e:
print(f"Unexpected error: {e}")
raise e
解释:
- 使用
bind=True
允许任务访问其自身(self
)。 - 设置
max_retries=3
限制最大重试次数。 - 在捕获到
requests.exceptions.RequestException
时,调用self.retry
进行重试,延迟 5 秒后再次尝试。 - 如果超过最大重试次数,则记录失败并抛出异常。
3.9.3 实现回调验证
为了确保回调请求的安全性,可以在回调请求中添加验证机制,例如签名验证或预共享密钥。
示例:简单的回调验证
-
在任务请求中添加验证参数
修改
TaskRequest
模型和任务启动逻辑。schemas.py
class TaskRequest(BaseModel): data: str callback_url: str callback_token: str # 添加回调验证 token
main.py
@app.post("/start-task/", response_model=TaskResponse) async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)): """ 启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。 """ # 启动 Celery 任务,传递 callback_token task_instance = process_task.delay(task.data, task.callback_url, task.callback_token) return {"task_id": task_instance.id, "message": "Task started"}
-
修改 Celery 任务以包含回调 token
tasks.py
from celery_app import celery import requests import time @celery.task(bind=True, max_retries=3) def process_task(self, data: str, callback_url: str, callback_token: str): try: # 模拟任务处理 print(f"Processing data: {data}") time.sleep(10) # 模拟耗时任务 result = f"Processed data: {data}" # 发送回调请求,包含 token payload = {"result": result, "token": callback_token} response = requests.post(callback_url, json=payload, timeout=5) response.raise_for_status() print("Callback sent successfully.") except requests.exceptions.RequestException as e: print(f"Failed to send callback: {e}, retrying...") try: self.retry(exc=e, countdown=5) except MaxRetriesExceededError: print("Max retries exceeded. Failed to send callback.") raise e except Exception as e: print(f"Unexpected error: {e}") raise e
-
在回调处理函数中验证 token
main.py
@app.post("/task-callback/") async def task_callback(payload: Dict[str, str]): """ 处理来自异步任务的回调请求。 """ result = payload.get("result") token = payload.get("token") if not result or not token: raise HTTPException(status_code=400, detail="Missing result or token") # 验证 token if token != "expected_callback_token": raise HTTPException(status_code=403, detail="Invalid callback token") # 处理回调结果,例如更新数据库状态 print(f"Received callback with result: {result}") return {"message": "Callback received"}
解释:
- 在任务请求中添加
callback_token
,用于验证回调请求的合法性。 - 在 Celery 任务中,将
callback_token
一同发送到回调 URL。 - 在回调处理函数中,验证接收到的
callback_token
,确保回调请求来自可信来源。
学习建议
- 深入理解 Celery:学习 Celery 的高级功能,如任务调度、任务链、任务结果存储等。
- 安全性:设计回调机制时,考虑安全因素,如回调验证、加密传输等,确保系统的安全性。
- 错误处理和重试策略:设计健壮的错误处理和重试机制,提高系统的可靠性和稳定性。
- 监控与管理:使用监控工具(如 Flower)监控 Celery Worker 的状态和任务执行情况,及时发现和解决问题。
4. 综合示例
以下是一个完整的示例项目,结合依赖注入和回调机制,展示如何构建一个功能全面的 FastAPI 应用。
4.1 项目结构
project/
├── main.py
├── celery_app.py
├── tasks.py
├── schemas.py
├── dependencies.py
├── models.py
├── database.py
└── requirements.txt
4.2 完整代码
requirements.txt
fastapi
uvicorn
celery
redis
pydantic
requests
sqlalchemy
database.py
from sqlalchemy import create_engine, MetaData
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
DATABASE_URL = "sqlite:///./test.db" # 使用 SQLite 数据库
engine = create_engine(
DATABASE_URL, connect_args={"check_same_thread": False}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
metadata = MetaData()
models.py
from sqlalchemy import Column, Integer, String
from database import Base
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, index=True)
email = Column(String, unique=True, index=True)
schemas.py
from pydantic import BaseModel
from typing import Optional
class TaskRequest(BaseModel):
data: str
callback_url: str
callback_token: str # 添加回调验证 token
class TaskResponse(BaseModel):
task_id: str
message: str
class UserBase(BaseModel):
name: str
email: str
class UserCreate(UserBase):
pass
class UserResponse(UserBase):
id: int
class Config:
orm_mode = True
dependencies.py
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from typing import Generator
from database import SessionLocal
from models import User
def get_db() -> Generator:
db = SessionLocal()
try:
yield db
finally:
db.close()
def verify_token(token: str = None):
# 简单的 token 验证
if token != "expected_token":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Invalid or missing token"
)
return token
celery_app.py
from celery import Celery
celery = Celery(
"tasks",
broker="redis://localhost:6379/0",
backend="redis://localhost:6379/0"
)
tasks.py
from celery_app import celery
import requests
import time
@celery.task(bind=True, max_retries=3)
def process_task(self, data: str, callback_url: str, callback_token: str):
try:
# 模拟任务处理
print(f"Processing data: {data}")
time.sleep(10) # 模拟耗时任务
result = f"Processed data: {data}"
# 发送回调请求,包含 token
payload = {"result": result, "token": callback_token}
response = requests.post(callback_url, json=payload, timeout=5)
response.raise_for_status()
print("Callback sent successfully.")
except requests.exceptions.RequestException as e:
print(f"Failed to send callback: {e}, retrying...")
try:
self.retry(exc=e, countdown=5)
except self.MaxRetriesExceededError:
print("Max retries exceeded. Failed to send callback.")
raise e
except Exception as e:
print(f"Unexpected error: {e}")
raise e
main.py
from fastapi import FastAPI, Depends, HTTPException, status
from typing import Dict, List
from tasks import process_task
from schemas import TaskRequest, TaskResponse, UserCreate, UserResponse
from dependencies import get_db, verify_token
from models import User
from database import engine, Base
from sqlalchemy.orm import Session
app = FastAPI()
# 创建数据库表
Base.metadata.create_all(bind=engine)
@app.post("/start-task/", response_model=TaskResponse)
async def start_task(task: TaskRequest, token: str = Depends(verify_token), db: Session = Depends(get_db)):
"""
启动一个异步任务,并在任务完成后通过回调 URL 通知客户端。
"""
# 启动 Celery 任务
task_instance = process_task.delay(task.data, task.callback_url, task.callback_token)
return {"task_id": task_instance.id, "message": "Task started"}
@app.post("/task-callback/")
async def task_callback(payload: Dict[str, str]):
"""
处理来自异步任务的回调请求。
"""
result = payload.get("result")
token = payload.get("token")
if not result or not token:
raise HTTPException(status_code=400, detail="Missing result or token")
# 验证 token
if token != "expected_callback_token":
raise HTTPException(status_code=403, detail="Invalid callback token")
# 处理回调结果,例如更新数据库状态
print(f"Received callback with result: {result}")
return {"message": "Callback received"}
@app.post("/users/", response_model=UserResponse)
async def create_user(user: UserCreate, db: Session = Depends(get_db)):
"""
创建一个新用户。
"""
db_user = db.query(User).filter(User.email == user.email).first()
if db_user:
raise HTTPException(status_code=400, detail="Email already registered")
new_user = User(name=user.name, email=user.email)
db.add(new_user)
db.commit()
db.refresh(new_user)
return new_user
@app.get("/users/", response_model=List[UserResponse])
async def read_users(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
"""
获取用户列表。
"""
users = db.query(User).offset(skip).limit(limit).all()
return users
@app.get("/users/{user_id}", response_model=UserResponse)
async def read_user(user_id: int, db: Session = Depends(get_db)):
"""
获取指定用户的信息。
"""
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise HTTPException(status_code=404, detail="User not found")
return user
8. 启动服务
-
启动 Redis
确保 Redis 服务正在运行。
-
启动 Celery Worker
在项目根目录中运行:
celery -A tasks worker --loglevel=info
-
启动 FastAPI 应用
在另一个终端中运行:
uvicorn main:app --reload
9. 测试综合流程
-
创建用户
发送一个 POST 请求到
/users/
,创建一个新用户。示例请求:
curl -X POST "http://127.0.0.1:8000/users/" \ -H "Content-Type: application/json" \ -d '{"name": "Alice", "email": "alice@example.com"}'
响应:
{ "id": 1, "name": "Alice", "email": "alice@example.com" }
-
启动异步任务
发送一个 POST 请求到
/start-task/
,包含data
、callback_url
和callback_token
。示例请求:
curl -X POST "http://127.0.0.1:8000/start-task/" \ -H "Content-Type: application/json" \ -H "Authorization: Bearer expected_token" \ -d '{"data": "Sample Data", "callback_url": "http://127.0.0.1:8000/task-callback/", "callback_token": "expected_callback_token"}'
响应:
{ "task_id": "e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d", "message": "Task started" }
-
任务处理与回调
-
Celery Worker 将接收到任务并开始处理,您可以在 Celery Worker 终端中看到日志:
[2023-XX-XX 12:35:00,123: INFO/Worker-1] Processing data: Sample Data [2023-XX-XX 12:35:10,456: INFO/Worker-1] Callback sent successfully.
-
处理完成后,Worker 会向
callback_url
发送 POST 请求,包含处理结果和回调 token。 -
FastAPI 应用接收到回调请求后,执行
task_callback
路由处理函数,并返回确认消息。回调请求示例:
{ "result": "Processed data: Sample Data", "token": "expected_callback_token" }
回调响应:
{ "message": "Callback received" }
-
FastAPI 应用控制台将显示:
Received callback with result: Processed data: Sample Data
-
-
查询任务状态(可选)
您可以添加一个路由来查询任务状态。
修改
main.py
from celery.result import AsyncResult @app.get("/task-status/{task_id}") async def get_task_status(task_id: str): """ 获取任务状态。 """ task_result = AsyncResult(task_id) if task_result.state == 'PENDING': response = {"status": "Pending"} elif task_result.state == 'SUCCESS': response = {"status": "Success", "result": task_result.result} elif task_result.state == 'FAILURE': response = {"status": "Failure", "reason": str(task_result.info)} else: response = {"status": task_result.state} return response
测试任务状态查询
使用任务 ID 查询任务状态。
示例请求:
curl "http://127.0.0.1:8000/task-status/e2f4f1d3-3e6f-4cde-8b9c-3b3b7e7f1c9d"
响应:
-
当任务在处理中:
{ "status": "Pending" }
-
当任务完成:
{ "status": "Success", "result": "Processed data: Sample Data" }
-
当任务失败:
{ "status": "Failure", "reason": "Error message" }
-
4. 总结
通过本节的详细讲解和实践示例,您已经了解了如何在 FastAPI 中实现依赖注入和回调机制。以下是本节的关键点总结:
- 依赖注入(Dependency Injection)
- 基础:使用
Depends
声明依赖,管理共享资源如数据库连接、认证等。 - 共享依赖:多个路由可以共享同一个依赖函数或类。
- 嵌套依赖:依赖函数可以依赖于其他依赖函数,实现复杂的依赖关系。
- 类的依赖注入:使用类来组织依赖,提升代码的结构化和可扩展性。
- 实践:通过实际项目实现数据库连接管理,提升代码的模块化和可维护性。
- 基础:使用
- 回调机制(Callback Mechanism)
- 基础:通过回调 URL 通知客户端异步任务的完成状态和结果。
- 使用
BackgroundTasks
:适用于简单的后台任务处理,但不适合复杂的任务队列。 - 使用 Celery:适用于复杂的异步任务处理和分布式任务队列,提供可靠的任务调度和重试机制。
- 安全性:设计回调机制时,考虑回调请求的验证和安全性,防止未经授权的请求。
- 实践:通过实际项目实现异步任务的处理和回调通知,提升系统的响应能力和用户体验。
- 综合应用
- 项目结构:组织项目代码,确保模块化和可维护性。
- 实际操作:通过完整的示例项目,理解和应用依赖注入和回调机制,构建一个功能全面的 FastAPI 应用。
- 扩展与优化:添加任务状态查询、重试机制和回调验证,提升系统的鲁棒性和安全性。
持续学习的建议
- 深入文档:阅读 FastAPI 官方文档 中关于依赖注入和异步任务的章节,了解更多高级特性和最佳实践。
- 学习 Celery:深入学习 Celery 的更多功能,如任务链、任务组、定时任务等,提升异步任务处理能力。
- 实践项目:通过实际项目应用所学知识,解决真实问题,积累经验。
- 安全性与优化:学习如何在回调机制中实现更高级的安全措施,如签名验证、加密传输等,同时优化任务处理性能。
- 监控与调试:掌握如何监控 Celery Worker 的状态,使用工具如 Flower 监控任务执行情况,及时发现和解决问题。
通过系统性的学习和持续的实践,您将能够熟练掌握 FastAPI 的依赖注入和回调机制,构建高效、可靠且可维护的后端服务。
祝您在 Python 后端开发的道路上取得更大的进步和成就!