Coverage for custom_components/autoarm/helpers.py: 89%

44 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-12-08 20:27 +0000

1import datetime as dt 

2import logging 

3 

4import homeassistant.util.dt as dt_util 

5from homeassistant.components.alarm_control_panel.const import AlarmControlPanelState 

6from homeassistant.core import State 

7 

8_LOGGER = logging.getLogger(__name__) 

9 

10 

11def alarm_state_as_enum(state_str: str | None) -> AlarmControlPanelState | None: 

12 if state_str is None: 

13 return None 

14 try: 

15 return AlarmControlPanelState(state_str) 

16 except ValueError as e: 

17 _LOGGER.warning("AUTOARM Invalid alarm state: %s", e) 

18 return None 

19 

20 

21def safe_state(state: State | None) -> str | None: 

22 try: 

23 return state.state if state is not None else None 

24 except Exception as e: 

25 _LOGGER.debug("AUTOARM Failed to load state %s: %s", state, e) 

26 return None 

27 

28 

29class Limiter: 

30 """Rate limiting tracker""" 

31 

32 def __init__(self, window: dt.timedelta, max_calls: int = 4) -> None: 

33 self.calls: list[dt.datetime] = [] 

34 self.window: dt.timedelta = window 

35 self.max_calls: int = max_calls 

36 _LOGGER.debug( 

37 "AUTOARM Rate limiter initialized with window %s and max_calls %s", 

38 window, 

39 max_calls, 

40 ) 

41 

42 def triggered(self) -> bool: 

43 """Register a call and check if window based rate limit triggered""" 

44 cut_off: dt.datetime = dt_util.now() - self.window 

45 self.calls.append(dt_util.now()) 

46 in_scope = 0 

47 

48 for call in self.calls[:]: 

49 if call >= cut_off: 

50 in_scope += 1 

51 else: 

52 self.calls.remove(call) 

53 

54 return in_scope > self.max_calls 

55 

56 

57def deobjectify(obj: object) -> dict | str | int | float | bool | None: 

58 if obj is None or isinstance(obj, (str, int, float, bool)): 

59 return obj 

60 if isinstance(obj, (dt.datetime, dt.time, dt.date)): 

61 return obj.isoformat() 

62 as_dict = getattr(obj, "as_dict", None) 

63 if as_dict is None: 

64 return str(obj) 

65 return as_dict()