Home Assistant Unofficial Reference 2024.12.1
ratelimit.py
Go to the documentation of this file.
1 """Ratelimit helper."""
2 
3 from __future__ import annotations
4 
5 import asyncio
6 from collections.abc import Callable, Hashable
7 import logging
8 import time
9 
10 from homeassistant.core import HomeAssistant, callback
11 
12 _LOGGER = logging.getLogger(__name__)
13 
14 
16  """Class to track rate limits."""
17 
18  def __init__(
19  self,
20  hass: HomeAssistant,
21  ) -> None:
22  """Initialize ratelimit tracker."""
23  self.hasshass = hass
24  self._last_triggered: dict[Hashable, float] = {}
25  self._rate_limit_timers: dict[Hashable, asyncio.TimerHandle] = {}
26 
27  @callback
28  def async_has_timer(self, key: Hashable) -> bool:
29  """Check if a rate limit timer is running."""
30  return key in self._rate_limit_timers
31 
32  @callback
33  def async_triggered(self, key: Hashable, now: float | None = None) -> None:
34  """Call when the action we are tracking was triggered."""
35  self.async_cancel_timerasync_cancel_timer(key)
36  self._last_triggered[key] = now or time.time()
37 
38  @callback
39  def async_cancel_timer(self, key: Hashable) -> None:
40  """Cancel a rate limit time that will call the action."""
41  if handle := self._rate_limit_timers.pop(key, None):
42  handle.cancel()
43 
44  @callback
45  def async_remove(self) -> None:
46  """Remove all timers."""
47  for timer in self._rate_limit_timers.values():
48  timer.cancel()
49  self._rate_limit_timers.clear()
50 
51  @callback
52  def async_schedule_action[*_Ts](
53  self,
54  key: Hashable,
55  rate_limit: float | None,
56  now: float,
57  action: Callable[[*_Ts], None],
58  *args: *_Ts,
59  ) -> float | None:
60  """Check rate limits and schedule an action if we hit the limit.
61 
62  If the rate limit is hit:
63  Schedules the action for when the rate limit expires
64  if there are no pending timers. The action must
65  be called in async.
66 
67  Returns the time the rate limit will expire
68 
69  If the rate limit is not hit:
70 
71  Return None
72  """
73  if rate_limit is None:
74  return None
75 
76  if not (last_triggered := self._last_triggered.get(key)):
77  return None
78 
79  next_call_time = last_triggered + rate_limit
80 
81  if next_call_time <= now:
82  self.async_cancel_timerasync_cancel_timer(key)
83  return None
84 
85  _LOGGER.debug(
86  "Reached rate limit of %s for %s and deferred action until %s",
87  rate_limit,
88  key,
89  next_call_time,
90  )
91 
92  if key not in self._rate_limit_timers:
93  self._rate_limit_timers[key] = self.hasshass.loop.call_later(
94  next_call_time - now,
95  action,
96  *args,
97  )
98 
99  return next_call_time
None __init__(self, HomeAssistant hass)
Definition: ratelimit.py:21
None async_triggered(self, Hashable key, float|None now=None)
Definition: ratelimit.py:33
bool async_has_timer(self, Hashable key)
Definition: ratelimit.py:28
None async_cancel_timer(self, Hashable key)
Definition: ratelimit.py:39
web.Response get(self, web.Request request, str config_key)
Definition: view.py:88