Transactions allow multiple queries to be committed only once successful.
This is useful for things like migrations, where you can’t have it fail in an inbetween state.
In the examples below we need to access the database
Table contains a reference to its
Engine, which is the easiest
way to access it. For example, with our
DB = Band._meta.db
This is useful when you want to programmatically add some queries to the transaction before running it.
transaction = DB.atomic() transaction.add(Manager.create_table()) transaction.add(Concert.create_table()) await transaction.run() # You're also able to run this synchronously: transaction.run_sync()
This is the preferred way to run transactions - it currently only works with async.
async with DB.transaction(): await Manager.create_table() await Concert.create_table()
The transaction is automatically committed when you exit the context manager.
async with DB.transaction(): await query_1 await query_2 # Automatically committed if the code reaches here.
You can manually commit it if you prefer:
async with DB.transaction() as transaction: await query_1 await query_2 await transaction.commit() print('transaction committed!')
If an exception is raised within the body of the context manager, then the transaction is automatically rolled back. The exception is still propagated though.
Rather than raising an exception, if you want to rollback a transaction manually you can do so as follows:
async with DB.transaction() as transaction: await Manager.create_table() await Band.create_table() await transaction.rollback()
Nested transactions aren’t supported in Postgres, but we can achieve something similar using savepoints.
Nested context managers¶
If you have nested context managers, for example:
async with DB.transaction(): async with DB.transaction(): ...
By default, the inner context manager does nothing, as we’re already inside a transaction.
You can change this behaviour using
allow_nested=False, in which case a
TransactionError is raised if you try creating a transaction when one
async with DB.transaction(): async with DB.transaction(allow_nested=False): # TransactionError('A transaction is already active.')
You can check whether your code is currently inside a transaction using the following:
>>> DB.transaction_exists() True
Postgres supports savepoints, which is a way of partially rolling back a transaction.
async with DB.transaction() as transaction: await Band.insert(Band(name='Pythonistas')) savepoint_1 = await transaction.savepoint() await Band.insert(Band(name='Terrible band')) # Oops, I made a mistake! await savepoint_1.rollback_to()
In the above example, the first query will be committed, but not the second.
By default, we assign a name to the savepoint for you. But you can explicitly give it a name:
This means you can rollback to this savepoint at any point just using the name:
For SQLite you may want to specify the transaction type, as it can have an effect on how well the database handles concurrent requests.