From 472b12bef5d0b2e63e75cef5936876ecf56653ec Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 16 Aug 2020 11:16:28 -0500 Subject: [PATCH] Update TrackTemplateResultInfo to remove side effects from init (#38934) * Verify and case * Review comments * Update homeassistant/helpers/event.py Co-authored-by: Martin Hjelmare * Update homeassistant/helpers/event.py Co-authored-by: Martin Hjelmare Co-authored-by: Martin Hjelmare --- homeassistant/helpers/event.py | 28 +++++++++++++++++---- tests/helpers/test_event.py | 46 ++++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 6c7d6771a13..dd1d39a0a9b 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -28,7 +28,7 @@ from homeassistant.core import ( from homeassistant.exceptions import TemplateError from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED from homeassistant.helpers.sun import get_astral_event_next -from homeassistant.helpers.template import Template, result_as_boolean +from homeassistant.helpers.template import RenderInfo, Template, result_as_boolean from homeassistant.helpers.typing import TemplateVarsType from homeassistant.loader import bind_hass from homeassistant.util import dt as dt_util @@ -431,7 +431,7 @@ track_template = threaded_listener_factory(async_track_template) _UNCHANGED = object() -class TrackTemplateResultInfo: +class _TrackTemplateResultInfo: """Handle removal / refresh of tracker.""" def __init__( @@ -451,7 +451,12 @@ class TrackTemplateResultInfo: self._all_listener: Optional[Callable] = None self._domains_listener: Optional[Callable] = None self._entities_listener: Optional[Callable] = None - self._info = template.async_render_to_info(variables) + self._info: Optional[RenderInfo] = None + self._last_info: Optional[RenderInfo] = None + + def async_setup(self) -> None: + """Activation of template tracking.""" + self._info = self._template.async_render_to_info(self._variables) if self._info.exception: self._last_exception = True _LOGGER.exception(self._info.exception) @@ -460,6 +465,8 @@ class TrackTemplateResultInfo: @property def _needs_all_listener(self) -> bool: + assert self._info + # Tracking all states if self._info.all_states: return True @@ -480,6 +487,8 @@ class TrackTemplateResultInfo: @callback def _create_listeners(self) -> None: + assert self._info + if self._info.is_static: return @@ -516,6 +525,9 @@ class TrackTemplateResultInfo: @callback def _update_listeners(self) -> None: + assert self._info + assert self._last_info + if self._needs_all_listener: if self._all_listener: return @@ -544,6 +556,8 @@ class TrackTemplateResultInfo: @callback def _setup_entities_listener(self) -> None: + assert self._info + entities = set(self._info.entities) for entity_id in self.hass.states.async_entity_ids(self._info.domains): entities.add(entity_id) @@ -553,6 +567,8 @@ class TrackTemplateResultInfo: @callback def _setup_domains_listener(self) -> None: + assert self._info + self._domains_listener = async_track_state_added_domain( self.hass, self._info.domains, self._refresh ) @@ -631,7 +647,7 @@ def async_track_template_result( template: Template, action: TrackTemplateResultListener, variables: Optional[TemplateVarsType] = None, -) -> TrackTemplateResultInfo: +) -> _TrackTemplateResultInfo: """Add a listener that fires when a the result of a template changes. The action will fire with the initial result from the template, and @@ -662,7 +678,9 @@ def async_track_template_result( Info object used to unregister the listener, and refresh the template. """ - return TrackTemplateResultInfo(hass, template, action, variables) + tracker = _TrackTemplateResultInfo(hass, template, action, variables) + tracker.async_setup() + return tracker @callback diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index b61d8a9365c..00daf6b9574 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -796,6 +796,52 @@ async def test_track_template_result_with_group(hass): assert specific_runs[-1] == str(100.1 + 200.2 + 0 + 800.8) +async def test_track_template_result_and_conditional(hass): + """Test tracking template with an and conditional.""" + specific_runs = [] + hass.states.async_set("light.a", "off") + hass.states.async_set("light.b", "off") + template_str = '{% if states.light.a.state == "on" and states.light.b.state == "on" %}on{% else %}off{% endif %}' + + template = Template(template_str, hass) + + def specific_run_callback(event, template, old_result, new_result): + import pprint + + pprint.pprint([event, template, old_result, new_result]) + specific_runs.append(new_result) + + async_track_template_result(hass, template, specific_run_callback) + await hass.async_block_till_done() + + hass.states.async_set("light.b", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 0 + + hass.states.async_set("light.a", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 1 + assert specific_runs[0] == "on" + + hass.states.async_set("light.b", "off") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + assert specific_runs[1] == "off" + + hass.states.async_set("light.a", "off") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + hass.states.async_set("light.b", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 2 + + hass.states.async_set("light.a", "on") + await hass.async_block_till_done() + assert len(specific_runs) == 3 + assert specific_runs[2] == "on" + + async def test_track_template_result_iterator(hass): """Test tracking template.""" iterator_runs = []