Source code for mersal.transport.default_transaction_context
from __future__ import annotations
import types
from typing import TYPE_CHECKING, Any, Self
from .transaction_context import TransactionContext
if TYPE_CHECKING:
from collections.abc import Callable
from mersal.types import AsyncTransactionContextCallable
__all__ = (
"DefaultTransactionContext",
"InvalidTransactioContextStateError",
)
[docs]
class DefaultTransactionContext(TransactionContext):
[docs]
def __init__(self) -> None:
self.items: dict[str | type, Any] = {}
self._on_committed_actions: list[AsyncTransactionContextCallable] = []
self._on_rollback_actions: list[AsyncTransactionContextCallable] = []
self._on_ack_actions: list[AsyncTransactionContextCallable] = []
self._on_nack_actions: list[AsyncTransactionContextCallable] = []
self._on_closed_actions: list[AsyncTransactionContextCallable] = []
self._on_error_actions: list[Callable[[Exception], None]] = []
self._must_commit: bool | None = None
self._must_ack: bool | None = None
self._completed: bool = False
self._closed: bool = False
async def __aenter__(self) -> Self:
return self
async def __aexit__(
self,
exc_type: type[BaseException] | None,
exc_val: BaseException | None,
exc_tb: types.TracebackType | None,
) -> None:
await self.close()
def on_commit(self, action: AsyncTransactionContextCallable) -> None:
self._raise_exception_if_completed()
self._on_committed_actions.append(action)
def on_rollback(self, action: AsyncTransactionContextCallable) -> None:
self._raise_exception_if_completed()
self._on_rollback_actions.append(action)
def on_ack(self, action: AsyncTransactionContextCallable) -> None:
self._raise_exception_if_completed()
self._on_ack_actions.append(action)
def on_nack(self, action: AsyncTransactionContextCallable) -> None:
self._raise_exception_if_completed()
self._on_nack_actions.append(action)
def on_close(self, action: AsyncTransactionContextCallable) -> None:
self._raise_exception_if_completed()
self._on_closed_actions.append(action)
def on_error(self, action: Callable[[Exception], None]) -> None:
self._on_error_actions.append(action)
async def complete(self) -> None:
if self._must_commit is None or self._must_ack is None:
raise InvalidTransactioContextStateError(
"Transaction state `complete` method called before calling `set_result`"
)
try:
await self._try_to_commit_or_rollback()
await self._ack_or_nack()
finally:
self._completed = True
def set_result(self, commit: bool, ack: bool) -> None:
self._must_commit = commit
self._must_ack = ack
async def close(self) -> None:
if self._closed:
return
if not self._must_commit:
await self._try_to_rollback_or_invoke_error()
if not self._must_ack:
await self._try_to_nack_or_invoke_error()
try:
await self._invoke_closed_actions()
except Exception as e: # noqa: BLE001
self._invoke_on_error_actions(e)
self._closed = True
async def _commit_or_rollback(self) -> None:
if self._must_commit:
await self._invoke_committed_actions()
else:
await self._invoke_rollback_actions()
async def _ack_or_nack(self) -> None:
if self._must_ack:
await self._invoke_ack_actions()
else:
await self._invoke_nack_actions()
async def _try_to_commit_or_rollback(self) -> None:
try:
await self._commit_or_rollback()
except:
await self._try_to_nack_or_invoke_error()
raise
async def _try_to_nack_or_invoke_error(self) -> None:
try:
await self._invoke_nack_actions()
except Exception as e: # noqa: BLE001
self._invoke_on_error_actions(e)
async def _try_to_rollback_or_invoke_error(self) -> None:
try:
await self._invoke_rollback_actions()
except Exception as e: # noqa: BLE001
self._invoke_on_error_actions(e)
def _raise_exception_if_completed(self) -> None:
if self._completed:
raise InvalidTransactioContextStateError("TransactionContext has been completed, cannot add actions")
async def _invoke_committed_actions(self) -> None:
await self._invoke_actions(self._on_committed_actions)
async def _invoke_rollback_actions(self) -> None:
await self._invoke_actions(self._on_rollback_actions)
async def _invoke_ack_actions(self) -> None:
await self._invoke_actions(self._on_ack_actions)
async def _invoke_nack_actions(self) -> None:
await self._invoke_actions(self._on_nack_actions)
async def _invoke_closed_actions(self) -> None:
await self._invoke_actions(self._on_closed_actions)
async def _invoke_actions(self, actions: list[AsyncTransactionContextCallable]) -> None:
_actions = actions.copy()
actions.clear()
for action in _actions:
await action(self)
def _invoke_on_error_actions(self, exception: Exception) -> None:
for action in self._on_error_actions:
action(exception)
class InvalidTransactioContextStateError(Exception):
def __init__(self, message: str) -> None:
super().__init__(message)