fastapi之自定义路由类

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!")

解释

  1. client = TestClient(app):
  • 创建一个 TestClient 实例,它模拟一个客户端,用于发送请求到定义的 FastAPI 应用。这使得能够在不启动实际服务器的情况下测试 API。
  1. test_secure_route_with_correct_credentials 测试函数:
  • 该函数测试 /secure-route/ 路由,并使用正确的用户名和密码进行身份验证。
  • 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应包含欢迎信息。
  1. test_secure_route_with_wrong_credentials 测试函数:
  • 该函数测试 /secure-route/ 路由,并使用错误的凭据进行身份验证。
  • 它检查服务器是否返回 401 状态码,并且返回的 JSON 响应包含错误消息。
  1. test_log_route 测试函数:
  • 该函数测试 /log-route/ 路由,并发送一个 JSON 数据的 POST 请求。
  • 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应包含发送的数据。
  1. test_custom_route 测试函数:
  • 该函数测试 /custom-route/ 路由,确保它使用正确的凭据时返回预期的自定义消息。
  • 它检查服务器是否返回 200 状态码,并且返回的 JSON 响应符合预期。
  1. if __name__ == "__main__":
  • 这个部分的代码允许直接运行测试脚本。当运行这个脚本时,它会依次调用所有的测试函数并输出测试结果。

结果

在这里插入图片描述

总结

本指南详细介绍了如何在 FastAPI 中自定义 APIRouter 类,以实现统一的路由配置和逻辑扩展。通过自定义 APIRouter,开发者可以在保留原有功能的基础上,添加自定义的逻辑,如自动添加标签、统一的权限验证、日志记录等。具体实现包括继承 APIRouter 并重写其核心方法 api_route,在路由注册时应用自定义逻辑。最终,通过实例化自定义的路由类并将其挂载到 FastAPI 应用中,实现了统一管理路由的行为。
此外,本指南还提供了一个完整的示例代码,展示了如何应用这些自定义逻辑,并附上了详细的测试代码。通过 TestClient 模拟客户端请求,编写了一系列测试函数,确保应用在不同情况下的正确性。这样不仅提升了代码的模块化和可维护性,还保证了应用在不同场景下的一致性和稳定性。

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

trust Tomorrow

感谢支持!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值