Transaction¶
Transaction is a context manager that wraps one or more SQL operations
in a single atomic unit: commits on success, rolls back on exception.
Nested with Transaction(model) blocks use PostgreSQL savepoints
automatically — an exception in an inner block rolls back only that scope,
leaving the outer transaction intact.
See Learn halfORM in half an hour for a full walkthrough.
Work that must wait for the COMMIT¶
A mutation only becomes a fact when the transaction commits. Anything whose effect leaves the database — a notification, a cache eviction, a websocket event — must therefore be deferred until then, or it announces rows that a rollback has taken back.
with Transaction(blog):
post = Post(title='First post').ho_insert()
Transaction(blog).after_commit(lambda: broadcast('post', 'create', post['id']))
index(post) # raises → nothing is broadcast
# the COMMIT having succeeded, the callback runs here
Transaction(model).after_commit(...) reaches the open transaction of the
current thread from anywhere in the call stack: a model method can register a
callback without knowing who opened the transaction, and without a reference
to the object that did. Outside a transaction the callback runs immediately,
a mutation committing on its own.
AsyncTransaction carries the same two methods as coroutines — await
AsyncTransaction(model).after_commit(cb) — accepting coroutine functions and
plain ones alike.
Transaction
¶
Context manager for atomic database operations.
Wraps one or more SQL operations in a single transaction: commits on success, rolls back on exception. Transactions are per-thread and per-model instance.
Nested with Transaction(model) blocks use PostgreSQL savepoints
automatically: an exception in an inner block rolls back only that inner
block, leaving the outer transaction intact.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
model
|
Model
|
the :class: |
required |
Example
Atomic insert of two related rows:
from half_orm.transaction import Transaction
with Transaction(blog):
alice = Author(
first_name='Alice', last_name='Martin',
email='alice@example.com',
).ho_insert()
Post(
title='First post', content='Hello world',
author_id=alice['id'],
).ho_insert()
# both rows are committed, or neither is
Nested transactions use savepoints:
with Transaction(blog):
alice = Author(...).ho_insert()
with Transaction(blog): # savepoint
Post(...).ho_insert()
# exception here rolls back only the post, not Alice
Work that must wait for the COMMIT (see :meth:after_commit):
New in version 0.18.0: nested Transaction blocks use savepoints.
New in version 1.2.0: :meth:after_commit and :meth:after_rollback.
Methods:¶
after_commit(callback)
¶
Register callback to run once the transaction has committed.
A mutation only becomes a fact when the COMMIT succeeds: anything whose effect leaves the database — a notification, a cache eviction, a websocket event — has to wait for it, or it announces rows that a rollback has taken back.
Outside a transaction, a mutation commits on its own and the callback runs immediately. Inside one, it is queued and runs after the COMMIT of the outermost block, in registration order, with the connection back in autocommit mode. Rolling back a savepoint drops the callbacks registered inside it; releasing one hands them to the enclosing scope, whose own outcome then decides.
Registration is bound to the model and the current thread, not to this
instance: Transaction(model).after_commit(...) reaches the open
transaction from anywhere in the call stack, with no reference to the
object that opened it.
A callback failure is reported on stderr, and the callbacks that follow still run. It is never raised to the caller: the COMMIT has already succeeded, and an exception here would describe the transaction as having failed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
a callable taking no argument. Its return value is ignored. |
required |
Example
Publish an event only if the whole unit of work lands:
New in version 1.2.0.
after_rollback(callback)
¶
Register callback to run if the transaction is rolled back.
The mirror of :meth:after_commit: the callback runs when the scope
it was registered in is undone — the rollback of the outermost block,
the rollback of the savepoint it belongs to, or a COMMIT that fails.
Releasing a savepoint hands its callbacks to the enclosing scope,
which may still roll back.
Outside a transaction there is nothing to undo and the callback is
dropped — the symmetric case of :meth:after_commit running it at
once.
As with :meth:after_commit, a callback failure is reported on stderr
and never raised: here it would displace the exception the caller is
already handling.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
a callable taking no argument. Its return value is ignored. |
required |
New in version 1.2.0.