无法捕获SQLAlchemy的IntegrityError错误

62

尽管我努力尝试,但似乎无法正确捕获sqlalchemy的IntegrityError:

from sqlalchemy import exc

try:
    insert_record()
except exc.IntegrityError, exc:
    print exc # this is never called
    handle_elegantly() # this is never called

正如人们所预料的:

IntegrityError: (IntegrityError) insert or update on table "my_table" 
                violates foreign key constraint "my_table_some_column_fkey"

我已经尝试明确说明:

from sqlalchemy.exc import IntegrityError

更新:

我发现了一些似乎符合这里所发生情况的东西,即当会话刷新到数据库并且try/except块已被执行后,才会抛出完整性错误:Trying to catch integrity error with SQLAlchemy

然而,在try块中添加session.flush()会产生一个InvalidRequestError

ERROR:root:This Session's transaction has been rolled back due to a previous 
           exception during flush. To begin a new transaction with this Session, 
           first issue Session.rollback(). 
           Original exception was: (IntegrityError)

你确定这就是发生的地方吗? - Martin Konecny
你使用哪个数据库? - alecxe
另外,将“IntegrityError”更改为“DatabaseError”是否有帮助?谢谢。 - alecxe
你从哪里得到了消息 "ERROR:root:This Session's...。在我看来,print exc 生成了那个消息? - Martin Konecny
@MartinKonecny 不幸的是,这并不是打印语句(看起来更容易进行故障排除),而是通过我的服务器日志。 - Chrispy
显示剩余3条评论
5个回答

68

我在我的 Flask 应用程序中也有同样的需求,我像下面这样处理它,它可以正常工作:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import exc

db = SQLAlchemy(Flask(__name__))

try:
     db.session.add(resource)
     return db.session.commit()
except exc.IntegrityError:
     db.session.rollback()

3
谢谢,我一开始没有从SQLAlchemy导入exc.IntegrityError,所以使用失败了。 - Paul Tibbetts

39

IntegrityError被触发后,不论您是否捕获了该错误,您正在使用的会话都将无效。正如第二个错误消息所指示的那样,要开始一个新的事务,请先执行Session.rollback()。,如果想继续使用该会话,您需要执行 session.rollback()

我不能确定,但我猜测您或您的Web框架正在尝试以某种方式继续使用引发IntegrityError的会话。我建议您在捕获异常后或在handle_elegantly函数中执行session.rollback()

如果运行下面的代码,您就会明白我的意思:

from sqlalchemy import types
from sqlalchemy import exc
from sqlalchemy import create_engine
from sqlalchemy.schema import Column
from zope.sqlalchemy import ZopeTransactionExtension
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session

Base = declarative_base()


class User(Base):
    __tablename__ = 'user'
    name = Column(types.String, primary_key=True)


def handle_elegantly(name):
    session = DBSession()
    session.add(User(name=name))
    session.flush()
    print 'Exception elegantly handled!!\n'


def pretend_view(request):
    """Pretend view in a Pyramid application using pyramid_tm"""
    session = DBSession()
    user = User()
    print '\n-------Here we rollback before continuing -------'
    try:
        session.add(user)
        session.flush()
    except exc.IntegrityError:
        session.rollback()
        handle_elegantly('This will run fine')

    print '\n------- Here we do not, and this will error -------'
    try:
        session.add(user)
        session.flush()
    except exc.IntegrityError:
        handle_elegantly('Exception will be raised')


if __name__ == '__main__':
    engine = create_engine('sqlite://')
    global DBSession
    DBSession = scoped_session(
        sessionmaker(extension=ZopeTransactionExtension()))
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine
    Base.metadata.create_all()
    pretend_view("dummy request")

2

在捕获错误后,您必须添加session.rollback()

try:
    session.flush()
except IntegrityError:
    session.rollback()

0

答案与上面的答案相同,我只想添加一段额外的代码,如果您想在except中显示一些动态错误,则将其作为JsonResponse或其他形式返回。

注意:从安全角度来看,输出这种类型的错误不是很好。

from sqlalchemy import exc


try:
     db.session.add(resource)
     return db.session.commit()
except exc.IntegrityError as e:
     err_msg = str(e.orig).split(':')[-1].replace('\n', '').strip()
     print(err_mg)
     return err_mg 

exc.IntegrityError 有一个名为 orig 的键,其中包含此消息:

duplicate key value violates unique constraint "ix_table_column"
DETAIL:  Key (column)=(some_data) already exists.

此消息的类型为<class 'driver.errors.UniqueViolation'>而不是str,因此我们应该将其转换为str,然后通过一些简单的字符串操作仅获取确切的错误信息,这种情况下是:

Key (column)=(some_data) already exists.

-4
SQLALCHEMY_COMMIT_ON_TEARDOWN = False

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接