Skip to content

Transactions

dorm.transaction.atomic(using: str | Callable[..., Any] = 'default', *, durable: bool = False)

Wrap a block of code in a database transaction.

Usable as a context manager or as a decorator::

with dorm.transaction.atomic():
    ...

@dorm.transaction.atomic
def update_balance(...):
    ...

@dorm.transaction.atomic("replica")
def report(...):
    ...

On success the transaction is committed; on exception it is rolled back. Nested calls create savepoints so only the inner block is rolled back on inner failure. The returned context manager exposes :meth:set_rollback so test fixtures (or generic cleanup helpers) can force a rollback without having to raise an exception.

Pass durable=True (Django 3.2+) to assert that this atomic block is the outermost one — i.e. the surrounding code is NOT already inside another atomic(). The block raises :class:RuntimeError immediately if it would silently degrade to a savepoint instead of a top-level transaction. Use this on work that MUST land in its own COMMIT (write-then-publish patterns where the publish step waits on a real fsync).

dorm.transaction.aatomic(using: str | Callable[..., Any] = 'default', *, durable: bool = False)

Async counterpart of :func:atomic. Same usage as atomic: works as async with context manager or as a decorator on async functions. durable=True is enforced the same way as the sync version.

dorm.transaction.on_commit(callback: Callable[[], Any], using: str = 'default') -> None

Schedule callback to run when the current transaction commits.

Use this for side effects that must happen only after the surrounding write actually lands — sending email, enqueueing a Celery / RQ job, publishing to a message bus, calling an external API — so you never leak an effect for a transaction that ends up rolling back.

If called outside an :func:atomic block, the callback runs immediately (Django's behaviour). Inside nested atomics, callbacks are deferred all the way up to the outermost commit; a rollback at any depth discards callbacks scheduled inside that block.

Exceptions from a callback are logged on the dorm.transaction logger but do not roll back the (already-committed) transaction — by the time the callback fires, the DB state is durable. Wire that logger to your alerting if you depend on these for correctness.

dorm.transaction.aon_commit(callback: Union[Callable[[], Any], Callable[[], Awaitable[Any]]], using: str = 'default') -> None

Async counterpart of :func:on_commit.

Accepts both regular callables and coroutine functions / awaitables. Outside an :func:aatomic block, the callback fires immediately — if it returns an awaitable, a task is scheduled with asyncio.ensure_future so the call site doesn't have to await.

Inside aatomic, callbacks are deferred to the outermost commit just like the sync variant. Rolled-back blocks discard their pending callbacks.

Forcing a rollback: set_rollback

The context manager returned by atomic() and aatomic() exposes set_rollback(True) to force a rollback at exit time without raising an exception. Useful for test fixtures and speculative-write patterns:

with dorm.transaction.atomic() as tx:
    Author.objects.create(name="speculative", age=1)
    if not is_useful(...):
        tx.set_rollback(True)
    # Block exits cleanly; rollback fires anyway.

When set_rollback(True) is called, any pending on_commit callbacks scheduled inside that block are discarded — same behaviour as a real exception-driven rollback.

The async variant exposes the same method on the aatomic() context manager, with identical semantics.

on_commit / aon_commit semantics

on_commit(callback, using="default") schedules a zero-arg callable to run after the surrounding transaction commits. Outside an atomic() block, the callback fires immediately. Inside nested blocks, callbacks are deferred all the way to the outermost commit; a rollback at any depth discards the callbacks scheduled in that block (and any merged from nested commits).

A failing callback is logged on the dorm.transaction logger but does not propagate — by the time it runs the DB has already committed and raising would falsely claim the transaction failed.

aon_commit accepts both regular callables and coroutine functions; coroutine results are awaited at outermost-commit time. Outside an aatomic() block, coroutine callbacks are scheduled with asyncio.ensure_future so the call site doesn't have to await.