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
« prev ^ index » next coverage.py v7.10.6, created at 2025-12-08 20:27 +0000
1import datetime as dt
2import logging
4import homeassistant.util.dt as dt_util
5from homeassistant.components.alarm_control_panel.const import AlarmControlPanelState
6from homeassistant.core import State
8_LOGGER = logging.getLogger(__name__)
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
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
29class Limiter:
30 """Rate limiting tracker"""
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 )
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
48 for call in self.calls[:]:
49 if call >= cut_off:
50 in_scope += 1
51 else:
52 self.calls.remove(call)
54 return in_scope > self.max_calls
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()