Source code for gino.transaction

class _Break(BaseException):
    def __init__(self, tx, commit):
        super().__init__()
        self.tx = tx
        self.commit = commit


[docs]class GinoTransaction: """ Represents an underlying database transaction and its connection, offering methods to manage this transaction. :class:`.GinoTransaction` is supposed to be created by either :meth:`gino.engine.GinoConnection.transaction`, or :meth:`gino.engine.GinoEngine.transaction`, or :meth:`gino.api.Gino.transaction`, shown as follows:: async with db.transaction() as tx: ... async with engine.transaction() as tx: ... async with conn.transaction() as tx: ... tx = await conn.transaction() try: ... await tx.commit() except Exception: await tx.rollback() raise When in use with asynchronous context manager, :class:`.GinoTransaction` will be in **managed** mode, while the last example with ``await`` will put the :class:`.GinoTransaction` in **manual** mode where you have to call the :meth:`.commit` or :meth:`.rollback` to manually close the transaction. In **managed** mode the transaction will be automatically committed or rolled back on exiting the ``async with`` block depending on whether there is an exception or not. Meanwhile, you can explicitly exit the transaction early by :meth:`.raise_commit` or :meth:`.raise_rollback` which will raise an internal exception managed by the asynchronous context manager and interpreted as a commit or rollback action. In a nested transaction situation, the two exit-early methods always close up the very transaction which the two methods are referenced upon - all children transactions are either committed or rolled back correspondingly, while no parent transaction was ever touched. For example:: async with db.transaction() as tx1: async with db.transaction() as tx2: async with db.transaction() as tx3: tx2.raise_rollback() # Won't reach here # Won't reach here # Continues here with tx1, with both tx2 and tx3 rolled back. # For PostgreSQL, tx1 can still be committed successfully because # tx2 and tx3 are just SAVEPOINTs in transaction tx1 .. tip:: The internal exception raised from :meth:`.raise_commit` and :meth:`.raise_rollback` is a subclass of :exc:`BaseException`, so normal ``try ... except Exception:`` can't trap the commit or rollback. """ def __init__(self, conn, args, kwargs): self._conn = conn self._args = args self._kwargs = kwargs self._tx = None self._managed = None async def _begin(self): raw_conn = await self._conn.get_raw_connection() self._tx = self._conn.dialect.transaction(raw_conn, self._args, self._kwargs) await self._tx.begin() return self @property def connection(self): """ Accesses to the :class:`~gino.engine.GinoConnection` of this transaction. This is useful if when the transaction is started from ``db`` or ``engine`` where the connection is implicitly acquired for you together with the transaction. """ return self._conn @property def raw_transaction(self): """ Accesses to the underlying transaction object, whose type depends on the dialect in use. """ return self._tx.raw_transaction
[docs] def raise_commit(self): """ Only available in managed mode: skip rest of the code in this transaction and commit immediately by raising an internal exception, which will be caught and handled by the asynchronous context manager:: async with db.transaction() as tx: await user.update(age=64).apply() tx.raise_commit() await user.update(age=32).apply() # won't reach here assert user.age == 64 # no exception raised before """ assert self._managed, 'Illegal in manual mode, use `commit` instead.' raise _Break(self, True)
async def commit(self): """ Only available in manual mode: manually commit this transaction. """ assert not self._managed, ('Illegal in managed mode, ' 'use `raise_commit` instead.') await self._tx.commit()
[docs] def raise_rollback(self): """ Only available in managed mode: skip rest of the code in this transaction and rollback immediately by raising an internal exception, which will be caught and handled by the asynchronous context manager:: assert user.age == 64 # assumption async with db.transaction() as tx: await user.update(age=32).apply() tx.raise_rollback() await user.update(age=128).apply() # won't reach here assert user.age == 64 # no exception raised before """ assert self._managed, 'Illegal in manual mode, use `rollback` instead.' raise _Break(self, False)
async def rollback(self): """ Only available in manual mode: manually rollback this transaction. """ assert not self._managed, ('Illegal in managed mode, ' 'use `raise_rollback` instead.') await self._tx.rollback() def __await__(self): assert self._managed is None, 'Cannot start the same transaction twice' self._managed = False return self._begin().__await__() async def __aenter__(self): assert self._managed is None, 'Cannot start the same transaction twice' self._managed = True await self._begin() return self async def __aexit__(self, ex_type, ex, ex_tb): is_break = ex_type is _Break if is_break and ex.commit or ex_type is None: await self._tx.commit() else: await self._tx.rollback() if is_break and ex.tx is self: return True