diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index f84edb1f204..b44ca45ce03 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -1,5 +1,6 @@ """Commands part of Websocket API.""" import asyncio +import logging import voluptuous as vol @@ -7,14 +8,21 @@ from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_READ from homeassistant.components.websocket_api.const import ERR_NOT_FOUND from homeassistant.const import EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL from homeassistant.core import DOMAIN as HASS_DOMAIN, callback -from homeassistant.exceptions import HomeAssistantError, ServiceNotFound, Unauthorized +from homeassistant.exceptions import ( + HomeAssistantError, + ServiceNotFound, + TemplateError, + Unauthorized, +) from homeassistant.helpers import config_validation as cv, entity -from homeassistant.helpers.event import async_track_state_change_event +from homeassistant.helpers.event import async_track_template_result from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.loader import IntegrationNotFound, async_get_integration from . import const, decorators, messages +_LOGGER = logging.getLogger(__name__) + # mypy: allow-untyped-calls, allow-untyped-defs @@ -244,27 +252,26 @@ def handle_render_template(hass, connection, msg): variables = msg.get("variables") - entity_ids = msg.get("entity_ids") - if entity_ids is None: - entity_ids = template.extract_entities(variables) - @callback - def state_listener(*_): - connection.send_message( - messages.event_message( - msg["id"], {"result": template.async_render(variables)} + def _template_listener(event, template, last_result, result): + if isinstance(result, TemplateError): + _LOGGER.error( + "TemplateError('%s') " "while processing template '%s'", + result, + template, ) - ) - if entity_ids and entity_ids != MATCH_ALL: - connection.subscriptions[msg["id"]] = async_track_state_change_event( - hass, entity_ids, state_listener - ) - else: - connection.subscriptions[msg["id"]] = lambda: None + result = None + + connection.send_message(messages.event_message(msg["id"], {"result": result})) + + info = async_track_template_result(hass, template, _template_listener, variables) + + connection.subscriptions[msg["id"]] = info.async_remove connection.send_result(msg["id"]) - state_listener() + + hass.loop.call_soon_threadsafe(info.async_refresh) @callback diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py index c3d4c12eec6..5e35f2de04f 100644 --- a/tests/components/websocket_api/test_commands.py +++ b/tests/components/websocket_api/test_commands.py @@ -430,19 +430,17 @@ async def test_render_template_renders_template( assert event == {"result": "State is: off"} -async def test_render_template_with_manual_entity_ids( +async def test_render_template_manual_entity_ids_no_longer_needed( hass, websocket_client, hass_admin_user ): """Test that updates to specified entity ids cause a template rerender.""" hass.states.async_set("light.test", "on") - hass.states.async_set("light.test2", "on") await websocket_client.send_json( { "id": 5, "type": "render_template", "template": "State is: {{ states('light.test') }}", - "entity_ids": ["light.test2"], } ) @@ -457,12 +455,35 @@ async def test_render_template_with_manual_entity_ids( event = msg["event"] assert event == {"result": "State is: on"} - hass.states.async_set("light.test2", "off") + 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: on"} + assert event == {"result": "State is: off"} + + +async def test_render_template_with_error( + hass, websocket_client, hass_admin_user, caplog +): + """Test a template with an error.""" + await websocket_client.send_json( + {"id": 5, "type": "render_template", "template": "{{ my_unknown_var() + 1 }}"} + ) + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == const.TYPE_RESULT + assert msg["success"] + + msg = await websocket_client.receive_json() + assert msg["id"] == 5 + assert msg["type"] == "event" + event = msg["event"] + assert event == {"result": None} + + assert "my_unknown_var" in caplog.text + assert "TemplateError" in caplog.text async def test_render_template_returns_with_match_all(