为了账号安全,请及时绑定邮箱和手机立即绑定

FastAPI与SQLAlchemy集中式异常处理指南

标签:
Python API

如果你之前实现过 FastAPI + SQLAlchemy 或 Django,你可能遇到过需要为每个视图或控制器添加的通用异常处理块。随着应用程序的扩展,维护这些异常处理会变得越来越繁琐。确保所有视图的一致日志记录和异常处理也可能变得非常具有挑战性和耗时。

我在我的 FastAPI 应用中的 API 接口组织方式是这样的:


    @app.post("/users/")
    def create_user(user: UserSchema, session: Session = Depends(get_db)):  
        try:  
            users = session.scalars(  
                select(UserModel).where(UserModel.name == user.name)  
            ).all()  
            if users:  
                raise UniqueViolation()  
            user_model = UserModel(**user.model_dump())  
            session.add(user_model)  
            session.commit()  
            return JSONResponse(  
                status_code=status.HTTP_201_CREATED,  
                content={"message": "用户创建成功。"},  
            )  
        except UniqueViolation as unique_violation:  
            raise HTTPException(  
                status_code=400, detail="已经有同名用户了。"  
            ) from unique_violation  
        except Exception as exception:  
            raise HTTPException(  
                status_code=500, detail="好像出了点问题,请稍后再试。"  
            ) from exception  

    @app.post("/tasks/")
    def create_task(task: TaskSchema, session: Session = Depends(get_db)):  
        try:  
            tasks = session.scalars(  
                select(TaskModel).where(TaskModel.title == task.title)  
            ).all()  
            if tasks:  
                raise UniqueViolation()  # 唯一性冲突
            task_model = TaskModel(**task.model_dump())  
            session.add(task_model)  
            session.commit()  
            return JSONResponse(  
                status_code=status.HTTP_201_CREATED,  
                content={"message": "任务创建成功。"},  
            )  
        except UniqueViolation as unique_violation:  
            raise HTTPException(  
                status_code=400, detail="已经有同名任务了。"  
            ) from unique_violation  
        except Exception as exception:  
            raise HTTPException(  
                status_code=500, detail="好像出了点问题,请稍后再试。"  
            ) from exception

如你所见,端点之间异常处理的重复性使得日志记录和错误管理的一致性变得相当麻烦。这种方法还可能遗漏某些异常,这可能导致未处理的错误,进而引发应用程序故障。

现在,让我们试着用上下文管理器(context manager)来集中处理这些异常。


import logging
from contextlib import contextmanager

from fastapi.exceptions import HTTPException
from psycopg.errors import (
    UndefinedColumn,
    UndefinedTable,
    UniqueViolation,
)
from sqlalchemy.exc import MultipleResultsFound, NoResultFound

logger = logging.getLogger(__name__)

COMMON_ERROR_DETAILS = {
    "no_result_found": "{type}未找到。",
    "unique_violation": "{type}已存在。",
}

@contextmanager
def exception_handler(error_message_mappings: dict):
    try:
        yield
    except NoResultFound as exception:
        raise HTTPException(
            status_code=400,
            detail=COMMON_ERROR_DETAILS["no_result_found"].format(
                type=error_message_mappings.get("type", "未知")
            ),
        ) from exception
    except UniqueViolation as exception:
        raise HTTPException(
            status_code=400,
            detail=COMMON_ERROR_DETAILS["unique_violation"].format(
                type=error_message_mappings.get("type", "未知")
            ),
        ) from exception
    except Exception as exception:
        logger.error("发生了意外错误: %s", str(exception), exc_info=True)
        raise HTTPException(
            status_code=500, detail="发生了错误,请稍后再试。"
        ) from exception

现在咱们来更新 FastAPI 应用中的 API 端点。


    @app.post("/users/")
    def 创建用户(user: UserSchema, db: Session = Depends(get_db)):
        with exception_handler(error_message_mappings={"type": "User"}):
            users = db.scalars(
                select(UserModel).where(UserModel.name == user.name)
            ).all()
            if users:
                raise UniqueViolation()
            user_model = UserModel(**user.model_dump())
            db.add(user_model)
            db.commit()
            return JSONResponse(
                status_code=status.HTTP_201_CREATED,
                content={"message": "用户创建成功。"}
            )

    @app.post("/tasks/")
    def 创建任务(task: TaskSchema, db: Session = Depends(get_db)):
        with exception_handler(error_message_mappings={"type": "Task"}):
            tasks = db.scalars(
                select(TaskModel).where(TaskModel.title == task.title)
            ).all()
            if tasks:
                raise UniqueViolation()
            task_model = TaskModel(**task.model_dump())
            db.add(task_model)
            db.commit()
            return JSONResponse(
                status_code=status.HTTP_201_CREATED,
                content={"message": "任务创建成功。"}
            )

如你所见,我们成功地从端点处移除了重复出现的异常处理代码。

虽然这种方法提供了诸如一致的错误处理方式和改善的可读性等好处,使代码更易于维护,随着应用规模的扩大,但也存在一些缺点。下面我们来看看这种方法的缺点。

不足:

  1. 虽然这简化了端点,但随着异常数量的增加,上下文管理器可能会变得复杂。
  2. 上下文管理器或异常处理可能无法适应应用程序可能出现的所有情况。
  3. 由于异常处理被抽象到了上下文管理器中,新开发人员可能很难理解。

参考: 我在查看Advanced alchemy包的源码时找到了这种异常处理方法,参考上下文管理器: https://github.com/litestar-org/advanced-alchemy/blob/main/advanced_alchemy/exceptions.py#L277

点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消