Lostsoul Lostsoul - 3 months ago 28
Python Question

Trying to catch integrity error with SQLAlchemy

I'm having problems with trying to catch an error. I'm using Pyramid/SQLAlchemy and made a sign up form with email as the primary key. The problem is when a duplicate email is entered it raises a IntegrityError, so I'm trying to catch that error and provide a message but no matter what I do I can't catch it, the error keeps appearing.

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?"


I get the same message when I tried
except exc.SQLAlchemyError
(although I want to catch specific errors and not a blanket catch all). I also tried
exc.IntegrityError
but no luck (although it exists in the API).

Is there something wrong with my Python syntax, or is there something I need to do special in SQLAlchemy to catch it?




I don't know how to solve this problem but I have a few ideas of what could be causing the problem. Maybe the try statement isn't failing but succeeding because SQLAlchemy is raising the exception itself and Pyramid is generating the view so the
except IntegrityError:
never gets activated. Or, more likely, I'm catching this error completely wrong.

Answer

In Pyramid, if you've configured your session (which the scaffold does for you automatically) to use the ZopeTransactionExtension, then session is not flushed/committed until after the view has executed. If you want to catch any SQL errors yourself in your view, you need to force a flush to send the SQL to the engine. DBSession.flush() should do it after the add(...).

Update

I'm updating this answer with an example of a savepoint just because there are very few examples around of how to do this with the transaction package.

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)

Note that even this is susceptible to duplicates between transactions if no table-level locking is used, but it at least shows how to use a savepoint.

Comments