From ab17b4ab70eee5b4fada5f3d22477df972616c90 Mon Sep 17 00:00:00 2001
From: "J. Nick Koston" <nick@koston.org>
Date: Fri, 2 Oct 2020 07:27:39 -0500
Subject: [PATCH] Simplify template tracking and make it easier to follow
 (#41030)

---
 homeassistant/helpers/event.py | 134 ++++++++++++++++++++-------------
 1 file changed, 81 insertions(+), 53 deletions(-)

diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py
index 52a43fca3ff..d143ed1d106 100644
--- a/homeassistant/helpers/event.py
+++ b/homeassistant/helpers/event.py
@@ -774,16 +774,65 @@ class _TrackTemplateResultInfo:
         """Force recalculate the template."""
         self._refresh(None)
 
-    @callback
-    def _event_triggers_template(self, template: Template, event: Event) -> bool:
-        """Determine if a template should be re-rendered from an event."""
-        entity_id = event.data.get(ATTR_ENTITY_ID)
-        return (
-            self._info[template].filter(entity_id)
-            or event.data.get("new_state") is None
-            or event.data.get("old_state") is None
-            and self._info[template].filter_lifecycle(entity_id)
-        )
+    def _render_template_if_ready(
+        self,
+        track_template_: TrackTemplate,
+        now: datetime,
+        event: Optional[Event],
+    ) -> Union[bool, TrackTemplateResult]:
+        """Re-render the template if conditions match.
+
+        Returns False if the template was not be re-rendered
+
+        Returns True if the template re-rendered and did not
+        change.
+
+        Returns TrackTemplateResult if the template re-render
+        generates a new result.
+        """
+        template = track_template_.template
+
+        if event:
+            info = self._info[template]
+
+            if not self._rate_limit.async_has_timer(
+                template
+            ) and not _event_triggers_rerender(event, info):
+                return False
+
+            if self._rate_limit.async_schedule_action(
+                template,
+                info.rate_limit or track_template_.rate_limit,
+                now,
+                self._refresh,
+                event,
+            ):
+                return False
+
+            _LOGGER.debug(
+                "Template update %s triggered by event: %s",
+                template.template,
+                event,
+            )
+
+        self._rate_limit.async_triggered(template, now)
+        self._info[template] = template.async_render_to_info(track_template_.variables)
+
+        try:
+            result: Union[str, TemplateError] = self._info[template].result()
+        except TemplateError as ex:
+            result = ex
+
+        last_result = self._last_result.get(template)
+
+        # Check to see if the result has changed
+        if result == last_result:
+            return True
+
+        if isinstance(result, TemplateError) and isinstance(last_result, TemplateError):
+            return True
+
+        return TrackTemplateResult(template, last_result, result)
 
     @callback
     def _refresh(self, event: Optional[Event]) -> None:
@@ -792,51 +841,13 @@ class _TrackTemplateResultInfo:
         now = dt_util.utcnow()
 
         for track_template_ in self._track_templates:
-            template = track_template_.template
-            if event:
-                if not self._rate_limit.async_has_timer(
-                    template
-                ) and not self._event_triggers_template(template, event):
-                    continue
-
-                if self._rate_limit.async_schedule_action(
-                    template,
-                    self._info[template].rate_limit or track_template_.rate_limit,
-                    now,
-                    self._refresh,
-                    event,
-                ):
-                    continue
-
-                _LOGGER.debug(
-                    "Template update %s triggered by event: %s",
-                    template.template,
-                    event,
-                )
-
-            self._rate_limit.async_triggered(template, now)
-            self._info[template] = template.async_render_to_info(
-                track_template_.variables
-            )
-            info_changed = True
-
-            try:
-                result: Union[str, TemplateError] = self._info[template].result()
-            except TemplateError as ex:
-                result = ex
-
-            last_result = self._last_result.get(template)
-
-            # Check to see if the result has changed
-            if result == last_result:
-                continue
-
-            if isinstance(result, TemplateError) and isinstance(
-                last_result, TemplateError
-            ):
+            update = self._render_template_if_ready(track_template_, now, event)
+            if not update:
                 continue
 
-            updates.append(TrackTemplateResult(template, last_result, result))
+            info_changed = True
+            if isinstance(update, TrackTemplateResult):
+                updates.append(update)
 
         if info_changed:
             assert self._track_state_changes
@@ -1348,3 +1359,20 @@ def _render_infos_to_track_states(render_infos: Iterable[RenderInfo]) -> TrackSt
         return TrackStates(True, set(), set())
 
     return TrackStates(False, *_entities_domains_from_render_infos(render_infos))
+
+
+@callback
+def _event_triggers_rerender(event: Event, info: RenderInfo) -> bool:
+    """Determine if a template should be re-rendered from an event."""
+    entity_id = event.data.get(ATTR_ENTITY_ID)
+
+    if info.filter(entity_id):
+        return True
+
+    if (
+        event.data.get("new_state") is not None
+        and event.data.get("old_state") is not None
+    ):
+        return False
+
+    return bool(info.filter_lifecycle(entity_id))
-- 
GitLab