From f81606cbf58ca5acba39b47a3fd861ceab626951 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 11 Sep 2020 13:18:40 -0500 Subject: [PATCH] Return the listeners with the template result for the websocket api (#39925) --- .../components/websocket_api/commands.py | 8 +- homeassistant/helpers/event.py | 9 ++ .../components/websocket_api/test_commands.py | 25 ++++-- tests/helpers/test_event.py | 87 +++++++++++++++++-- 4 files changed, 118 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 04ad0ae3d3a..036cd690da2 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -253,9 +253,11 @@ def handle_render_template(hass, connection, msg): template.hass = hass variables = msg.get("variables") + info = None @callback def _template_listener(event, updates): + nonlocal info track_template_result = updates.pop() result = track_template_result.result if isinstance(result, TemplateError): @@ -267,7 +269,11 @@ def handle_render_template(hass, connection, msg): result = None - connection.send_message(messages.event_message(msg["id"], {"result": result})) + connection.send_message( + messages.event_message( + msg["id"], {"result": result, "listeners": info.listeners} # type: ignore + ) + ) info = async_track_template_result( hass, [TrackTemplate(template, variables)], _template_listener diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py index d9f1b8d9681..52ebe2d4de4 100644 --- a/homeassistant/helpers/event.py +++ b/homeassistant/helpers/event.py @@ -527,6 +527,15 @@ class _TrackTemplateResultInfo: self._last_info = self._info.copy() self._create_listeners() + @property + def listeners(self) -> Dict: + """State changes that will cause a re-render.""" + return { + "all": self._all_listener is not None, + "entities": self._last_entities, + "domains": self._last_domains, + } + @property def _needs_all_listener(self) -> bool: for track_template_ in self._track_templates: diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index 4113a833872..1b9eea86018 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -420,14 +420,20 @@ async def test_render_template_renders_template( assert msg["id"] == 5 assert msg["type"] == "event" event = msg["event"] - assert event == {"result": "State is: on"} + assert event == { + "result": "State is: on", + "listeners": {"all": False, "domains": [], "entities": ["light.test"]}, + } hass.states.async_set("light.test", "off") msg = await websocket_client.receive_json() assert msg["id"] == 5 assert msg["type"] == "event" event = msg["event"] - assert event == {"result": "State is: off"} + assert event == { + "result": "State is: off", + "listeners": {"all": False, "domains": [], "entities": ["light.test"]}, + } async def test_render_template_manual_entity_ids_no_longer_needed( @@ -453,14 +459,20 @@ async def test_render_template_manual_entity_ids_no_longer_needed( assert msg["id"] == 5 assert msg["type"] == "event" event = msg["event"] - assert event == {"result": "State is: on"} + assert event == { + "result": "State is: on", + "listeners": {"all": False, "domains": [], "entities": ["light.test"]}, + } hass.states.async_set("light.test", "off") msg = await websocket_client.receive_json() assert msg["id"] == 5 assert msg["type"] == "event" event = msg["event"] - assert event == {"result": "State is: off"} + assert event == { + "result": "State is: off", + "listeners": {"all": False, "domains": [], "entities": ["light.test"]}, + } async def test_render_template_with_error( @@ -480,7 +492,10 @@ async def test_render_template_with_error( assert msg["id"] == 5 assert msg["type"] == "event" event = msg["event"] - assert event == {"result": None} + assert event == { + "result": None, + "listeners": {"all": True, "domains": [], "entities": []}, + } assert "my_unknown_var" in caplog.text assert "TemplateError" in caplog.text diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py index cc06c0fd19c..ba14c8a757f 100644 --- a/tests/helpers/test_event.py +++ b/tests/helpers/test_event.py @@ -682,20 +682,33 @@ async def test_track_template_result_complex(hass): hass.states.async_set("light.one", "on") hass.states.async_set("lock.one", "locked") - async_track_template_result( + info = async_track_template_result( hass, [TrackTemplate(template_complex, None)], specific_run_callback ) await hass.async_block_till_done() + assert info.listeners == {"all": True, "domains": set(), "entities": set()} + hass.states.async_set("sensor.domain", "light") await hass.async_block_till_done() assert len(specific_runs) == 1 assert specific_runs[0].strip() == "['light.one']" + assert info.listeners == { + "all": False, + "domains": {"light"}, + "entities": {"sensor.domain"}, + } + hass.states.async_set("sensor.domain", "lock") await hass.async_block_till_done() assert len(specific_runs) == 2 assert specific_runs[1].strip() == "['lock.one']" + assert info.listeners == { + "all": False, + "domains": {"lock"}, + "entities": {"sensor.domain"}, + } hass.states.async_set("sensor.domain", "all") await hass.async_block_till_done() @@ -703,11 +716,17 @@ async def test_track_template_result_complex(hass): assert "light.one" in specific_runs[2] assert "lock.one" in specific_runs[2] assert "sensor.domain" in specific_runs[2] + assert info.listeners == {"all": True, "domains": set(), "entities": set()} hass.states.async_set("sensor.domain", "light") await hass.async_block_till_done() assert len(specific_runs) == 4 assert specific_runs[3].strip() == "['light.one']" + assert info.listeners == { + "all": False, + "domains": {"light"}, + "entities": {"sensor.domain"}, + } hass.states.async_set("light.two", "on") await hass.async_block_till_done() @@ -715,6 +734,11 @@ async def test_track_template_result_complex(hass): assert "light.one" in specific_runs[4] assert "light.two" in specific_runs[4] assert "sensor.domain" not in specific_runs[4] + assert info.listeners == { + "all": False, + "domains": {"light"}, + "entities": {"sensor.domain"}, + } hass.states.async_set("light.three", "on") await hass.async_block_till_done() @@ -723,26 +747,51 @@ async def test_track_template_result_complex(hass): assert "light.two" in specific_runs[5] assert "light.three" in specific_runs[5] assert "sensor.domain" not in specific_runs[5] + assert info.listeners == { + "all": False, + "domains": {"light"}, + "entities": {"sensor.domain"}, + } hass.states.async_set("sensor.domain", "lock") await hass.async_block_till_done() assert len(specific_runs) == 7 assert specific_runs[6].strip() == "['lock.one']" + assert info.listeners == { + "all": False, + "domains": {"lock"}, + "entities": {"sensor.domain"}, + } hass.states.async_set("sensor.domain", "single_binary_sensor") await hass.async_block_till_done() assert len(specific_runs) == 8 assert specific_runs[7].strip() == "unknown" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"binary_sensor.single", "sensor.domain"}, + } hass.states.async_set("binary_sensor.single", "binary_sensor_on") await hass.async_block_till_done() assert len(specific_runs) == 9 assert specific_runs[8].strip() == "binary_sensor_on" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"binary_sensor.single", "sensor.domain"}, + } hass.states.async_set("sensor.domain", "lock") await hass.async_block_till_done() assert len(specific_runs) == 10 assert specific_runs[9].strip() == "['lock.one']" + assert info.listeners == { + "all": False, + "domains": {"lock"}, + "entities": {"sensor.domain"}, + } async def test_track_template_result_with_wildcard(hass): @@ -766,7 +815,7 @@ async def test_track_template_result_with_wildcard(hass): hass.states.async_set("cover.office_window", "closed") hass.states.async_set("cover.office_skylight", "open") - async_track_template_result( + info = async_track_template_result( hass, [TrackTemplate(template_complex, None)], specific_run_callback ) await hass.async_block_till_done() @@ -774,6 +823,7 @@ async def test_track_template_result_with_wildcard(hass): hass.states.async_set("cover.office_window", "open") await hass.async_block_till_done() assert len(specific_runs) == 1 + assert info.listeners == {"all": True, "domains": set(), "entities": set()} assert "cover.office_drapes=closed" in specific_runs[0] assert "cover.office_window=open" in specific_runs[0] @@ -808,11 +858,22 @@ async def test_track_template_result_with_group(hass): def specific_run_callback(event, updates): specific_runs.append(updates.pop().result) - async_track_template_result( + info = async_track_template_result( hass, [TrackTemplate(template_complex, None)], specific_run_callback ) await hass.async_block_till_done() + assert info.listeners == { + "all": False, + "domains": set(), + "entities": { + "group.power_sensors", + "sensor.power_1", + "sensor.power_2", + "sensor.power_3", + }, + } + hass.states.async_set("sensor.power_1", 100.1) await hass.async_block_till_done() assert len(specific_runs) == 1 @@ -851,10 +912,11 @@ async def test_track_template_result_and_conditional(hass): def specific_run_callback(event, updates): specific_runs.append(updates.pop().result) - async_track_template_result( + info = async_track_template_result( hass, [TrackTemplate(template, None)], specific_run_callback ) await hass.async_block_till_done() + assert info.listeners == {"all": False, "domains": set(), "entities": {"light.a"}} hass.states.async_set("light.b", "on") await hass.async_block_till_done() @@ -864,11 +926,21 @@ async def test_track_template_result_and_conditional(hass): await hass.async_block_till_done() assert len(specific_runs) == 1 assert specific_runs[0] == "on" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"light.a", "light.b"}, + } hass.states.async_set("light.b", "off") await hass.async_block_till_done() assert len(specific_runs) == 2 assert specific_runs[1] == "off" + assert info.listeners == { + "all": False, + "domains": set(), + "entities": {"light.a", "light.b"}, + } hass.states.async_set("light.a", "off") await hass.async_block_till_done() @@ -924,7 +996,7 @@ async def test_track_template_result_iterator(hass): def filter_callback(event, updates): filter_runs.append(updates.pop().result) - async_track_template_result( + info = async_track_template_result( hass, [ TrackTemplate( @@ -939,6 +1011,11 @@ async def test_track_template_result_iterator(hass): filter_callback, ) await hass.async_block_till_done() + assert info.listeners == { + "all": False, + "domains": {"sensor"}, + "entities": {"sensor.test"}, + } hass.states.async_set("sensor.test", 6) await hass.async_block_till_done()