文章目录
APIRouter的角色
在 FastAPI 中,
APIRouter
是一个用于定义和管理一组相关路由的类。它允许将应用的路由模块化,并提供了一种灵活的方式来组织代码。
方法重写的概念
Python 提供了面向对象编程的能力,包括继承和方法重写。通过继承一个类,并重写其方法,可以改变或扩展该类的行为。在自定义路由类中,通过继承
APIRouter
并重写其方法来实现自定义逻辑。
核心方法 api_route
APIRouter
中的api_route
方法是所有路由注册方法的底层实现,包括get
,post
,put
,delete
等 HTTP 方法。通过重写api_route
方法,可以在所有类型的路由注册时应用自定义逻辑。
实现自定义路由类的步骤
继承 APIRouter
首先,通过创建一个新的类继承
APIRouter
。这允许在保留APIRouter
原有功能的基础上,添加自定义的逻辑。
from fastapi import APIRouter
class CustomRouter(APIRouter):
pass
重写 api_route
方法
重写
api_route
方法,可以在每次路由注册时应用自定义逻辑。示例中,选择在没有tags
参数时,为路由添加一个默认标签。
class CustomRouter(APIRouter):
def api_route(self, path: str, **kwargs):
if "tags" not in kwargs:
kwargs["tags"] = ["default"]
return super().api_route(path, **kwargs)
参数解释:
path: str
: 路由路径。kwargs
: 其他关键字参数,包括 HTTP 方法、响应模型、标签等。
功能逻辑:
- 检查
kwargs
中是否包含tags
参数。 - 如果没有
tags
,则设置一个默认值["default"]
。 - 调用父类的
api_route
方法以保留APIRouter
的其他功能。
实例化并使用自定义路由类
创建
CustomRouter
的实例,并像使用普通APIRouter
一样使用它来定义路由。
router = CustomRouter()
@router.get("/custom-route/")
def custom_route():
return {"message": "This is a custom route"}
@router.get("/custom-route/")
: 定义一个 GET 请求的路由。通过使用CustomRouter
实例,这个路由会自动应用在api_route
方法中定义的自定义逻辑。
将自定义路由类挂载到 FastAPI 应用
将自定义的 router 实例挂载到 FastAPI 应用中,和普通
APIRouter
一样:
from fastapi import FastAPI
app = FastAPI()
app.include_router(router)
完整示例代码
import os
from fastapi import FastAPI, APIRouter
import uvicorn
# 自定义路由类
class CustomRouter(APIRouter):
def api_route(self, path: str, **kwargs):
# 如果未定义tags,则设置默认标签
kwargs["tags"] = ["default"]
return super().api_route(path, **kwargs)
# 创建自定义路由实例
router = CustomRouter()
# 定义一个路由
@router.get("/custom-route/")
def custom_route():
return {"message": "This is a custom route"}
# 创建FastAPI应用并挂载自定义路由
app = FastAPI()
app.include_router(router)
if __name__ == "__main__":
uvicorn.run(
f"{os.path.basename(__file__).split('.')[0]}:app",
host="127.0.0.1",
port=8000,
reload=True,
)
测试
全面示例
自定义路由_全面.py
import os
from fastapi import FastAPI, APIRouter, Depends, HTTPException, Request
from fastapi.responses import JSONResponse
from fastapi.security import HTTPBasic, HTTPBasicCredentials
import logging
import uvicorn
# 设置基础依赖和中间件
security = HTTPBasic()
# 自定义的凭证验证函数
def verify_credentials(credentials: HTTPBasicCredentials = Depends(security)):
correct_username = "admin"
correct_password = "password"
if not (
credentials.username == correct_username
and credentials.password == correct_password
):
# 如果用户名或密码不匹配,则抛出401异常
raise HTTPException(status_code=401, detail="Invalid credentials")
return credentials.username
# 日志记录函数
async def log_request(request: Request):
logging.info(f"Handling request: {request.method} {request.url}")
# 自定义路由类
class CustomRouter(APIRouter):
def api_route(self, path: str, **kwargs):
if not kwargs.get("tags"): # 检查是否指定了标签
kwargs["tags"] = ["default"] # 如果没有指定标签,设置默认标签为 "default"
if not kwargs.get("dependencies"): # 检查是否指定了依赖
kwargs["dependencies"] = [
Depends(verify_credentials)
] # 如果没有指定依赖,添加默认的凭证验证依赖
else:
kwargs["dependencies"].append(
Depends(verify_credentials)
) # 如果已有依赖,追加凭证验证依赖
# 调用父类的 `api_route` 方法来注册路由
return super().api_route(path, **kwargs)
# 创建 FastAPI 应用
app = FastAPI()
# 定义统一的异常处理
@app.exception_handler(HTTPException)
async def custom_http_exception_handler(request: Request, exc: HTTPException):
return JSONResponse(
status_code=exc.status_code,
content={"message": f"Oops! Something went wrong: {exc.detail}"},
)
# 使用自定义路由类定义路由
router = CustomRouter()
@router.get("/secure-route/")
async def secure_route(username: str = Depends(verify_credentials)):
return {"message": f"Welcome {username}!"}
@router.post("/log-route/")
async def log_route(data: dict):
return {"received": data}
@router.get("/custom-route/")
async def custom_route():
return {"message": "This is a custom route with default tag and security"}
# 挂载自定义路由
app.include_router(router)
# 运行应用
if __name__ == "__main__":
uvicorn.run(
f"{os.path.basename(__file__).split('.')[0]}:app", # 动态获取当前文件名来设置应用路径
host="127.0.0.1", # 设置主机为本地
port=8000, # 设置端口为8000
reload=True, # 启用自动重载(开发模式下非常有用)
)
解释
-
权限验证: 在
api_route
方法中,为每个路由自动添加 verify_credentials 依赖,确保所有路由都需要权限验证。 -
日志记录: 在每个请求处理之前,调用
log_request
记录请求的基本信息。 -
默认标签: 如果用户在定义路由时未指定
tags
,系统会自动为其添加一个默认标签["default"]
。 -
统一异常处理: 在
app
中定义了一个统一的异常处理函数,处理HTTPException
。
测试
首先安装
pip install fastapi httpx pytest
之后
from fastapi.testclient import TestClient
from 自定义路由_全面 import app # 导入要测试的 FastAPI 应用
# 创建测试客户端
client = TestClient(app)
# 测试 secure-route 路由,使用正确的凭据
def test_secure_route_with_correct_credentials():
# 使用正确的用户名和密码发起GET请求
response = client.get("/secure-route/", auth=("admin", "password"))
# 断言响应的状态码应为200,表示请求成功
assert response.status_code == 200
# 断言响应的JSON内容应为欢迎信息
assert response.json() == {"message": "Welcome admin!"}
# 测试 secure-route 路由,使用错误的凭据
def test_secure_route_with_wrong_credentials():
# 使用错误的用户名和密码发起GET请求
response = client.get("/secure-route/", auth=("wronguser", "wrongpass"))
# 断言响应的状态码应为401,表示未经授权
assert response.status_code == 401
# 断言响应的JSON内容应为错误提示信息
assert response.json() == {
"message": "Oops! Something went wrong: Invalid credentials"
}
# 测试 log-route 路由
def test_log_route():
# 使用正确的凭据和JSON数据发起POST请求
response = client.post(
"/log-route/", json={"test": "data"}, auth=("admin", "password")
)
# 断言响应的状态码应为200,表示请求成功
assert response.status_code == 200
# 断言响应的JSON内容应为接收到的数据
assert response.json() == {"received": {"test": "data"}}
# 测试 custom-route 路由
def test_custom_route():
# 使用正确的凭据发起GET请求
response = client.get("/custom-route/", auth=("admin", "password"))
# 断言响应的状态码应为200,表示请求成功
assert response.status_code == 200
# 断言响应的JSON内容应为指定的自定义消息
assert response.json() == {
"message": "This is a custom route with default tag and security"
}
# 运行所有测试
if __name__ == "__main__":
# 逐个调用测试函数
test_secure_route_with_correct_credentials()
test_secure_route_with_wrong_credentials()
test_log_route()
test_custom_route()
# 所有测试通过时,打印提示信息
print("All tests passed!")
解释
client = TestClient(app)
:
- 创建一个
TestClient
实例,它模拟一个客户端,用于发送请求到定义的 FastAPI 应用。这使得能够在不启动实际服务器的情况下测试 API。
test_secure_route_with_correct_credentials
测试函数:
- 该函数测试
/secure-route/
路由,并使用正确的用户名和密码进行身份验证。 - 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应包含欢迎信息。
test_secure_route_with_wrong_credentials
测试函数:
- 该函数测试
/secure-route/
路由,并使用错误的凭据进行身份验证。 - 它检查服务器是否返回 401 状态码,并且返回的 JSON 响应包含错误消息。
- test_log_route 测试函数:
- 该函数测试 /log-route/ 路由,并发送一个 JSON 数据的 POST 请求。
- 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应包含发送的数据。
test_custom_route
测试函数:
- 该函数测试 /custom-route/ 路由,确保它使用正确的凭据时返回预期的自定义消息。
- 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应符合预期。
if __name__ == "__main__"
:
- 这个部分的代码允许直接运行测试脚本。当运行这个脚本时,它会依次调用所有的测试函数并输出测试结果。
结果
总结
本指南详细介绍了如何在 FastAPI 中自定义
APIRouter
类,以实现统一的路由配置和逻辑扩展。通过自定义APIRouter
,开发者可以在保留原有功能的基础上,添加自定义的逻辑,如自动添加标签、统一的权限验证、日志记录等。具体实现包括继承APIRouter
并重写其核心方法api_route
,在路由注册时应用自定义逻辑。最终,通过实例化自定义的路由类并将其挂载到 FastAPI 应用中,实现了统一管理路由的行为。
此外,本指南还提供了一个完整的示例代码,展示了如何应用这些自定义逻辑,并附上了详细的测试代码。通过TestClient
模拟客户端请求,编写了一系列测试函数,确保应用在不同情况下的正确性。这样不仅提升了代码的模块化和可维护性,还保证了应用在不同场景下的一致性和稳定性。