Coverage for src / lexigram / contracts / admin / action_hooks.py: 100%

16 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-08-15 18:58 +0800

1"""Action lifecycle hook contracts — before/after/failure hooks for admin actions. 

2 

3Defines the ``ActionHookProtocol`` that action-level and resource-level 

4hooks must satisfy, plus the ``HasActionHooks`` protocol marking objects 

5that expose hook collections. 

6""" 

7 

8from __future__ import annotations 

9 

10from typing import TYPE_CHECKING, Any, Protocol, runtime_checkable 

11 

12if TYPE_CHECKING: 

13 from lexigram.contracts.admin.errors import AdminError 

14 from lexigram.contracts.core.result import Result 

15 

16 

17@runtime_checkable 

18class ActionHookProtocol(Protocol): 

19 """Lifecycle hooks for admin actions. 

20 

21 Hooks are executed around the action body by ``ActionExecutor``: 

22 

23 - ``before()`` runs first and may modify the input data or abort the 

24 action by returning ``Err``. 

25 - ``after()`` runs after successful execution. 

26 - ``on_failure()`` runs when ``before()`` returned ``Err`` or the 

27 action body raised an exception. 

28 """ 

29 

30 async def before( 

31 self, record: Any, data: dict[str, Any] 

32 ) -> Result[dict[str, Any], AdminError]: 

33 """Run before the action body. 

34 

35 Args: 

36 record: The target record (may be ``None`` for global actions). 

37 data: Action payload; may be amended by the hook. 

38 

39 Returns: 

40 ``Ok(data)`` with possibly-amended data, or ``Err`` to abort. 

41 """ 

42 ... 

43 

44 async def after(self, record: Any, result: Any) -> None: 

45 """Run after a successful action execution. 

46 

47 Args: 

48 record: The target record (may be ``None`` for global actions). 

49 result: The action result payload. 

50 """ 

51 ... 

52 

53 async def on_failure(self, record: Any, error: Exception) -> None: 

54 """Run when the action fails. 

55 

56 Args: 

57 record: The target record (may be ``None`` for global actions). 

58 error: The exception or error that caused the failure. 

59 """ 

60 ... 

61 

62 

63@runtime_checkable 

64class HasActionHooks(Protocol): 

65 """Protocol for objects exposing action lifecycle hooks. 

66 

67 Implemented by action handlers and resources so the executor can 

68 discover ``before`` / ``after`` / ``failure`` hooks declaratively. 

69 """ 

70 

71 @property 

72 def before_hooks(self) -> list[ActionHookProtocol]: 

73 """Hooks run before the action body.""" 

74 ... 

75 

76 @property 

77 def after_hooks(self) -> list[ActionHookProtocol]: 

78 """Hooks run after successful execution.""" 

79 ... 

80 

81 @property 

82 def failure_hooks(self) -> list[ActionHookProtocol]: 

83 """Hooks run on action failure.""" 

84 ... 

85 

86 

87__all__ = ["ActionHookProtocol", "HasActionHooks"]