"""
----------------------------------------------------------------------------
METADATA:
File: meta.py
Project: paperap
Created: 2025-03-07
Version: 0.0.8
Author: Jess Mann
Email: jess@jmann.me
Copyright (c) 2025 Jess Mann
----------------------------------------------------------------------------
LAST MODIFIED:
2025-03-07 By Jess Mann
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Iterable, Literal
from paperap.const import ModelStatus
if TYPE_CHECKING:
from paperap.models.abstract import BaseModel
[docs]
class StatusContext:
"""
Context manager for safely updating model status.
Attributes:
model (SomeModel): The model whose status is being updated.
new_status (ModelStatus): The status to set within the context.
previous_status (ModelStatus): The status before entering the context.
Examples:
>>> class SomeModel(BaseModel):
... def perform_update(self):
... with StatusContext(self, ModelStatus.UPDATING):
... # Perform an update
"""
_model: "BaseModel"
_new_status: ModelStatus
_previous_status: ModelStatus | None
_save_lock_acquired: bool = False
@property
def model(self) -> "BaseModel":
"""Read-only access to the model."""
return self._model
@property
def _model_meta(self) -> "BaseModel.Meta":
"""Read-only access to the model's meta."""
return self.model._meta # pyright: ignore[reportPrivateUsage] # pylint: disable=protected-access
@property
def new_status(self) -> ModelStatus:
"""Read-only access to the new status."""
return self._new_status
@property
def previous_status(self) -> ModelStatus | None:
"""Read-only access to the previous status."""
return self._previous_status
[docs]
def __init__(self, model: "BaseModel", new_status: ModelStatus) -> None:
self._model = model
self._new_status = new_status
self._previous_status = None
super().__init__()
[docs]
def save_lock(self) -> None:
"""
Acquire the save lock
"""
# Trigger the self.model._save_lock (threading.RLock) to be acquired
self.model._save_lock.acquire() # type: ignore # allow protected access
self._save_lock_acquired = True
[docs]
def save_unlock(self) -> None:
"""
Release the save lock, only if this statuscontext previous acquired it.
"""
# Release the self.model._save_lock (threading.RLock)
if self._save_lock_acquired:
self.model._save_lock.release() # type: ignore # allow protected access
def __enter__(self) -> None:
# Acquire a save lock
if self.new_status == ModelStatus.SAVING:
self.save_lock()
self._previous_status = self._model._status # type: ignore # allow private access
self._model._status = self.new_status # type: ignore # allow private access
# Do NOT return context manager, because we want to guarantee that the status is reverted
# so we do not want to allow access to the context manager object
def __exit__(
self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: Iterable[Any]
) -> None:
if self.previous_status is not None:
self._model._status = self.previous_status # type: ignore # allow private access
else:
self._model._status = ModelStatus.ERROR # type: ignore # allow private access
self.save_unlock()