Database Transactions
buraq.db.transaction provides async-first transaction management.
from buraq.db import transactionatomic()
Section titled “atomic()”Wrap a block of database work in a single transaction. If an exception is raised, the transaction rolls back automatically.
As a context manager
Section titled “As a context manager”from buraq.db import transaction
async def create_order(user, items): async with transaction.atomic(): order = await Order.objects.create(user_id=user.id) for item in items: await OrderItem.objects.create(order_id=order.id, product_id=item.id) await Inventory.objects.filter(product_id__in=[i.id for i in items]).update( stock=F("stock") - 1 ) # commits here — rollback on any exception aboveAs a decorator
Section titled “As a decorator”@transaction.atomicasync def transfer_funds(from_account_id, to_account_id, amount): await Account.objects.filter(id=from_account_id).update(balance=F("balance") - amount) await Account.objects.filter(id=to_account_id).update(balance=F("balance") + amount)Both forms are equivalent — choose whichever fits the call site better.
on_commit()
Section titled “on_commit()”Run a callback after the current transaction commits successfully. Useful for side effects that must not happen if the transaction rolls back (sending emails, triggering webhooks, etc.):
async with transaction.atomic(): user = await User.objects.create(email="alice@example.com")
async def send_welcome(): await send_mail("Welcome!", "Thanks for signing up.", [user.email])
await transaction.on_commit(send_welcome)on_commit accepts both sync and async callables.
Callbacks registered with on_commit are deferred — they are collected
inside the async with block and executed only after the transaction commits
successfully. If the transaction rolls back, the callbacks are discarded.
save() inside atomic()
Section titled “save() inside atomic()”Model.save() called inside an atomic() block automatically participates in
the outer transaction rather than opening its own session:
async with transaction.atomic(): order = await Order.objects.create(user_id=user.id, total=99) order.status = "confirmed" await order.save() # uses the same session — rolls back with the blockWithout atomic(), save() opens and closes its own session per call. With
atomic(), all saves share one session and either commit or roll back together.
non_atomic()
Section titled “non_atomic()”Mark a function as explicitly not requiring a transaction — useful for read-only views or functions that manage their own sessions:
@transaction.non_atomicasync def read_report(request): return await Report.objects.all()This is a documentation marker only — it does not open or close any transaction.
Nesting
Section titled “Nesting”Nested atomic() calls are supported — the inner block shares the outer transaction:
async with transaction.atomic(): await Post.objects.create(title="Draft")
async with transaction.atomic(): await Tag.objects.create(name="python") # inner block exits — still in outer transaction
# outer commits or rolls back everythingError handling
Section titled “Error handling”from buraq.db.transaction import TransactionManagementError
try: async with transaction.atomic(): await Post.objects.create(title="") # raises ValidationErrorexcept Exception as e: # transaction already rolled back print(f"Failed: {e}")