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.

  1. with db.atomic() as transaction: # Opens new transaction.
  2. try:
  3. save_some_objects()
  4. except ErrorSavingData:
  5. # Because this block of code is wrapped with "atomic", a
  6. # new transaction will begin automatically after the call
  7. # to rollback().
  8. transaction.rollback()
  9. error_saving = True
  10.  
  11. create_report(error_saving=error_saving)
  12. # Note: no need to call commit. Since this marks the end of the
  13. # wrapped block of code, the `atomic` context manager will
  14. # automatically call commit for us.

Note

atomic() can be used as either a context manager ora decorator.

Context manager

Using atomic as context manager:

  1. db = SqliteDatabase(':memory:')
  2.  
  3. with db.atomic() as txn:
  4. # This is the outer-most level, so this block corresponds to
  5. # a transaction.
  6. User.create(username='charlie')
  7.  
  8. with db.atomic() as nested_txn:
  9. # This block corresponds to a savepoint.
  10. User.create(username='huey')
  11.  
  12. # This will roll back the above create() query.
  13. nested_txn.rollback()
  14.  
  15. User.create(username='mickey')
  16.  
  17. # When the block ends, the transaction is committed (assuming no error
  18. # 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:

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

Decorator

Using atomic as a decorator:

  1. @db.atomic()def create_user(username):

  2. # This statement will run in a transaction. If the caller is already
  3. # running in an `atomic` block, then a savepoint will be used instead.
  4. return User.create(username=username)
  5. 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.

  1. with db.atomic() as txn:
  2. perform_operation()
  3.  
  4. with db.atomic() as nested_txn:
  5. 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.

  1. db = SqliteDatabase(':memory:')
  2.  
  3. with db.transaction() as txn:
  4. # Delete the user and their associated tweets.
  5. 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.

  1. with db.transaction() as txn:
  2. User.create(username='mickey')
  3. txn.commit() # Changes are saved and a new transaction begins.
  4. User.create(username='huey')
  5.  
  6. # Roll back. "huey" will not be saved, but since "mickey" was already
  7. # committed, that row will remain in the database.
  8. txn.rollback()
  9.  
  10. with db.transaction() as txn:
  11. User.create(username='whiskers')
  12. # Roll back changes, which removes "whiskers".
  13. txn.rollback()
  14.  
  15. # Create a new row for "mr. whiskers" which will be implicitly committed
  16. # at the end of the `with` block.
  17. 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.

  1. with db.transaction() as txn:
  2. with db.savepoint() as sp:
  3. User.create(username='mickey')
  4.  
  5. with db.savepoint() as sp2:
  6. User.create(username='zaizee')
  7. 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:

  1. with db.manual_commit():
  2. db.begin() # Have to begin transaction explicitly.
  3. try:
  4. user.delete_instance(recursive=True)
  5. except:
  6. db.rollback() # Rollback! An error occurred.
  7. raise
  8. else:
  9. try:
  10. db.commit() # Commit changes.
  11. except:
  12. db.rollback()
  13. raise

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