Transactions
Peewee provides several interfaces for working with transactions. The most general is the Database.atomic()
method, which also supports nested transactions. 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 atomic()
context manager, you can explicitly rollback or commit at any point by calling Transaction.rollback()
or Transaction.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
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
atomic()
provides transparent nesting of transactions. When using 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 savepoint()
).
Explicit transaction
If you wish to explicitly run code in a transaction, you can use transaction()
. Like atomic()
, 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 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 atomic()
.
Explicit Savepoints
Just as you can explicitly create transactions, you can also explicitly create savepoints using the 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 transaction
, 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 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 Database.manual_commit()
context-manager/decorator.
Here is how you might emulate the behavior of the 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.