Sessions & Transactions¶
NextORM uses an identity map session — a lightweight cache that ensures at most one Python object exists per database row within a given scope. The session also tracks dirty instances and defers writes until commit time.
The db_session context manager¶
All reads, writes, and relation accesses should take place inside a
db_session() context:
from nextorm import db_session
with db_session:
user = User(name="alice") # scheduled for INSERT
user.name = "bob" # marked dirty → UPDATE on commit
# ← session ends: flush + commit happen automatically
Using db_session() as a decorator wraps an entire
function in a session:
@db_session
def create_user(name: str) -> None:
User(name=name)
Session lifecycle¶
Enter — a new
SessionCache(identity map) is pushed.Work — entities created or loaded inside the session are tracked.
Exit (clean) —
flush()is called first to write all pending inserts/updates; then every database is committed, primary-first.Exit (exception) — every database is rolled back; the exception propagates.
Dirty tracking¶
When you modify an attribute on a loaded entity, the session marks it dirty automatically:
with db_session:
user = db.select(User).filter(User.id == 1).get()
user.name = "alice" # ← session sees this change
# ← UPDATE user SET name = 'alice' WHERE id = 1
Nested sessions¶
Nested with db_session: calls reuse the outermost session’s identity map.
The commit only happens when the outermost session exits:
with db_session: # outermost — creates identity map
with db_session: # nested — shares the same cache
User(name="inner")
# ← inner exit: no commit yet
# ← outermost exit: flush + commit
Parametrised sessions¶
Pass keyword arguments to configure the session behaviour:
with db_session(sql_debug=True): # log every SQL statement
...
with db_session(serializable=True): # SERIALIZABLE isolation (if supported)
...
with db_session(optimistic=False): # disable per-field optimistic checks
...
@db_session(retry=3): # retry on TransactionError up to 3 times
def do_work() -> None:
...
@db_session(allowed_exceptions=[KeyError]): # suppress KeyError, commit normally
def process() -> None:
raise KeyError("ok") # ← session commits; KeyError is re-raised after cleanup
Flushing¶
flush() writes all pending objects to the database
without committing the transaction. Use it when you need the auto-generated
primary key of an object inside the same session:
from nextorm import flush, db_session
with db_session:
order = Order(total=99.0)
flush() # INSERT fires → order.id is now available
line = OrderLine(order_id=order.id, product_id=42, qty=1)
The module-level flush() flushes all databases that have
pending entities in the current session.
Manual commit and rollback¶
Within a session you can commit or roll back manually:
from nextorm import commit, rollback, db_session
with db_session:
User(name="alice")
commit() # flush + commit — session continues
User(name="bob")
rollback() # discard "bob", clear identity map
Multi-database commit¶
When a session spans multiple databases, NextORM uses a staged commit:
Flush all — write pending changes to every database. If flush fails, roll back all databases.
Commit primary — commit the first database. If this fails, roll back all secondaries and raise
CommitException.Commit secondaries — commit each remaining database. If any fail, a
PartialCommitExceptionis raised (the primary commit is already durable).
The primary database is the one whose entity appears first in the session’s identity map.
Session depth and introspection¶
from nextorm.session import db_session
print(db_session.depth) # 0 outside, ≥1 inside
cache = db_session.current() # SessionCache | None
Async sessions¶
from nextorm import db_session
async with db_session:
await db.asave(User(name="alice"))
The __aexit__ path is identical to __exit__ except async databases have
their flush/commit/rollback awaited.
Exceptions¶
Exception |
Raised when |
|---|---|
A deadlock, serialisation failure, or invalid transaction state is
detected. Retried automatically when |
|
An optimistic concurrency check fails (another process changed a field you read since your last load). |
|
The primary-database commit fails in a multi-database session. |
|
One or more secondary-database commits fail (primary already durable). |