Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions homeassistant/helpers/event.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Helpers for listening to events."""
import asyncio
import copy
from dataclasses import dataclass
from datetime import datetime, timedelta
import functools as ft
Expand Down Expand Up @@ -820,6 +821,8 @@ def _render_template_if_ready(
if not _event_triggers_rerender(event, info):
return False

had_timer = self._rate_limit.async_has_timer(template)

if self._rate_limit.async_schedule_action(
template,
_rate_limit_for_event(event, info, track_template_),
Expand All @@ -829,7 +832,7 @@ def _render_template_if_ready(
(track_template_,),
True,
):
return False
return not had_timer

_LOGGER.debug(
"Template update %s triggered by event: %s",
Expand Down Expand Up @@ -893,7 +896,14 @@ def _refresh(
if info_changed:
assert self._track_state_changes
self._track_state_changes.async_update_listeners(
_render_infos_to_track_states(self._info.values()),
_render_infos_to_track_states(
[
_suppress_domain_all_in_render_info(self._info[template])
if self._rate_limit.async_has_timer(template)
else self._info[template]
for template in self._info
]
)
)
_LOGGER.debug(
"Template group %s listens for %s",
Expand Down Expand Up @@ -1458,3 +1468,13 @@ def _rate_limit_for_event(

rate_limit: Optional[timedelta] = info.rate_limit
return rate_limit


def _suppress_domain_all_in_render_info(render_info: RenderInfo) -> RenderInfo:
"""Remove the domains and all_states from render info during a ratelimit."""
rate_limited_render_info = copy.copy(render_info)
rate_limited_render_info.all_states = False
rate_limited_render_info.all_states_lifecycle = False
rate_limited_render_info.domains = set()
rate_limited_render_info.domains_lifecycle = set()
return rate_limited_render_info
65 changes: 65 additions & 0 deletions tests/helpers/test_event.py
Original file line number Diff line number Diff line change
Expand Up @@ -1494,6 +1494,71 @@ def refresh_listener(event, updates):
assert refresh_runs == [0, 1, 2, 4]


async def test_track_template_rate_limit_suppress_listener(hass):
"""Test template rate limit will suppress the listener during the rate limit."""
template_refresh = Template("{{ states | count }}", hass)

refresh_runs = []

@ha.callback
def refresh_listener(event, updates):
refresh_runs.append(updates.pop().result)

info = async_track_template_result(
hass,
[TrackTemplate(template_refresh, None, timedelta(seconds=0.1))],
refresh_listener,
)
await hass.async_block_till_done()
info.async_refresh()

assert info.listeners == {"all": True, "domains": set(), "entities": set()}
await hass.async_block_till_done()

assert refresh_runs == [0]
hass.states.async_set("sensor.one", "any")
await hass.async_block_till_done()
assert refresh_runs == [0]
info.async_refresh()
assert refresh_runs == [0, 1]
hass.states.async_set("sensor.two", "any")
await hass.async_block_till_done()
# Should be suppressed during the rate limit
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1]
next_time = dt_util.utcnow() + timedelta(seconds=0.125)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
await hass.async_block_till_done()
# Rate limit released and the all listener returns
assert info.listeners == {"all": True, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.three", "any")
await hass.async_block_till_done()
assert refresh_runs == [0, 1, 2]
hass.states.async_set("sensor.four", "any")
await hass.async_block_till_done()
assert refresh_runs == [0, 1, 2]
# Rate limit hit and the all listener is shut off
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 2)
with patch(
"homeassistant.helpers.ratelimit.dt_util.utcnow", return_value=next_time
):
async_fire_time_changed(hass, next_time)
await hass.async_block_till_done()
# Rate limit released and the all listener returns
assert info.listeners == {"all": True, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2, 4]
hass.states.async_set("sensor.five", "any")
await hass.async_block_till_done()
# Rate limit hit and the all listener is shut off
assert info.listeners == {"all": False, "domains": set(), "entities": set()}
assert refresh_runs == [0, 1, 2, 4]


async def test_track_template_rate_limit_five(hass):
"""Test template rate limit of 5 seconds."""
template_refresh = Template("{{ states | count }}", hass)
Expand Down