Попытка поймать ошибку целостности с помощью SQLAlchemy - программирование

Попытка поймать ошибку целостности с помощью SQLAlchemy

У меня возникают проблемы с попыткой поймать ошибку. Я использую Pyramid/SQLAlchemy и сделал форму регистрации с электронной почтой в качестве первичного ключа. Проблема заключается в том, когда вводится дублирующее письмо, оно вызывает IntegrityError, поэтому я пытаюсь поймать эту ошибку и предоставить сообщение, но независимо от того, что я делаю, я не могу ее уловить, ошибка продолжает появляться.

try:
    new_user = Users(email, firstname, lastname, password)
    DBSession.add(new_user)
    return HTTPFound(location = request.route_url('new'))
except IntegrityError:
    message1 = "Yikes! Your email already exists in our system. Did you forget your password?"

Я получаю одно и то же сообщение, когда пытаюсь except exc.SQLAlchemyError (хотя я хочу поймать определенные ошибки, а не одевать все). Я также пробовал exc.IntegrityError, но не повезло (хотя оно существует в API).

Что-то не так с моим синтаксисом Python, или есть что-то, что мне нужно сделать в SQLAlchemy, чтобы поймать его?


Я не знаю, как решить эту проблему, но у меня есть несколько идей о том, что может вызвать проблему. Возможно, оператор try не терпит неудачу, но преуспевает, потому что SQLAlchemy создает само исключение, а Pyramid генерирует представление, поэтому except IntegrityError: никогда не активируется. Или, что более вероятно, я полностью ошибаюсь в этой ошибке.

4b9b3361

Ответ 1

В Pyramid, если вы настроили свой сеанс (который для этого делает файл автоматически) для использования ZopeTransactionExtension, тогда сеанс не будет краснет/зафиксирован, пока после выполнения представления. Если вы хотите поймать любые ошибки SQL самостоятельно в своем представлении, вам нужно заставить flush отправить SQL в движок. DBSession.flush() должен сделать это после add(...).

Обновление

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

def create_unique_object(db, max_attempts=3):
    while True:
        sp = transaction.savepoint()
        try:
            obj = MyObject()
            obj.identifier = uuid.uuid4().hex
            db.add(obj)
            db.flush()
        except IntegrityError:
            sp.rollback()
            max_attempts -= 1
            if max_attempts < 1:
                raise
        else:
            return obj

obj = create_unique_object(DBSession)

Обратите внимание, что даже это подвержено дублированию между транзакциями, если не используется блокировка на уровне таблицы, но, по крайней мере, показывает, как использовать точку сохранения.

Ответ 2

Что вам нужно сделать, это поймать общее исключение и вывести его класс; то вы можете сделать исключение более конкретным.

except Exception as ex:
    print ex.__class__

Ответ 3

Не может быть никаких операций с базой данных до DBSession.commit(), поэтому IntegrityError возникает позже в стеке после того, как код контроллера, который имеет try/except, уже вернулся.

Ответ 4

Изменить: отредактированный ответ выше - лучший способ сделать это, используя откат.

-

Если вы хотите обрабатывать транзакции в середине приложения пирамиды или что-то, где автоматическая фиксация транзакции выполняется в конце последовательности, нет никакой магии, которая должна произойти.

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

Вот так:

def my_view(request):
   ... # Do things
   if success:
     try:
       instance = self._instance(**data)
       DBSession.add(instance)
       transaction.commit()
       return {'success': True}
     except IntegrityError as e:  # <--- Oh no! Duplicate unique key
       transaction.abort()
       transaction.begin() # <--- Start new transaction
       return {'success': False}

Обратите внимание, что вызов .commit() в успешной транзакции прекрасен, поэтому нет необходимости запускать новую транзакцию после успешного вызова.

Вам нужно только прервать транзакцию и запустить новую, если транзакция находится в состоянии отказа.

(Если транзакция не была такой poc, вы могли бы использовать точку сохранения и откат назад к точке сохранения вместо того, чтобы начинать новую транзакцию; к сожалению, это невозможно, так как попытка фиксации делает недействительным известную предыдущую точку сохранения. да?) (edit: < --- Оказывается, я ошибаюсь в этом...)

Ответ 5

Вот как я это делаю.

from contextlib import(
        contextmanager,
        )


@contextmanager
def session_scope():
    """Provide a transactional scope around a series of operations."""
    session = Session()
    try:
        yield session
        session.commit()
    except:
        session.rollback()
        raise
    finally:
        session.close()



def create_user(email, firstname, lastname, password):

    new_user = Users(email, firstname, lastname, password)

    try:

        with session_scope() as session:

            session.add(new_user)

    except sqlalchemy.exc.IntegrityError as e:
        pass

http://docs.sqlalchemy.org/en/latest/orm/session_basics.html#when-do-i-construct-a-session-when-do-i-commit-it-and-when-do-i-close-it