Обработка ошибок SQLAlchemy - как это делается?

Я хочу обработать случай, когда есть конфликт первичного ключа или уникального ключа, то есть дублирующаяся запись. Для этого я ловлю IntegrityError, которая прекрасно ловит ошибку. Проблема в том, что я не могу найти ни одного простого сообщения об ошибке или кода ошибки для проверки. Все, что я получаю, это свойство IntegrityError.message которое представляет собой строку, которая выглядит следующим образом:

(IntegrityError) (1062, "Дублирующая запись 'foobar' для ключа 'name'")

Это не очень полезно. Используя это, я собираюсь начать анализ сообщений об ошибках для их кода и сообщения. Вызов dir для исключения показывает только следующие свойства:

'args', 'connection_invalidated', 'instance', 'message', 'orig', 'params', 'Statement'

args - это просто одноэлементный кортеж с вышеупомянутой строкой внутри, а params - это данные, которые я пытался вставить. Я не могу найти какой-либо способ определить, что это на самом деле ошибка дублированного ключа, без необходимости разбирать сообщение об ошибке с помощью регулярных выражений или чего-то еще.

Кто-нибудь может пролить свет на этот вопрос?

Ответ 1

Я понял это во время написания вопроса, внимательно прочитав документацию. Я все еще собираюсь опубликовать это, хотя, так как это может помочь кому-то.

В документации по SQLAlchemy DBAPIError, из которой подкласс IntegrityError, объясняется, что исключение является просто оболочкой для базовой ошибки API базы данных и что исходная ошибка сохраняется как исключение orig в orig. Конечно, вызывая e.orig.args я получаю хорошо организованный кортеж:

(1062, "Дублирующаяся запись" foobar "для ключа" name "")

Ответ 2

Насколько я знаю, единственный способ - проанализировать строку ошибки в исключении. Я не могу найти кортеж, указывающий ошибку, столбец с нарушенным ограничением уникальности и значением отдельно.

В частности, можно искать сообщение об исключении, используя оператор поиска подстроки или регулярное выражение. Например:

    db.session.add(SupplierUser(supplier_id=supplier_id, username=username, password=password, creator=user))
    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        if "UNIQUE constraint failed: user.username" in str(err):
            return False, "error, username already exists (%s)" % username
        elif "FOREIGN KEY constraint failed" in str(err):
            return False, "supplier does not exist"
        else:
            return False, "unknown error adding user"

Однако эти строки могут быть довольно длинными, потому что оператор SQL включен, например:

(sqlite3.IntegrityError) UNIQUE constraint failed: user.username [SQL: 'INSERT INTO user (username, password_hash, created_time, creator_id, role, is_active, supplier_id) VALUES (?, ?, ?, ?, ?, ?, ?)'] [parameters: ('bob', ...

Таким образом, вы минимизируете ошибки обработки паролей обработки ошибок, если вы просматриваете сообщение об ошибке базы данных, без дополнительной информации из sqlalchemy. Это можно сделать, исследуя err.args, который должен быть меньше:

'(sqlite3.IntegrityError) UNIQUE constraint failed: supplier_user.username',)

Обновленный пример:

    db.session.add(SupplierUser(supplier_id=supplier_id, username=username, password=password, creator=user))
    try:
        db.session.commit()
    except IntegrityError as err:
        db.session.rollback()
        err_msg = err.args[0]
        if "UNIQUE constraint failed: supplier_user.username" in err_msg:
            return False, "error, supplier username already exists (%s)" % username
        elif "FOREIGN KEY constraint failed" in err_msg:
            return False, "supplier does not exist"
        else:
            return False, "unknown error adding user"

Обратите внимание на синтаксис ошибки, который я использовал здесь для sqlite3. Разбор сообщения об ошибке исключения mysql типа:

(1062, "Duplicate entry 'usr544' for key 'username'")

Может быть выполнено с соответствующим регулярным выражением. Обратите внимание, что он выглядит как кортеж, но на самом деле это строка (sqlalchemy версии 1.1.3 и mysql 5.5).

Например:

    except IntegrityError as err:
        db.session.rollback()
        if re.match("(.*)Duplicate entry(.*)for key 'username'(.*)", err.args[0]):
    .... etc ....