Coverage for src / kemi / operations / _ops_hooks.py: 91%

23 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-06-05 15:47 +0000

1"""Event hook operations: add_event_hook, remove_event_hook, _run_hooks.""" 

2 

3from __future__ import annotations 

4 

5import logging 

6from typing import TYPE_CHECKING, Any, Callable 

7 

8if TYPE_CHECKING: 

9 from kemi._memory_impl import Memory 

10 

11logger = logging.getLogger(__name__) 

12 

13 

14def add(memory: "Memory", phase: str, callback: Callable[..., Any]) -> None: 

15 """Register an event hook callback. 

16 

17 Args: 

18 phase: "pre" or "post" — called before or after the operation. 

19 callback: Callable that receives (operation, **kwargs). 

20 """ 

21 if phase not in ("pre", "post"): 

22 raise ValueError("phase must be 'pre' or 'post'") 

23 memory._event_hooks[phase].append(callback) 

24 

25 

26def remove( 

27 memory: "Memory", phase: str, callback: Callable[..., Any] 

28) -> bool: 

29 """Remove a previously registered event hook callback. 

30 

31 Returns True if removed, False if not found. 

32 """ 

33 if phase in memory._event_hooks and callback in memory._event_hooks[phase]: 

34 memory._event_hooks[phase].remove(callback) 

35 return True 

36 return False 

37 

38 

39def run( 

40 memory: "Memory", 

41 phase: str, 

42 operation: str, 

43 *, 

44 raise_on_error: bool | None = None, 

45 **kwargs: Any, 

46) -> None: 

47 """Run all hooks registered for a phase/operation. 

48 

49 Args: 

50 phase: "pre" or "post". 

51 operation: Name of the operation triggering the hook. 

52 raise_on_error: If True, exceptions from hooks are re-raised so 

53 a failing pre-hook can abort the operation. If None (default), 

54 the value is taken from ``memory._config.hooks_raise_on_error``. 

55 **kwargs: Passed through to each callback. 

56 """ 

57 if raise_on_error is None: 

58 raise_on_error = memory._config.hooks_raise_on_error 

59 for hook in memory._event_hooks.get(phase, []): 

60 try: 

61 hook(operation, **kwargs) 

62 except Exception: 

63 # Broad catch: hooks are user code, so any error is their fault. 

64 # Honour the configured raise_on_error flag. 

65 if raise_on_error: 

66 raise 

67 logger.warning( 

68 f"Event hook failed for {phase}:{operation}", exc_info=True 

69 )