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.