Avoid falling back to listening for all states when a template render raises an exception (#89392)
When a template render raised an exception we would start listening for all states until the template did not raise an exception anymore. This was not needed since the entity that is causing the exception was already in the tracker. Re-rendering on all state changes can be extremely expensive and can bring an instance into a sluggish or unresponsive state when updating from a much older version that did not raise ValueError when a default was missing.pull/89366/head^2
parent
84b5ea8ac0
commit
cefba7c638
|
@ -838,6 +838,10 @@ class TrackTemplateResultInfo:
|
|||
self._track_state_changes: _TrackStateChangeFiltered | None = None
|
||||
self._time_listeners: dict[Template, Callable[[], None]] = {}
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return the representation."""
|
||||
return f"<TrackTemplateResultInfo {self._info}>"
|
||||
|
||||
def async_setup(self, raise_on_template_error: bool, strict: bool = False) -> None:
|
||||
"""Activation of template tracking."""
|
||||
block_render = False
|
||||
|
@ -1651,12 +1655,6 @@ def _render_infos_needs_all_listener(render_infos: Iterable[RenderInfo]) -> bool
|
|||
if render_info.all_states or render_info.all_states_lifecycle:
|
||||
return True
|
||||
|
||||
# Previous call had an exception
|
||||
# so we do not know which states
|
||||
# to track
|
||||
if render_info.exception:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
|
|
|
@ -274,6 +274,8 @@ class RenderInfo:
|
|||
f" entities={self.entities}"
|
||||
f" rate_limit={self.rate_limit}"
|
||||
f" has_time={self.has_time}"
|
||||
f" exception={self.exception}"
|
||||
f" is_static={self.is_static}"
|
||||
">"
|
||||
)
|
||||
|
||||
|
|
|
@ -2209,7 +2209,7 @@ async def test_track_template_result_errors(
|
|||
hass.states.async_set("switch.not_exist", "on")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(syntax_error_runs) == 1
|
||||
assert len(syntax_error_runs) == 0
|
||||
assert len(not_exist_runs) == 2
|
||||
assert not_exist_runs[1][0].data.get("entity_id") == "switch.not_exist"
|
||||
assert not_exist_runs[1][1] == template_not_exist
|
||||
|
@ -2229,6 +2229,56 @@ async def test_track_template_result_errors(
|
|||
assert isinstance(not_exist_runs[2][3], TemplateError)
|
||||
|
||||
|
||||
async def test_track_template_result_transient_errors(
|
||||
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
|
||||
) -> None:
|
||||
"""Test tracking template with transient errors in the template."""
|
||||
hass.states.async_set("sensor.error", "unknown")
|
||||
template_that_raises_sometimes = Template(
|
||||
"{{ states('sensor.error') | float }}", hass
|
||||
)
|
||||
|
||||
sometimes_error_runs = []
|
||||
|
||||
@ha.callback
|
||||
def sometimes_error_listener(event, updates):
|
||||
track_result = updates.pop()
|
||||
sometimes_error_runs.append(
|
||||
(
|
||||
event,
|
||||
track_result.template,
|
||||
track_result.last_result,
|
||||
track_result.result,
|
||||
)
|
||||
)
|
||||
|
||||
info = async_track_template_result(
|
||||
hass,
|
||||
[TrackTemplate(template_that_raises_sometimes, None)],
|
||||
sometimes_error_listener,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert sometimes_error_runs == []
|
||||
assert "ValueError" in caplog.text
|
||||
assert "ValueError" in repr(info)
|
||||
caplog.clear()
|
||||
|
||||
hass.states.async_set("sensor.error", "unavailable")
|
||||
await hass.async_block_till_done()
|
||||
assert len(sometimes_error_runs) == 1
|
||||
assert isinstance(sometimes_error_runs[0][3], TemplateError)
|
||||
sometimes_error_runs.clear()
|
||||
assert "ValueError" in repr(info)
|
||||
|
||||
hass.states.async_set("sensor.error", "4")
|
||||
await hass.async_block_till_done()
|
||||
assert len(sometimes_error_runs) == 1
|
||||
assert sometimes_error_runs[0][3] == 4.0
|
||||
sometimes_error_runs.clear()
|
||||
assert "ValueError" not in repr(info)
|
||||
|
||||
|
||||
async def test_static_string(hass: HomeAssistant) -> None:
|
||||
"""Test a static string."""
|
||||
template_refresh = Template("{{ 'static' }}", hass)
|
||||
|
|
|
@ -4323,3 +4323,14 @@ def test_contains(hass: HomeAssistant, seq, value, expected) -> None:
|
|||
)
|
||||
== expected
|
||||
)
|
||||
|
||||
|
||||
async def test_render_to_info_with_exception(hass: HomeAssistant) -> None:
|
||||
"""Test info is still available if the template has an exception."""
|
||||
hass.states.async_set("test_domain.object", "dog")
|
||||
info = render_to_info(hass, '{{ states("test_domain.object") | float }}')
|
||||
with pytest.raises(TemplateError, match="no default was specified"):
|
||||
info.result()
|
||||
|
||||
assert info.all_states is False
|
||||
assert info.entities == {"test_domain.object"}
|
||||
|
|
Loading…
Reference in New Issue