Coverage for src/paperap/models/abstract/meta.py: 98%
41 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-18 12:26 -0400
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-18 12:26 -0400
1"""
2----------------------------------------------------------------------------
4 METADATA:
6 File: meta.py
7 Project: paperap
8 Created: 2025-03-07
9 Version: 0.0.8
10 Author: Jess Mann
11 Email: jess@jmann.me
12 Copyright (c) 2025 Jess Mann
14----------------------------------------------------------------------------
16 LAST MODIFIED:
18 2025-03-07 By Jess Mann
20"""
22from __future__ import annotations
24from typing import TYPE_CHECKING, Any, Iterable, Literal
26from paperap.const import ModelStatus
28if TYPE_CHECKING:
29 from paperap.models.abstract import BaseModel
32class StatusContext:
33 """
34 Context manager for safely updating model status.
36 Attributes:
37 model (SomeModel): The model whose status is being updated.
38 new_status (ModelStatus): The status to set within the context.
39 previous_status (ModelStatus): The status before entering the context.
41 Examples:
42 >>> class SomeModel(BaseModel):
43 ... def perform_update(self):
44 ... with StatusContext(self, ModelStatus.UPDATING):
45 ... # Perform an update
47 """
49 _model: "BaseModel"
50 _new_status: ModelStatus
51 _previous_status: ModelStatus | None
52 _save_lock_acquired: bool = False
54 @property
55 def model(self) -> "BaseModel":
56 """Read-only access to the model."""
57 return self._model
59 @property
60 def _model_meta(self) -> "BaseModel.Meta":
61 """Read-only access to the model's meta."""
62 return self.model._meta # pyright: ignore[reportPrivateUsage] # pylint: disable=protected-access
64 @property
65 def new_status(self) -> ModelStatus:
66 """Read-only access to the new status."""
67 return self._new_status
69 @property
70 def previous_status(self) -> ModelStatus | None:
71 """Read-only access to the previous status."""
72 return self._previous_status
74 def __init__(self, model: "BaseModel", new_status: ModelStatus) -> None:
75 self._model = model
76 self._new_status = new_status
77 self._previous_status = None
78 super().__init__()
80 def save_lock(self) -> None:
81 """
82 Acquire the save lock
83 """
84 # Trigger the self.model._save_lock (threading.RLock) to be acquired
85 self.model._save_lock.acquire() # type: ignore # allow protected access
86 self._save_lock_acquired = True
88 def save_unlock(self) -> None:
89 """
90 Release the save lock, only if this statuscontext previous acquired it.
91 """
92 # Release the self.model._save_lock (threading.RLock)
93 if self._save_lock_acquired:
94 self.model._save_lock.release() # type: ignore # allow protected access
96 def __enter__(self) -> None:
97 # Acquire a save lock
98 if self.new_status == ModelStatus.SAVING:
99 self.save_lock()
101 self._previous_status = self._model._status # type: ignore # allow private access
102 self._model._status = self.new_status # type: ignore # allow private access
104 # Do NOT return context manager, because we want to guarantee that the status is reverted
105 # so we do not want to allow access to the context manager object
107 def __exit__(
108 self, exc_type: type[BaseException] | None, exc_value: BaseException | None, traceback: Iterable[Any]
109 ) -> None:
110 if self.previous_status is not None:
111 self._model._status = self.previous_status # type: ignore # allow private access
112 else:
113 self._model._status = ModelStatus.ERROR # type: ignore # allow private access
115 self.save_unlock()