Session Management¶
PySpring Model handles database sessions automatically through SessionContextHolder, which provides thread-safe and async-safe session isolation using Python's contextvars.
How it works¶
SessionContextHolder maintains a stack of TransactionState objects per execution context (thread or async task). Each TransactionState holds a reference to a PySpringSession and a depth counter for nested transactions.
graph TD
A[HTTP Request] --> B[SessionContextHolder]
B --> C[ContextVar per thread/task]
C --> D[TransactionState Stack]
D --> E[PySpringSession]
E --> F[SQLAlchemy Engine]
Automatic session management¶
In most cases, you don't interact with SessionContextHolder directly. The @Transactional decorator and CrudRepository methods manage sessions for you:
@Transactionalcreates or joins a transaction viaTransactionManagerTransactionManagerdelegates to the appropriatePropagationHandler- The handler uses
SessionContextHolderto push/pop transaction states CrudRepositorymethods callSessionContextHolder.get_or_create_session()to get the current session
HTTP request lifecycle¶
For HTTP applications, PySpring Model automatically registers a session cleanup middleware. This ensures sessions are properly closed after each request:
- Request arrives
- Controller/service methods use
@Transactional— sessions are created as needed - After the response is sent, the middleware calls
SessionContextHolder.clear() - All sessions in the current context are closed
Manual session usage¶
For cases where you need direct session control (e.g., in @SkipAutoImplementation methods), use PySpringModel.create_managed_session():
from py_spring_model import PySpringModel
with PySpringModel.create_managed_session() as session:
result = session.exec(select(User).where(User.email == email))
user = result.first()
# Session commits automatically on exit
# Rolls back on exception
The managed session context manager:
- Creates a new
PySpringSession - Commits on successful exit (configurable with
should_commit=False) - Rolls back on exception
- Always closes the session in
finally
Read-only sessions¶
with PySpringModel.create_managed_session(should_commit=False) as session:
users = session.exec(select(User)).all()
# No commit — read-only
SessionContextHolder API¶
| Method | Description |
|---|---|
get_or_create_session() |
Get the current session or create a new one |
has_active_transaction() |
Check if a transaction is active (depth >= 1) |
has_session() |
Check if a session exists in the current context |
current_state() |
Get the current TransactionState (or None) |
push_state(state) |
Push a new transaction state onto the stack |
pop_state() |
Pop the top transaction state from the stack |
clear() |
Close all sessions and clear the state stack |
Context isolation¶
SessionContextHolder uses ContextVar, which means:
- Each thread gets its own session stack
- Each
asynciotask gets its own session stack - No cross-contamination between concurrent requests
- No need for explicit thread-local management
This is the same isolation mechanism used by Python's decimal.localcontext() and contextvars.copy_context().