Trying to catch integrity error with SQLAlchemy

后端 未结 5 840
深忆病人
深忆病人 2021-01-01 20:30

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 ema

相关标签:
5条回答
  • 2021-01-01 20:33

    Edit: The edited answer above is a better way of doing this, using rollback.

    --

    If you want to handle transactions in the middle of a pyramid application or something where an automatic transaction commit is performed at the end of a sequence, there's no magic that needs to happen.

    Just remember to start a new transaction if the previous transaction has failed.

    Like this:

    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}
    

    Notice that calling .commit() on a successful transaction is fine, so it is not necessary to start a new transaction after a successful call.

    You only need to abort the transaction and start a new one if the transaction is in a failed state.

    (If transaction wasn't such a poc, you could use a savepoint and roll back to the savepoint rather than starting a new transaction; sadly, that is not possible, as attempting a commit invalidates a known previous savepoint. Great stuff huh?) (edit: <--- Turns out I'm wrong about that...)

    0 讨论(0)
  • 2021-01-01 20:34

    This is how I do it.

    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

    0 讨论(0)
  • 2021-01-01 20:45

    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.

    0 讨论(0)
  • 2021-01-01 20:51

    What you need to do is catch a general exception and output its class; then you can make the exception more specific.

    except Exception as ex:
        print ex.__class__
    
    0 讨论(0)
  • 2021-01-01 21:00

    There might be no database operations until DBSession.commit() therefore the IntegrityError is raised later in the stack after the controller code that has try/except has already returned.

    0 讨论(0)
提交回复
热议问题