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.

When this is called from inside an aatomic() block, the callback is registered on the async stack and fires from the async pop path — matching the sync semantics.

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.