Eve*_*ett 30 python sqlalchemy
我有一个使用 sqlalchemy 将数据保存到数据库的小型 Python Web 应用程序(用 Flask 编写)。当我尝试插入重复行时,会引发异常,如下所示:
(psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "uix_my_column"
Run Code Online (Sandbox Code Playgroud)
我想包装异常并重新引发我自己的异常,以便我可以添加我自己的特定于该特定错误的日志记录和消息传递。这是我尝试过的(简化):
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')
Run Code Online (Sandbox Code Playgroud)
但这无法捕获错误,因为psycopg2.errors.UniqueViolation它实际上不是类名 (!)。
在 PHP 中,这就像捕获复制/粘贴异常的类名一样简单,但在 Python 中,这更加模糊。
有一个类似的问题在这里,但它并没有解决这个问题的具体使用情况和(重要的),它没有澄清如何识别根异常类名。
如何找出实际引发的异常?为什么 Python 隐藏了这个?
Sup*_*oot 28
您在问题中发布的错误不是已提出的错误。完整的错误信息是:
sqlalchemy.exc.IntegrityError: (psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "model_name_key"
Run Code Online (Sandbox Code Playgroud)
关键部分是您出于某种原因选择忽略的 SQLAlchemy 错误。SQLAlchemy 捕获原始错误,将其包装在自己的错误中并引发该错误。
但在 Python 中,这更加模糊......为什么 Python 隐藏了这一点?
这不是混淆,没有任何隐藏,行为被记录下来,特定于您正在使用的框架,并且不是由 Python 语言强制执行的。SQLAlchemy 是一个抽象库,如果它引发特定于底层 dpapi 适配器的异常,它将显着降低在其中编写的代码的可移植性。
从文档:
SQLAlchemy 不会直接生成这些异常。相反,它们从数据库驱动程序中截获并由 SQLAlchemy 提供的异常 DBAPIError 包装,但是异常中的消息是由驱动程序生成的,而不是 SQLAlchemy。
由DBAPI层引起的异常被包裹在所述的子类sqlalchemy.exc.DBAPIError,其中应注意:
包装的异常对象在
orig属性中可用。
因此psycopg2.errors.UniqueViolation,如您所料,捕获 SQLAlchemy 异常并检查原始异常(它是 的实例)非常简单。但是,除非您的错误处理非常特定于 dbapi 层引发的类型,否则我建议检查底层类型可能是不必要的,因为引发的 SQLAlchemy 异常将提供足够的运行时信息来执行您必须执行的操作。
这是一个示例脚本,它引发sqlalchemy.exc.IntegrityError、捕获它、通过orig属性检查底层异常并引发替代的、本地定义的异常。
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.exc import IntegrityError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from psycopg2.errors import UniqueViolation
engine = create_engine("postgresql+psycopg2://some-user:mysecretpassword@localhost:5432/some-user")
Base = declarative_base()
Session = sessionmaker(bind=engine)
class BadRequest(Exception):
pass
class Model(Base):
__tablename__ = "model"
id = Column(Integer, primary_key=True)
name = Column(String, unique=True)
if __name__ == "__main__":
Base.metadata.drop_all(engine)
Base.metadata.create_all(engine)
s = Session()
s.add(Model(name="a"))
s.commit()
s.add(Model(name="a"))
try:
s.commit()
except IntegrityError as e:
assert isinstance(e.orig, UniqueViolation) # proves the original exception
raise BadRequest from e
Run Code Online (Sandbox Code Playgroud)
这引发了:
sqlalchemy.exc.IntegrityError: (psycopg2.errors.UniqueViolation) duplicate key value violates unique constraint "model_name_key"
DETAIL: Key (name)=(a) already exists.
[SQL: INSERT INTO model (name) VALUES (%(name)s) RETURNING model.id]
[parameters: {'name': 'a'}]
(Background on this error at: http://sqlalche.me/e/gkpj)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File ".\main.py", line 36, in <module>
raise BadRequest from e
__main__.BadRequest
Run Code Online (Sandbox Code Playgroud)
Eva*_*mir 23
我的答案略有不同,可以避免查找特定的数字错误代码。只需导入定义的常量UNIQUE_VIOLATION:
from psycopg2.errorcodes import UNIQUE_VIOLATION
from psycopg2 import errors
Run Code Online (Sandbox Code Playgroud)
然后使用错误查找函数:
except errors.lookup(UNIQUE_VIOLATION) as e:
Run Code Online (Sandbox Code Playgroud)
为我解决了这个问题。您可以根据需要导入其他错误代码常量。
小智 9
根据 psycopg2文档:
根据 DB API 2.0,该模块通过以下异常提供有关错误的信息:
异常 psycopg2.Error
异常是所有其他错误异常的基类。您可以使用它通过一个 except 语句捕获所有错误。警告不被视为错误,因此不使用此类作为基础。它是 Python StandardError(Python 3 上的异常)的子类。
因此,捕获异常的正确方法是:
try:
# your stuff here
except psycopg2.Error as e:
# get error code
error = e.pgcode
# then do something.
Run Code Online (Sandbox Code Playgroud)
根据ErrCodes 表,您的错误尤其是 23505
有关如何使用一些快速食谱导入 psycopg2 UniqueViolation(或任何其他错误)的快速参考。
import traceback # Used for printing the full traceback | Better for debug.
from psycopg2 import errors
UniqueViolation = errors.lookup('23505') # Correct way to Import the psycopg2 errors
# ...... Code ....
try:
db.commit()
except UniqueViolation as err:
traceback.print_exc()
db.rollback()
# ...... Code ....
Run Code Online (Sandbox Code Playgroud)
UniqueViolation 基本异常实际上是 IntegrityError ,因此对于更广泛的错误捕获(无论出于何种原因,通常不推荐,但规则意味着被打破)
import traceback # Used for printing the full traceback | Better for debug.
from psycopg2._psycopg import IntegrityError
# ...... Code ....
try:
db.commit()
except IntegrityError as err:
traceback.print_exc()
db.rollback()
# ...... Code ....
Run Code Online (Sandbox Code Playgroud)
psycopg2错误模块位于此处 --> /psycopg2/errors.py,实际上就像真实错误代码列表的网关。
在这里您可以看到用于通过给定代码调用正确错误的函数:
#
# NOTE: the exceptions are injected into this module by the C extention.
#
def lookup(code):
"""Lookup an error code and return its exception class.
Raise `!KeyError` if the code is not found.
"""
from psycopg2._psycopg import sqlstate_errors # avoid circular import
return sqlstate_errors[code]
Run Code Online (Sandbox Code Playgroud)
但真正有趣的东西在这里找到 --->\psycopg2\_psycopg\__init__.py
一旦在这里找到变量sqlstate_errors,它是一个包含代码作为值和实际错误作为注释的字典,这是一个小片段(相当大):
sqlstate_errors = {
'02000': None, # (!) real value is "<class 'psycopg2.errors.NoData'>"
'02001': None, # (!) real value is "<class 'psycopg2.errors.NoAdditionalDynamicResultSetsReturned'>"
'03000': None, # (!) real value is "<class 'psycopg2.errors.SqlStatementNotYetComplete'>"
'08000': None, # (!) real value is "<class 'psycopg2.errors.ConnectionException'>"
'08001': None, # (!) real value is "<class 'psycopg2.errors.SqlclientUnableToEstablishSqlconnection'>"
'08003': None, # (!) real value is "<class 'psycopg2.errors.ConnectionDoesNotExist'>"
'08004': None, # (!) real value is "<class 'psycopg2.errors.SqlserverRejectedEstablishmentOfSqlconnection'>"
'08006': None, # (!) real value is "<class 'psycopg2.errors.ConnectionFailure'>"
'08007': None, # (!) real value is "<class 'psycopg2.errors.TransactionResolutionUnknown'>"
'08P01': None, # (!) real value is "<class 'psycopg2.errors.ProtocolViolation'>"
# -------- Lots of lines ---------- #
'23503': None, # (!) real value is "<class 'psycopg2.errors.ForeignKeyViolation'>"
# There you are!!!
'23505': None, # (!) real value is "<class 'psycopg2.errors.UniqueViolation'>"
# ----------------
'23514': None, # (!) real value is "<class 'psycopg2.errors.CheckViolation'>"
'23P01': None, # (!) real value is "<class 'psycopg2.errors.ExclusionViolation'>"
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
15769 次 |
| 最近记录: |