Remove manual rate_limit control directive from templates (#41225)
Increase default rate limit for all states and entire domain states to one minute Ensure specifically referenced entities are excluded from the rate limit
This commit is contained in:
parent
e75557c1f5
commit
51da605b9f
4 changed files with 51 additions and 218 deletions
|
@ -802,7 +802,7 @@ class _TrackTemplateResultInfo:
|
||||||
|
|
||||||
if self._rate_limit.async_schedule_action(
|
if self._rate_limit.async_schedule_action(
|
||||||
template,
|
template,
|
||||||
info.rate_limit or track_template_.rate_limit,
|
_rate_limit_for_event(event, info, track_template_),
|
||||||
now,
|
now,
|
||||||
self._refresh,
|
self._refresh,
|
||||||
event,
|
event,
|
||||||
|
@ -1376,3 +1376,22 @@ def _event_triggers_rerender(event: Event, info: RenderInfo) -> bool:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return bool(info.filter_lifecycle(entity_id))
|
return bool(info.filter_lifecycle(entity_id))
|
||||||
|
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def _rate_limit_for_event(
|
||||||
|
event: Event, info: RenderInfo, track_template_: TrackTemplate
|
||||||
|
) -> Optional[timedelta]:
|
||||||
|
"""Determine the rate limit for an event."""
|
||||||
|
entity_id = event.data.get(ATTR_ENTITY_ID)
|
||||||
|
|
||||||
|
# Specifically referenced entities are excluded
|
||||||
|
# from the rate limit
|
||||||
|
if entity_id in info.entities:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if track_template_.rate_limit is not None:
|
||||||
|
return track_template_.rate_limit
|
||||||
|
|
||||||
|
rate_limit: Optional[timedelta] = info.rate_limit
|
||||||
|
return rate_limit
|
||||||
|
|
|
@ -72,7 +72,7 @@ _COLLECTABLE_STATE_ATTRIBUTES = {
|
||||||
"name",
|
"name",
|
||||||
}
|
}
|
||||||
|
|
||||||
DEFAULT_RATE_LIMIT = timedelta(seconds=1)
|
DEFAULT_RATE_LIMIT = timedelta(minutes=1)
|
||||||
|
|
||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
|
@ -489,26 +489,6 @@ class Template:
|
||||||
return 'Template("' + self.template + '")'
|
return 'Template("' + self.template + '")'
|
||||||
|
|
||||||
|
|
||||||
class RateLimit:
|
|
||||||
"""Class to control update rate limits."""
|
|
||||||
|
|
||||||
def __init__(self, hass: HomeAssistantType):
|
|
||||||
"""Initialize rate limit."""
|
|
||||||
self._hass = hass
|
|
||||||
|
|
||||||
def __call__(self, *args: Any, **kwargs: Any) -> str:
|
|
||||||
"""Handle a call to the class."""
|
|
||||||
render_info = self._hass.data.get(_RENDER_INFO)
|
|
||||||
if render_info is not None:
|
|
||||||
render_info.rate_limit = timedelta(*args, **kwargs)
|
|
||||||
|
|
||||||
return ""
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
"""Representation of a RateLimit."""
|
|
||||||
return "<template RateLimit>"
|
|
||||||
|
|
||||||
|
|
||||||
class AllStates:
|
class AllStates:
|
||||||
"""Class to expose all HA states as attributes."""
|
"""Class to expose all HA states as attributes."""
|
||||||
|
|
||||||
|
@ -1310,11 +1290,10 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
||||||
self.globals["is_state_attr"] = hassfunction(is_state_attr)
|
self.globals["is_state_attr"] = hassfunction(is_state_attr)
|
||||||
self.globals["state_attr"] = hassfunction(state_attr)
|
self.globals["state_attr"] = hassfunction(state_attr)
|
||||||
self.globals["states"] = AllStates(hass)
|
self.globals["states"] = AllStates(hass)
|
||||||
self.globals["rate_limit"] = RateLimit(hass)
|
|
||||||
|
|
||||||
def is_safe_callable(self, obj):
|
def is_safe_callable(self, obj):
|
||||||
"""Test if callback is safe."""
|
"""Test if callback is safe."""
|
||||||
return isinstance(obj, (AllStates, RateLimit)) or super().is_safe_callable(obj)
|
return isinstance(obj, AllStates) or super().is_safe_callable(obj)
|
||||||
|
|
||||||
def is_safe_attribute(self, obj, attr, value):
|
def is_safe_attribute(self, obj, attr, value):
|
||||||
"""Test if attribute is safe."""
|
"""Test if attribute is safe."""
|
||||||
|
|
|
@ -927,7 +927,6 @@ async def test_track_template_result_complex(hass):
|
||||||
"""Test tracking template."""
|
"""Test tracking template."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
template_complex_str = """
|
template_complex_str = """
|
||||||
{{ rate_limit(seconds=0) }}
|
|
||||||
{% if states("sensor.domain") == "light" %}
|
{% if states("sensor.domain") == "light" %}
|
||||||
{{ states.light | map(attribute='entity_id') | list }}
|
{{ states.light | map(attribute='entity_id') | list }}
|
||||||
{% elif states("sensor.domain") == "lock" %}
|
{% elif states("sensor.domain") == "lock" %}
|
||||||
|
@ -948,7 +947,9 @@ async def test_track_template_result_complex(hass):
|
||||||
hass.states.async_set("lock.one", "locked")
|
hass.states.async_set("lock.one", "locked")
|
||||||
|
|
||||||
info = async_track_template_result(
|
info = async_track_template_result(
|
||||||
hass, [TrackTemplate(template_complex, None)], specific_run_callback
|
hass,
|
||||||
|
[TrackTemplate(template_complex, None, timedelta(seconds=0))],
|
||||||
|
specific_run_callback,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
@ -1236,7 +1237,7 @@ async def test_track_template_result_iterator(hass):
|
||||||
[
|
[
|
||||||
TrackTemplate(
|
TrackTemplate(
|
||||||
Template(
|
Template(
|
||||||
"""{{ rate_limit(seconds=0) }}
|
"""
|
||||||
{% for state in states.sensor %}
|
{% for state in states.sensor %}
|
||||||
{% if state.state == 'on' %}
|
{% if state.state == 'on' %}
|
||||||
{{ state.entity_id }},
|
{{ state.entity_id }},
|
||||||
|
@ -1246,6 +1247,7 @@ async def test_track_template_result_iterator(hass):
|
||||||
hass,
|
hass,
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
timedelta(seconds=0),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
iterator_callback,
|
iterator_callback,
|
||||||
|
@ -1268,11 +1270,12 @@ async def test_track_template_result_iterator(hass):
|
||||||
[
|
[
|
||||||
TrackTemplate(
|
TrackTemplate(
|
||||||
Template(
|
Template(
|
||||||
"""{{ rate_limit(seconds=0) }}{{ states.sensor|selectattr("state","equalto","on")
|
"""{{ states.sensor|selectattr("state","equalto","on")
|
||||||
|join(",", attribute="entity_id") }}""",
|
|join(",", attribute="entity_id") }}""",
|
||||||
hass,
|
hass,
|
||||||
),
|
),
|
||||||
None,
|
None,
|
||||||
|
timedelta(seconds=0),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
filter_callback,
|
filter_callback,
|
||||||
|
@ -1452,62 +1455,6 @@ async def test_track_template_rate_limit(hass):
|
||||||
assert refresh_runs == ["0", "1", "2", "4"]
|
assert refresh_runs == ["0", "1", "2", "4"]
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_rate_limit_overridden(hass):
|
|
||||||
"""Test template rate limit can be overridden from the template."""
|
|
||||||
template_refresh = Template(
|
|
||||||
"{% set x = rate_limit(seconds=0.1) %}{{ 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=5))],
|
|
||||||
refresh_listener,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
info.async_refresh()
|
|
||||||
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()
|
|
||||||
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()
|
|
||||||
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"]
|
|
||||||
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()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2", "4"]
|
|
||||||
hass.states.async_set("sensor.five", "any")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2", "4"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_rate_limit_five(hass):
|
async def test_track_template_rate_limit_five(hass):
|
||||||
"""Test template rate limit of 5 seconds."""
|
"""Test template rate limit of 5 seconds."""
|
||||||
template_refresh = Template("{{ states | count }}", hass)
|
template_refresh = Template("{{ states | count }}", hass)
|
||||||
|
@ -1541,19 +1488,11 @@ async def test_track_template_rate_limit_five(hass):
|
||||||
assert refresh_runs == ["0", "1"]
|
assert refresh_runs == ["0", "1"]
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_rate_limit_changes(hass):
|
async def test_specifically_referenced_entity_is_not_rate_limited(hass):
|
||||||
"""Test template rate limit can be changed."""
|
"""Test template rate limit of 5 seconds."""
|
||||||
template_refresh = Template(
|
hass.states.async_set("sensor.one", "none")
|
||||||
"""
|
|
||||||
{% if states.sensor.two.state == "any" %}
|
template_refresh = Template('{{ states | count }}_{{ states("sensor.one") }}', hass)
|
||||||
{% set x = rate_limit(seconds=5) %}
|
|
||||||
{% else %}
|
|
||||||
{% set x = rate_limit(seconds=0.1) %}
|
|
||||||
{% endif %}
|
|
||||||
{{ states | count }}
|
|
||||||
""",
|
|
||||||
hass,
|
|
||||||
)
|
|
||||||
|
|
||||||
refresh_runs = []
|
refresh_runs = []
|
||||||
|
|
||||||
|
@ -1563,114 +1502,34 @@ async def test_track_template_rate_limit_changes(hass):
|
||||||
|
|
||||||
info = async_track_template_result(
|
info = async_track_template_result(
|
||||||
hass,
|
hass,
|
||||||
[TrackTemplate(template_refresh, None)],
|
[TrackTemplate(template_refresh, None, timedelta(seconds=5))],
|
||||||
refresh_listener,
|
refresh_listener,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
info.async_refresh()
|
info.async_refresh()
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
assert refresh_runs == ["0"]
|
assert refresh_runs == ["1_none"]
|
||||||
hass.states.async_set("sensor.one", "any")
|
hass.states.async_set("sensor.one", "any")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert refresh_runs == ["0"]
|
assert refresh_runs == ["1_none", "1_any"]
|
||||||
info.async_refresh()
|
info.async_refresh()
|
||||||
assert refresh_runs == ["0", "1"]
|
assert refresh_runs == ["1_none", "1_any"]
|
||||||
hass.states.async_set("sensor.two", "any")
|
hass.states.async_set("sensor.two", "any")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert refresh_runs == ["0", "1"]
|
assert refresh_runs == ["1_none", "1_any"]
|
||||||
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1)
|
|
||||||
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()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2"]
|
|
||||||
hass.states.async_set("sensor.three", "any")
|
hass.states.async_set("sensor.three", "any")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert refresh_runs == ["0", "1", "2"]
|
assert refresh_runs == ["1_none", "1_any"]
|
||||||
hass.states.async_set("sensor.four", "any")
|
hass.states.async_set("sensor.one", "none")
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert refresh_runs == ["0", "1", "2"]
|
assert refresh_runs == ["1_none", "1_any", "3_none"]
|
||||||
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()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2"]
|
|
||||||
hass.states.async_set("sensor.five", "any")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_rate_limit_removed(hass):
|
|
||||||
"""Test template rate limit can be removed."""
|
|
||||||
template_refresh = Template(
|
|
||||||
"""
|
|
||||||
{% if states.sensor.two.state == "any" %}
|
|
||||||
{% set x = rate_limit(0) %}
|
|
||||||
{% else %}
|
|
||||||
{% set x = rate_limit(seconds=0.1) %}
|
|
||||||
{% endif %}
|
|
||||||
{{ 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)],
|
|
||||||
refresh_listener,
|
|
||||||
)
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
info.async_refresh()
|
|
||||||
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()
|
|
||||||
assert refresh_runs == ["0", "1"]
|
|
||||||
next_time = dt_util.utcnow() + timedelta(seconds=0.125 * 1)
|
|
||||||
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()
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
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", "3"]
|
|
||||||
hass.states.async_set("sensor.four", "any")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2", "3", "4"]
|
|
||||||
hass.states.async_set("sensor.five", "any")
|
|
||||||
await hass.async_block_till_done()
|
|
||||||
assert refresh_runs == ["0", "1", "2", "3", "4", "5"]
|
|
||||||
|
|
||||||
|
|
||||||
async def test_track_two_templates_with_different_rate_limits(hass):
|
async def test_track_two_templates_with_different_rate_limits(hass):
|
||||||
"""Test two templates with different rate limits."""
|
"""Test two templates with different rate limits."""
|
||||||
template_one = Template(
|
template_one = Template("{{ states | count }} ", hass)
|
||||||
"{% set x = rate_limit(seconds=0.1) %}{{ states | count }}", hass
|
template_five = Template("{{ states | count }}", hass)
|
||||||
)
|
|
||||||
template_five = Template(
|
|
||||||
"{% set x = rate_limit(seconds=5) %}{{ states | count }}", hass
|
|
||||||
)
|
|
||||||
|
|
||||||
refresh_runs = {
|
refresh_runs = {
|
||||||
template_one: [],
|
template_one: [],
|
||||||
|
@ -1684,7 +1543,10 @@ async def test_track_two_templates_with_different_rate_limits(hass):
|
||||||
|
|
||||||
info = async_track_template_result(
|
info = async_track_template_result(
|
||||||
hass,
|
hass,
|
||||||
[TrackTemplate(template_one, None), TrackTemplate(template_five, None)],
|
[
|
||||||
|
TrackTemplate(template_one, None, timedelta(seconds=0.1)),
|
||||||
|
TrackTemplate(template_five, None, timedelta(seconds=5)),
|
||||||
|
],
|
||||||
refresh_listener,
|
refresh_listener,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1867,9 +1729,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
|
||||||
template_1 = Template("{{ states.switch.test.state == 'on' }}")
|
template_1 = Template("{{ states.switch.test.state == 'on' }}")
|
||||||
template_2 = Template("{{ states.switch.test.state == 'on' }}")
|
template_2 = Template("{{ states.switch.test.state == 'on' }}")
|
||||||
template_3 = Template("{{ states.switch.test.state == 'off' }}")
|
template_3 = Template("{{ states.switch.test.state == 'off' }}")
|
||||||
template_4 = Template(
|
template_4 = Template("{{ states.switch | map(attribute='entity_id') | list }}")
|
||||||
"{{ rate_limit(seconds=0) }}{{ states.switch | map(attribute='entity_id') | list }}"
|
|
||||||
)
|
|
||||||
|
|
||||||
refresh_runs = []
|
refresh_runs = []
|
||||||
|
|
||||||
|
@ -1883,7 +1743,7 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
|
||||||
TrackTemplate(template_1, None),
|
TrackTemplate(template_1, None),
|
||||||
TrackTemplate(template_2, None),
|
TrackTemplate(template_2, None),
|
||||||
TrackTemplate(template_3, None),
|
TrackTemplate(template_3, None),
|
||||||
TrackTemplate(template_4, None),
|
TrackTemplate(template_4, None, timedelta(seconds=0)),
|
||||||
],
|
],
|
||||||
refresh_listener,
|
refresh_listener,
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""Test Home Assistant template helper methods."""
|
"""Test Home Assistant template helper methods."""
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
|
|
||||||
|
@ -2617,28 +2617,3 @@ async def test_unavailable_states(hass):
|
||||||
hass,
|
hass,
|
||||||
)
|
)
|
||||||
assert tpl.async_render() == "light.none, light.unavailable, light.unknown"
|
assert tpl.async_render() == "light.none, light.unavailable, light.unknown"
|
||||||
|
|
||||||
|
|
||||||
async def test_rate_limit(hass):
|
|
||||||
"""Test we can pickup a rate limit directive."""
|
|
||||||
tmp = template.Template("{{ states | count }}", hass)
|
|
||||||
|
|
||||||
info = tmp.async_render_to_info()
|
|
||||||
assert info.rate_limit is None
|
|
||||||
|
|
||||||
tmp = template.Template("{{ rate_limit(minutes=1) }}{{ states | count }}", hass)
|
|
||||||
|
|
||||||
info = tmp.async_render_to_info()
|
|
||||||
assert info.rate_limit == timedelta(minutes=1)
|
|
||||||
|
|
||||||
tmp = template.Template("{{ rate_limit(minutes=1) }}random", hass)
|
|
||||||
|
|
||||||
info = tmp.async_render_to_info()
|
|
||||||
assert info.result() == "random"
|
|
||||||
assert info.rate_limit == timedelta(minutes=1)
|
|
||||||
|
|
||||||
tmp = template.Template("{{ rate_limit(seconds=0) }}random", hass)
|
|
||||||
|
|
||||||
info = tmp.async_render_to_info()
|
|
||||||
assert info.result() == "random"
|
|
||||||
assert info.rate_limit == timedelta(seconds=0)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue