Coverage for src/paperap/models/abstract/meta.py: 98%

41 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-20 13:17 -0400

1""" 

2---------------------------------------------------------------------------- 

3 

4 METADATA: 

5 

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 

13 

14---------------------------------------------------------------------------- 

15 

16 LAST MODIFIED: 

17 

18 2025-03-07 By Jess Mann 

19 

20""" 

21 

22from __future__ import annotations 

23 

24from typing import TYPE_CHECKING, Any, Iterable, Literal 

25 

26from paperap.const import ModelStatus 

27 

28if TYPE_CHECKING: 

29 from paperap.models.abstract import BaseModel 

30 

31 

32class StatusContext: 

33 """ 

34 Context manager for safely updating model status. 

35 

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. 

40 

41 Examples: 

42 >>> class SomeModel(BaseModel): 

43 ... def perform_update(self): 

44 ... with StatusContext(self, ModelStatus.UPDATING): 

45 ... # Perform an update 

46 

47 """ 

48 

49 _model: "BaseModel" 

50 _new_status: ModelStatus 

51 _previous_status: ModelStatus | None 

52 _save_lock_acquired: bool = False 

53 

54 @property 

55 def model(self) -> "BaseModel": 

56 """Read-only access to the model.""" 

57 return self._model 

58 

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 

63 

64 @property 

65 def new_status(self) -> ModelStatus: 

66 """Read-only access to the new status.""" 

67 return self._new_status 

68 

69 @property 

70 def previous_status(self) -> ModelStatus | None: 

71 """Read-only access to the previous status.""" 

72 return self._previous_status 

73 

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__() 

79 

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 

87 

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 

95 

96 def __enter__(self) -> None: 

97 # Acquire a save lock 

98 if self.new_status == ModelStatus.SAVING: 

99 self.save_lock() 

100 

101 self._previous_status = self._model._status # type: ignore # allow private access 

102 self._model._status = self.new_status # type: ignore # allow private access 

103 

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 

106 

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 

114 

115 self.save_unlock()