Managing Transactions
Peewee provides several interfaces for working with transactions. The mostgeneral is the Database.atomic()
method, which also supports nestedtransactions. atomic()
blocks will be run in a transactionor savepoint, depending on the level of nesting.
If an exception occurs in a wrapped block, the current transaction/savepointwill be rolled back. Otherwise the statements will be committed at the end ofthe wrapped block.
Note
While inside a block wrapped by the atomic()
contextmanager, you can explicitly rollback or commit at any point by callingTransaction.rollback()
or Transaction.commit()
. When youdo this inside a wrapped block of code, a new transaction will be startedautomatically.
- 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
atomic()
can be used as either a context manager ora 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 aswell:
- 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
atomic()
provides transparent nesting of transactions. Whenusing atomic()
, the outer-most call will be wrapped in atransaction, 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 moreinformation, see savepoint()
).
Explicit transaction
If you wish to explicitly run code in a transaction, you can usetransaction()
. Like atomic()
,transaction()
can be used as a context manager or as adecorator.
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 wrappedblock. 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 thetransaction()
context manager, only the outer-mosttransaction will be used. However if an exception occurs in a nested block,this can lead to unpredictable behavior, so it is strongly recommended thatyou use atomic()
.
Explicit Savepoints
Just as you can explicitly create transactions, you can also explicitly createsavepoints using the savepoint()
method. Savepoints mustoccur 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 willnot automatically be created. This differs from the behavior oftransaction
, which will automatically open a new transactionafter manual commit/rollback.
Autocommit Mode
By default, Peewee operates in autocommit mode, such that any statementsexecuted outside of a transaction are run in their own transaction. To groupmultiple statements into a transaction, Peewee provides theatomic()
context-manager/decorator. This should cover alluse-cases, but in the unlikely event you want to temporarily disable Peewee’stransaction management completely, you can use theDatabase.manual_commit()
context-manager/decorator.
Here is how you might emulate the behavior of thetransaction()
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.