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.