Improve startup of unavailable template entities (#59827)
This commit is contained in:
parent
053c456199
commit
39d6aba3bc
2 changed files with 324 additions and 12 deletions
|
@ -789,7 +789,32 @@ class _TrackTemplateResultInfo:
|
||||||
|
|
||||||
def async_setup(self, raise_on_template_error: bool, strict: bool = False) -> None:
|
def async_setup(self, raise_on_template_error: bool, strict: bool = False) -> None:
|
||||||
"""Activation of template tracking."""
|
"""Activation of template tracking."""
|
||||||
|
block_render = False
|
||||||
|
super_template = self._track_templates[0] if self._has_super_template else None
|
||||||
|
|
||||||
|
# Render the super template first
|
||||||
|
if super_template is not None:
|
||||||
|
template = super_template.template
|
||||||
|
variables = super_template.variables
|
||||||
|
self._info[template] = info = template.async_render_to_info(
|
||||||
|
variables, strict=strict
|
||||||
|
)
|
||||||
|
|
||||||
|
# If the super template did not render to True, don't update other templates
|
||||||
|
try:
|
||||||
|
super_result: str | TemplateError = info.result()
|
||||||
|
except TemplateError as ex:
|
||||||
|
super_result = ex
|
||||||
|
if (
|
||||||
|
super_result is not None
|
||||||
|
and self._super_template_as_boolean(super_result) is not True
|
||||||
|
):
|
||||||
|
block_render = True
|
||||||
|
|
||||||
|
# Then update the remaining templates unless blocked by the super template
|
||||||
for track_template_ in self._track_templates:
|
for track_template_ in self._track_templates:
|
||||||
|
if block_render or track_template_ == super_template:
|
||||||
|
continue
|
||||||
template = track_template_.template
|
template = track_template_.template
|
||||||
variables = track_template_.variables
|
variables = track_template_.variables
|
||||||
self._info[template] = info = template.async_render_to_info(
|
self._info[template] = info = template.async_render_to_info(
|
||||||
|
@ -810,9 +835,10 @@ class _TrackTemplateResultInfo:
|
||||||
)
|
)
|
||||||
self._update_time_listeners()
|
self._update_time_listeners()
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Template group %s listens for %s",
|
"Template group %s listens for %s, first render blocker by super template: %s",
|
||||||
self._track_templates,
|
self._track_templates,
|
||||||
self.listeners,
|
self.listeners,
|
||||||
|
block_render,
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -932,6 +958,14 @@ class _TrackTemplateResultInfo:
|
||||||
|
|
||||||
return TrackTemplateResult(template, last_result, result)
|
return TrackTemplateResult(template, last_result, result)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _super_template_as_boolean(result: bool | str | TemplateError) -> bool:
|
||||||
|
"""Return True if the result is truthy or a TemplateError."""
|
||||||
|
if isinstance(result, TemplateError):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return result_as_boolean(result)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def _refresh(
|
def _refresh(
|
||||||
self,
|
self,
|
||||||
|
@ -974,13 +1008,6 @@ class _TrackTemplateResultInfo:
|
||||||
|
|
||||||
track_templates = track_templates or self._track_templates
|
track_templates = track_templates or self._track_templates
|
||||||
|
|
||||||
def _super_template_as_boolean(result: bool | str | TemplateError) -> bool:
|
|
||||||
"""Return True if the result is truthy or a TemplateError."""
|
|
||||||
if isinstance(result, TemplateError):
|
|
||||||
return True
|
|
||||||
|
|
||||||
return result_as_boolean(result)
|
|
||||||
|
|
||||||
# Update the super template first
|
# Update the super template first
|
||||||
if super_template is not None:
|
if super_template is not None:
|
||||||
update = self._render_template_if_ready(super_template, now, event)
|
update = self._render_template_if_ready(super_template, now, event)
|
||||||
|
@ -994,14 +1021,14 @@ class _TrackTemplateResultInfo:
|
||||||
# If the super template did not render to True, don't update other templates
|
# If the super template did not render to True, don't update other templates
|
||||||
if (
|
if (
|
||||||
super_result is not None
|
super_result is not None
|
||||||
and _super_template_as_boolean(super_result) is not True
|
and self._super_template_as_boolean(super_result) is not True
|
||||||
):
|
):
|
||||||
block_updates = True
|
block_updates = True
|
||||||
|
|
||||||
if (
|
if (
|
||||||
isinstance(update, TrackTemplateResult)
|
isinstance(update, TrackTemplateResult)
|
||||||
and _super_template_as_boolean(update.last_result) is not True
|
and self._super_template_as_boolean(update.last_result) is not True
|
||||||
and _super_template_as_boolean(update.result) is True
|
and self._super_template_as_boolean(update.result) is True
|
||||||
):
|
):
|
||||||
# Super template changed from not True to True, force re-render
|
# Super template changed from not True to True, force re-render
|
||||||
# of all templates in the group
|
# of all templates in the group
|
||||||
|
@ -1030,9 +1057,10 @@ class _TrackTemplateResultInfo:
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"Template group %s listens for %s",
|
"Template group %s listens for %s, re-render blocker by super template: %s",
|
||||||
self._track_templates,
|
self._track_templates,
|
||||||
self.listeners,
|
self.listeners,
|
||||||
|
block_updates,
|
||||||
)
|
)
|
||||||
|
|
||||||
if not updates:
|
if not updates:
|
||||||
|
|
|
@ -1233,6 +1233,151 @@ async def test_track_template_result_super_template(hass):
|
||||||
assert len(wildercard_runs_availability) == 6
|
assert len(wildercard_runs_availability) == 6
|
||||||
|
|
||||||
|
|
||||||
|
async def test_track_template_result_super_template_initially_false(hass):
|
||||||
|
"""Test tracking template with super template listening to same entity."""
|
||||||
|
specific_runs = []
|
||||||
|
specific_runs_availability = []
|
||||||
|
wildcard_runs = []
|
||||||
|
wildcard_runs_availability = []
|
||||||
|
wildercard_runs = []
|
||||||
|
wildercard_runs_availability = []
|
||||||
|
|
||||||
|
template_availability = Template("{{ is_number(states('sensor.test')) }}", hass)
|
||||||
|
template_condition = Template("{{states.sensor.test.state}}", hass)
|
||||||
|
template_condition_var = Template(
|
||||||
|
"{{(states.sensor.test.state|int) + test }}", hass
|
||||||
|
)
|
||||||
|
|
||||||
|
# Make the super template initially false
|
||||||
|
hass.states.async_set("sensor.test", "unavailable")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
def specific_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition:
|
||||||
|
specific_runs.append(int(track_result.result))
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
specific_runs_availability.append(track_result.result)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition, None),
|
||||||
|
],
|
||||||
|
specific_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def wildcard_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition:
|
||||||
|
wildcard_runs.append(
|
||||||
|
(int(track_result.last_result or 0), int(track_result.result))
|
||||||
|
)
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
wildcard_runs_availability.append(track_result.result)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition, None),
|
||||||
|
],
|
||||||
|
wildcard_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def wildercard_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition_var:
|
||||||
|
wildercard_runs.append(
|
||||||
|
(int(track_result.last_result or 0), int(track_result.result))
|
||||||
|
)
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
wildercard_runs_availability.append(track_result.result)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition_var, {"test": 5}),
|
||||||
|
],
|
||||||
|
wildercard_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == []
|
||||||
|
assert wildcard_runs_availability == []
|
||||||
|
assert wildercard_runs_availability == []
|
||||||
|
assert specific_runs == []
|
||||||
|
assert wildcard_runs == []
|
||||||
|
assert wildercard_runs == []
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 5)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True]
|
||||||
|
assert wildcard_runs_availability == [True]
|
||||||
|
assert wildercard_runs_availability == [True]
|
||||||
|
assert specific_runs == [5]
|
||||||
|
assert wildcard_runs == [(0, 5)]
|
||||||
|
assert wildercard_runs == [(0, 10)]
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", "unknown")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True, False]
|
||||||
|
assert wildcard_runs_availability == [True, False]
|
||||||
|
assert wildercard_runs_availability == [True, False]
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 30)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True, False, True]
|
||||||
|
assert wildcard_runs_availability == [True, False, True]
|
||||||
|
assert wildercard_runs_availability == [True, False, True]
|
||||||
|
|
||||||
|
assert specific_runs == [5, 30]
|
||||||
|
assert wildcard_runs == [(0, 5), (5, 30)]
|
||||||
|
assert wildercard_runs == [(0, 10), (10, 35)]
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", "other")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 30)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(specific_runs) == 2
|
||||||
|
assert len(wildcard_runs) == 2
|
||||||
|
assert len(wildercard_runs) == 2
|
||||||
|
assert len(specific_runs_availability) == 5
|
||||||
|
assert len(wildcard_runs_availability) == 5
|
||||||
|
assert len(wildercard_runs_availability) == 5
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 30)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(specific_runs) == 2
|
||||||
|
assert len(wildcard_runs) == 2
|
||||||
|
assert len(wildercard_runs) == 2
|
||||||
|
assert len(specific_runs_availability) == 5
|
||||||
|
assert len(wildcard_runs_availability) == 5
|
||||||
|
assert len(wildercard_runs_availability) == 5
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 31)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert len(specific_runs) == 3
|
||||||
|
assert len(wildcard_runs) == 3
|
||||||
|
assert len(wildercard_runs) == 3
|
||||||
|
assert len(specific_runs_availability) == 5
|
||||||
|
assert len(wildcard_runs_availability) == 5
|
||||||
|
assert len(wildercard_runs_availability) == 5
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"availability_template",
|
"availability_template",
|
||||||
[
|
[
|
||||||
|
@ -1370,6 +1515,145 @@ async def test_track_template_result_super_template_2(hass, availability_templat
|
||||||
assert wildercard_runs == [(0, 10), (10, 35)]
|
assert wildercard_runs == [(0, 10), (10, 35)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"availability_template",
|
||||||
|
[
|
||||||
|
"{{ states('sensor.test2') != 'unavailable' }}",
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} true {%- else -%} false {%- endif %}",
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} 1 {%- else -%} 0 {%- endif %}",
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} yes {%- else -%} no {%- endif %}",
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} on {%- else -%} off {%- endif %}",
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} enable {%- else -%} disable {%- endif %}",
|
||||||
|
# This will throw when sensor.test2 is not "unavailable"
|
||||||
|
"{% if states('sensor.test2') != 'unavailable' -%} {{'a' + 5}} {%- else -%} false {%- endif %}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_track_template_result_super_template_2_initially_false(
|
||||||
|
hass, availability_template
|
||||||
|
):
|
||||||
|
"""Test tracking template with super template listening to different entities."""
|
||||||
|
specific_runs = []
|
||||||
|
specific_runs_availability = []
|
||||||
|
wildcard_runs = []
|
||||||
|
wildcard_runs_availability = []
|
||||||
|
wildercard_runs = []
|
||||||
|
wildercard_runs_availability = []
|
||||||
|
|
||||||
|
template_availability = Template(availability_template)
|
||||||
|
template_condition = Template("{{states.sensor.test.state}}", hass)
|
||||||
|
template_condition_var = Template(
|
||||||
|
"{{(states.sensor.test.state|int) + test }}", hass
|
||||||
|
)
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test2", "unavailable")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
def _super_template_as_boolean(result):
|
||||||
|
if isinstance(result, TemplateError):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return result_as_boolean(result)
|
||||||
|
|
||||||
|
def specific_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition:
|
||||||
|
specific_runs.append(int(track_result.result))
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
specific_runs_availability.append(
|
||||||
|
_super_template_as_boolean(track_result.result)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition, None),
|
||||||
|
],
|
||||||
|
specific_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@ha.callback
|
||||||
|
def wildcard_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition:
|
||||||
|
wildcard_runs.append(
|
||||||
|
(int(track_result.last_result or 0), int(track_result.result))
|
||||||
|
)
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
wildcard_runs_availability.append(
|
||||||
|
_super_template_as_boolean(track_result.result)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition, None),
|
||||||
|
],
|
||||||
|
wildcard_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
async def wildercard_run_callback(event, updates):
|
||||||
|
for track_result in updates:
|
||||||
|
if track_result.template is template_condition_var:
|
||||||
|
wildercard_runs.append(
|
||||||
|
(int(track_result.last_result or 0), int(track_result.result))
|
||||||
|
)
|
||||||
|
elif track_result.template is template_availability:
|
||||||
|
wildercard_runs_availability.append(
|
||||||
|
_super_template_as_boolean(track_result.result)
|
||||||
|
)
|
||||||
|
|
||||||
|
async_track_template_result(
|
||||||
|
hass,
|
||||||
|
[
|
||||||
|
TrackTemplate(template_availability, None),
|
||||||
|
TrackTemplate(template_condition_var, {"test": 5}),
|
||||||
|
],
|
||||||
|
wildercard_run_callback,
|
||||||
|
has_super_template=True,
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == []
|
||||||
|
assert wildcard_runs_availability == []
|
||||||
|
assert wildercard_runs_availability == []
|
||||||
|
assert specific_runs == []
|
||||||
|
assert wildcard_runs == []
|
||||||
|
assert wildercard_runs == []
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test", 5)
|
||||||
|
hass.states.async_set("sensor.test2", "available")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True]
|
||||||
|
assert wildcard_runs_availability == [True]
|
||||||
|
assert wildercard_runs_availability == [True]
|
||||||
|
assert specific_runs == [5]
|
||||||
|
assert wildcard_runs == [(0, 5)]
|
||||||
|
assert wildercard_runs == [(0, 10)]
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test2", "unknown")
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True]
|
||||||
|
assert wildcard_runs_availability == [True]
|
||||||
|
assert wildercard_runs_availability == [True]
|
||||||
|
|
||||||
|
hass.states.async_set("sensor.test2", "available")
|
||||||
|
hass.states.async_set("sensor.test", 30)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert specific_runs_availability == [True]
|
||||||
|
assert wildcard_runs_availability == [True]
|
||||||
|
assert wildercard_runs_availability == [True]
|
||||||
|
assert specific_runs == [5, 30]
|
||||||
|
assert wildcard_runs == [(0, 5), (5, 30)]
|
||||||
|
assert wildercard_runs == [(0, 10), (10, 35)]
|
||||||
|
|
||||||
|
|
||||||
async def test_track_template_result_complex(hass):
|
async def test_track_template_result_complex(hass):
|
||||||
"""Test tracking template."""
|
"""Test tracking template."""
|
||||||
specific_runs = []
|
specific_runs = []
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue