我有一个小型 Python 网络应用程序(用 Flask 编写),它使用 sqlalchemy 将数据保存到数据库。当我尝试插入重复的行时,会引发异常,如下所示:
(psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "uix_my_column"
我想包装异常并重新引发我自己的异常,这样我就可以添加我自己的特定于该特定错误的日志记录和消息传递。这是我尝试过的(简化):
from db import DbApi
from my_exceptions import BadRequest
from psycopg2.errors import UniqueViolation # <-- this does not exist!
class MyClass:
def __init__(self):
self.db = DbApi()
def create(self, data: dict) -> MyRecord:
try:
with self.db.session_local(expire_on_commit=False) as session:
my_rec = MyRecord(**data)
session.add(my_rec)
session.commit()
session.refresh(my_rec)
return my_rec
except UniqueViolation as e:
raise BadRequest('A duplicate record already exists')
但这无法捕获错误,因为 psycopg2.errors.UniqueViolation
实际上不是类名(!)。
在 PHP 中,这就像捕获复制/粘贴异常的类名一样简单,但在 Python 中,这要复杂得多。
这里 有一个类似的问题,但它没有处理这个特定的用例,并且(重要的是)它没有阐明如何识别根异常类名。
如何找出实际引发的异常?为什么 Python 隐藏这个?
原文由 Everett 发布,翻译遵循 CC BY-SA 4.0 许可协议
您在问题中发布的错误不是已引发的错误。完整的错误信息是:
关键部分是您出于某种原因选择忽略的 SQLAlchemy 错误。 SQLAlchemy 捕获原始错误,将其包装在自己的错误中并引发错误。
这不是混淆,没有隐藏任何内容,行为已记录,特定于您正在使用的框架,并且不是由 Python 语言强制执行的。 SQLAlchemy 是一个抽象库,如果它引发特定于底层 dpapi 适配器的异常,它将显着降低在其中编写的代码的可移植性。
从 文档:
dbapi 层引发的异常被包装在 sqlalchemy.exc.DBAPIError 的子类中,其中指出:
因此,捕获 SQLAlchemy 异常并检查原始异常非常简单,正如您所期望的那样,原始异常是
psycopg2.errors.UniqueViolation
的一个实例。但是,除非您的错误处理非常特定于 dbapi 层引发的类型,否则我建议可能不需要检查基础类型,因为引发的 SQLAlchemy 异常将提供足够的运行时信息来执行您必须执行的操作。这是一个引发
sqlalchemy.exc.IntegrityError
的示例脚本,捕获它,通过orig
属性检查底层异常并引发一个替代的、本地定义的异常。这就提出了: