Skip to content

Latest commit

 

History

History
216 lines (160 loc) · 7.13 KB

transactions.rst

File metadata and controls

216 lines (160 loc) · 7.13 KB

Transactions

Peewee provides several interfaces for working with transactions. The most general is the :pyDatabase.atomic method, which also supports nested transactions. :py~Database.atomic blocks will be run in a transaction or savepoint, depending on the level of nesting.

If an exception occurs in a wrapped block, the current transaction/savepoint will be rolled back. Otherwise the statements will be committed at the end of the wrapped block.

Note

While inside a block wrapped by the :py~Database.atomic context manager, you can explicitly rollback or commit at any point by calling :pyTransaction.rollback or :pyTransaction.commit. When you do this inside a wrapped block of code, a new transaction will be started automatically.

with db.atomic() as transaction:  # Opens new transaction.
    try:
        save_some_objects()
    except ErrorSavingData:
        # Because this block of code is wrapped with "atomic", a
        # new transaction will begin automatically after the call
        # to rollback().
        transaction.rollback()
        error_saving = True

    create_report(error_saving=error_saving)
    # Note: no need to call commit. Since this marks the end of the
    # wrapped block of code, the `atomic` context manager will
    # automatically call commit for us.

Note

:py~Database.atomic can be used as either a context manager or a decorator.

Context manager

Using atomic as context manager:

db = SqliteDatabase(':memory:')

with db.atomic() as txn:
    # This is the outer-most level, so this block corresponds to
    # a transaction.
    User.create(username='charlie')

    with db.atomic() as nested_txn:
        # This block corresponds to a savepoint.
        User.create(username='huey')

        # This will roll back the above create() query.
        nested_txn.rollback()

    User.create(username='mickey')

# When the block ends, the transaction is committed (assuming no error
# occurs). At that point there will be two users, "charlie" and "mickey".

You can use the atomic method to perform get or create operations as well:

try:
    with db.atomic():
        user = User.create(username=username)
    return 'Success'
except peewee.IntegrityError:
    return 'Failure: %s is already in use.' % username

Decorator

Using atomic as a decorator:

@db.atomic()
def create_user(username):
    # This statement will run in a transaction. If the caller is already
    # running in an `atomic` block, then a savepoint will be used instead.
    return User.create(username=username)

create_user('charlie')

Nesting Transactions

:py~Database.atomic provides transparent nesting of transactions. When using :py~Database.atomic, the outer-most call will be wrapped in a transaction, and any nested calls will use savepoints.

with db.atomic() as txn:
    perform_operation()

    with db.atomic() as nested_txn:
        perform_another_operation()

Peewee supports nested transactions through the use of savepoints (for more information, see :py~Database.savepoint).

Explicit transaction

If you wish to explicitly run code in a transaction, you can use :py~Database.transaction. Like :py~Database.atomic, :py~Database.transaction can be used as a context manager or as a decorator.

If an exception occurs in a wrapped block, the transaction will be rolled back. Otherwise the statements will be committed at the end of the wrapped block.

db = SqliteDatabase(':memory:')

with db.transaction() as txn:
    # Delete the user and their associated tweets.
    user.delete_instance(recursive=True)

Transactions can be explicitly committed or rolled-back within the wrapped block. When this happens, a new transaction will be started.

with db.transaction() as txn:
    User.create(username='mickey')
    txn.commit()  # Changes are saved and a new transaction begins.
    User.create(username='huey')

    # Roll back. "huey" will not be saved, but since "mickey" was already
    # committed, that row will remain in the database.
    txn.rollback()

with db.transaction() as txn:
    User.create(username='whiskers')
    # Roll back changes, which removes "whiskers".
    txn.rollback()

    # Create a new row for "mr. whiskers" which will be implicitly committed
    # at the end of the `with` block.
    User.create(username='mr. whiskers')

Note

If you attempt to nest transactions with peewee using the :py~Database.transaction context manager, only the outer-most transaction will be used. However if an exception occurs in a nested block, this can lead to unpredictable behavior, so it is strongly recommended that you use :py~Database.atomic.

Explicit Savepoints

Just as you can explicitly create transactions, you can also explicitly create savepoints using the :py~Database.savepoint method. Savepoints must occur within a transaction, but can be nested arbitrarily deep.

with db.transaction() as txn:
    with db.savepoint() as sp:
        User.create(username='mickey')

    with db.savepoint() as sp2:
        User.create(username='zaizee')
        sp2.rollback()  # "zaizee" will not be saved, but "mickey" will be.

Warning

If you manually commit or roll back a savepoint, a new savepoint will not automatically be created. This differs from the behavior of :pytransaction, which will automatically open a new transaction after manual commit/rollback.

Autocommit Mode

By default, Peewee operates in autocommit mode, such that any statements executed outside of a transaction are run in their own transaction. To group multiple statements into a transaction, Peewee provides the :py~Database.atomic context-manager/decorator. This should cover all use-cases, but in the unlikely event you want to temporarily disable Peewee's transaction management completely, you can use the :pyDatabase.manual_commit context-manager/decorator.

Here is how you might emulate the behavior of the :py~Database.transaction context manager:

with db.manual_commit():
    db.begin()  # Have to begin transaction explicitly.
    try:
        user.delete_instance(recursive=True)
    except:
        db.rollback()  # Rollback! An error occurred.
        raise
    else:
        try:
            db.commit()  # Commit changes.
        except:
            db.rollback()
            raise

Again -- I don't anticipate anyone needing this, but it's here just in case.