From 20398cc0a6c56b4a50fdb9bfb93bb9300da8c37a Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 25 Aug 2020 17:20:04 -0500 Subject: [PATCH] Subscribe to state change events only if the template has entities (#39188) --- homeassistant/helpers/event.py | 6 --- tests/components/template/test_sensor.py | 65 ++++++++++++++++++++++- tests/components/template/test_trigger.py | 25 +++++++-- tests/helpers/test_event.py | 6 --- 4 files changed, 84 insertions(+), 18 deletions(-) diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index 0dc4764732f..6cd6a6c2cb9 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -484,12 +484,6 @@ class _TrackTemplateResultInfo: if self._info.exception: return True - # There are no entities in the template - # to track so this template will - # re-render on EVERY state change - if not self._info.domains and not self._info.entities: - return True - return False @callback diff --git a/tests/components/template/test_sensor.py b/tests/components/template/test_sensor.py index 3ffd9be4a64..2b4a34e8c4a 100644 --- a/tests/components/template/test_sensor.py +++ b/tests/components/template/test_sensor.py @@ -10,8 +10,10 @@ from homeassistant.const import ( STATE_ON, STATE_UNAVAILABLE, ) -from homeassistant.core import CoreState +from homeassistant.core import CoreState, callback +from homeassistant.helpers.template import Template from homeassistant.setup import ATTR_COMPONENT, async_setup_component, setup_component +import homeassistant.util.dt as dt_util from tests.common import assert_setup_component, get_test_home_assistant @@ -694,3 +696,64 @@ async def test_unique_id(hass): await hass.async_block_till_done() assert len(hass.states.async_all()) == 1 + + +async def test_sun_renders_once_per_sensor(hass): + """Test sun change renders the template only once per sensor.""" + + now = dt_util.utcnow() + hass.states.async_set( + "sun.sun", "above_horizon", {"elevation": 45.3, "next_rising": now} + ) + + await async_setup_component( + hass, + "sensor", + { + "sensor": { + "platform": "template", + "sensors": { + "solar_angle": { + "friendly_name": "Sun angle", + "unit_of_measurement": "degrees", + "value_template": "{{ state_attr('sun.sun', 'elevation') }}", + }, + "sunrise": { + "value_template": "{{ state_attr('sun.sun', 'next_rising') }}" + }, + }, + } + }, + ) + + await hass.async_block_till_done() + await hass.async_start() + await hass.async_block_till_done() + + assert len(hass.states.async_all()) == 3 + + assert hass.states.get("sensor.solar_angle").state == "45.3" + assert hass.states.get("sensor.sunrise").state == str(now) + + async_render_calls = [] + + @callback + def _record_async_render(self, *args, **kwargs): + """Catch async_render.""" + async_render_calls.append(self.template) + return "mocked" + + later = dt_util.utcnow() + + with patch.object(Template, "async_render", _record_async_render): + hass.states.async_set("sun.sun", {"elevation": 50, "next_rising": later}) + await hass.async_block_till_done() + + assert hass.states.get("sensor.solar_angle").state == "mocked" + assert hass.states.get("sensor.sunrise").state == "mocked" + + assert len(async_render_calls) == 2 + assert set(async_render_calls) == { + "{{ state_attr('sun.sun', 'elevation') }}", + "{{ state_attr('sun.sun', 'next_rising') }}", + } diff --git a/tests/components/template/test_trigger.py b/tests/components/template/test_trigger.py index 70455fa4c22..300173fdadf 100644 --- a/tests/components/template/test_trigger.py +++ b/tests/components/template/test_trigger.py @@ -39,7 +39,10 @@ async def test_if_fires_on_change_bool(hass, calls): automation.DOMAIN, { automation.DOMAIN: { - "trigger": {"platform": "template", "value_template": "{{ true }}"}, + "trigger": { + "platform": "template", + "value_template": "{{ states.test.entity.state and true }}", + }, "action": {"service": "test.automation"}, } }, @@ -64,7 +67,10 @@ async def test_if_fires_on_change_str(hass, calls): automation.DOMAIN, { automation.DOMAIN: { - "trigger": {"platform": "template", "value_template": '{{ "true" }}'}, + "trigger": { + "platform": "template", + "value_template": '{{ states.test.entity.state and "true" }}', + }, "action": {"service": "test.automation"}, } }, @@ -82,7 +88,10 @@ async def test_if_fires_on_change_str_crazy(hass, calls): automation.DOMAIN, { automation.DOMAIN: { - "trigger": {"platform": "template", "value_template": '{{ "TrUE" }}'}, + "trigger": { + "platform": "template", + "value_template": '{{ states.test.entity.state and "TrUE" }}', + }, "action": {"service": "test.automation"}, } }, @@ -100,7 +109,10 @@ async def test_if_not_fires_on_change_bool(hass, calls): automation.DOMAIN, { automation.DOMAIN: { - "trigger": {"platform": "template", "value_template": "{{ false }}"}, + "trigger": { + "platform": "template", + "value_template": "{{ states.test.entity.state and false }}", + }, "action": {"service": "test.automation"}, } }, @@ -178,7 +190,10 @@ async def test_if_fires_on_two_change(hass, calls): automation.DOMAIN, { automation.DOMAIN: { - "trigger": {"platform": "template", "value_template": "{{ true }}"}, + "trigger": { + "platform": "template", + "value_template": "{{ states.test.entity.state and true }}", + }, "action": {"service": "test.automation"}, } }, diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index a9a4277bae0..80f4dc9a09f 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -542,12 +542,6 @@ async def test_track_template_error(hass, caplog): assert "lunch" not in caplog.text assert "TemplateAssertionError" not in caplog.text - hass.states.async_set("switch.not_exist", "on") - await hass.async_block_till_done() - - assert "lunch" in caplog.text - assert "TemplateAssertionError" in caplog.text - async def test_track_template_result(hass): """Test tracking template."""