diff --git a/homeassistant/components/template/config_flow.py b/homeassistant/components/template/config_flow.py index ccc06989c71..093cbf14098 100644 --- a/homeassistant/components/template/config_flow.py +++ b/homeassistant/components/template/config_flow.py @@ -349,6 +349,7 @@ def ws_start_preview( def async_preview_updated( state: str | None, attributes: Mapping[str, Any] | None, + listeners: dict[str, bool | set[str]] | None, error: str | None, ) -> None: """Forward config entry state events to websocket.""" @@ -363,7 +364,7 @@ def ws_start_preview( connection.send_message( websocket_api.event_message( msg["id"], - {"attributes": attributes, "state": state}, + {"attributes": attributes, "listeners": listeners, "state": state}, ) ) diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index c33674fa86f..2ce42083117 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -34,6 +34,7 @@ from homeassistant.helpers.event import ( EventStateChangedData, TrackTemplate, TrackTemplateResult, + TrackTemplateResultInfo, async_track_template_result, ) from homeassistant.helpers.script import Script, _VarsType @@ -260,12 +261,18 @@ class TemplateEntity(Entity): ) -> None: """Template Entity.""" self._template_attrs: dict[Template, list[_TemplateAttribute]] = {} - self._async_update: Callable[[], None] | None = None + self._template_result_info: TrackTemplateResultInfo | None = None self._attr_extra_state_attributes = {} self._self_ref_update_count = 0 self._attr_unique_id = unique_id self._preview_callback: Callable[ - [str | None, dict[str, Any] | None, str | None], None + [ + str | None, + dict[str, Any] | None, + dict[str, bool | set[str]] | None, + str | None, + ], + None, ] | None = None if config is None: self._attribute_templates = attribute_templates @@ -427,9 +434,12 @@ class TemplateEntity(Entity): state, attrs = self._async_generate_attributes() validate_state(state) except Exception as err: # pylint: disable=broad-exception-caught - self._preview_callback(None, None, str(err)) + self._preview_callback(None, None, None, str(err)) else: - self._preview_callback(state, attrs, None) + assert self._template_result_info + self._preview_callback( + state, attrs, self._template_result_info.listeners, None + ) @callback def _async_template_startup(self, *_: Any) -> None: @@ -460,7 +470,7 @@ class TemplateEntity(Entity): has_super_template=has_availability_template, ) self.async_on_remove(result_info.async_remove) - self._async_update = result_info.async_refresh + self._template_result_info = result_info result_info.async_refresh() @callback @@ -494,7 +504,13 @@ class TemplateEntity(Entity): def async_start_preview( self, preview_callback: Callable[ - [str | None, Mapping[str, Any] | None, str | None], None + [ + str | None, + Mapping[str, Any] | None, + dict[str, bool | set[str]] | None, + str | None, + ], + None, ], ) -> CALLBACK_TYPE: """Render a preview.""" @@ -504,7 +520,7 @@ class TemplateEntity(Entity): try: self._async_template_startup() except Exception as err: # pylint: disable=broad-exception-caught - preview_callback(None, None, str(err)) + preview_callback(None, None, None, str(err)) return self._call_on_remove_callbacks async def async_added_to_hass(self) -> None: @@ -521,8 +537,8 @@ class TemplateEntity(Entity): async def async_update(self) -> None: """Call for forced update.""" - assert self._async_update - self._async_update() + assert self._template_result_info + self._template_result_info.async_refresh() async def async_run_script( self, diff --git a/homeassistant/helpers/trigger_template_entity.py b/homeassistant/helpers/trigger_template_entity.py index 8fc99f5cb52..0ee653b42bd 100644 --- a/homeassistant/helpers/trigger_template_entity.py +++ b/homeassistant/helpers/trigger_template_entity.py @@ -77,8 +77,8 @@ class TriggerBaseEntity(Entity): """Template Base entity based on trigger data.""" domain: str - extra_template_keys: tuple | None = None - extra_template_keys_complex: tuple | None = None + extra_template_keys: tuple[str, ...] | None = None + extra_template_keys_complex: tuple[str, ...] | None = None _unique_id: str | None def __init__( @@ -94,7 +94,7 @@ class TriggerBaseEntity(Entity): self._config = config self._static_rendered = {} - self._to_render_simple = [] + self._to_render_simple: list[str] = [] self._to_render_complex: list[str] = [] for itm in ( diff --git a/tests/components/template/test_config_flow.py b/tests/components/template/test_config_flow.py index ba939f3b8d1..b8634b68b1c 100644 --- a/tests/components/template/test_config_flow.py +++ b/tests/components/template/test_config_flow.py @@ -3,6 +3,7 @@ from typing import Any from unittest.mock import patch import pytest +from pytest_unordered import unordered from homeassistant import config_entries from homeassistant.components.template import DOMAIN, async_setup_entry @@ -257,6 +258,7 @@ async def test_options( "input_states", "template_states", "extra_attributes", + "listeners", ), ( ( @@ -266,6 +268,7 @@ async def test_options( {"one": "on", "two": "off"}, ["off", "on"], [{}, {}], + [["one", "two"], ["one"]], ), ( "sensor", @@ -274,6 +277,7 @@ async def test_options( {"one": "30.0", "two": "20.0"}, ["unavailable", "50.0"], [{}, {}], + [["one"], ["one", "two"]], ), ), ) @@ -286,6 +290,7 @@ async def test_config_flow_preview( input_states: list[str], template_states: str, extra_attributes: list[dict[str, Any]], + listeners: list[list[str]], ) -> None: """Test the config flow preview.""" client = await hass_ws_client(hass) @@ -323,6 +328,12 @@ async def test_config_flow_preview( msg = await client.receive_json() assert msg["event"] == { "attributes": {"friendly_name": "My template"} | extra_attributes[0], + "listeners": { + "all": False, + "domains": [], + "entities": unordered([f"{template_type}.{_id}" for _id in listeners[0]]), + "time": False, + }, "state": template_states[0], } @@ -336,6 +347,12 @@ async def test_config_flow_preview( "attributes": {"friendly_name": "My template"} | extra_attributes[0] | extra_attributes[1], + "listeners": { + "all": False, + "domains": [], + "entities": unordered([f"{template_type}.{_id}" for _id in listeners[1]]), + "time": False, + }, "state": template_states[1], } assert len(hass.states.async_all()) == 2 @@ -526,6 +543,7 @@ async def test_config_flow_preview_bad_state( "input_states", "template_state", "extra_attributes", + "listeners", ), [ ( @@ -537,6 +555,7 @@ async def test_config_flow_preview_bad_state( {"one": "on", "two": "off"}, "off", {}, + ["one", "two"], ), ( "sensor", @@ -547,6 +566,7 @@ async def test_config_flow_preview_bad_state( {"one": "30.0", "two": "20.0"}, "10.0", {}, + ["one", "two"], ), ], ) @@ -561,6 +581,7 @@ async def test_option_flow_preview( input_states: list[str], template_state: str, extra_attributes: dict[str, Any], + listeners: list[str], ) -> None: """Test the option flow preview.""" client = await hass_ws_client(hass) @@ -608,6 +629,12 @@ async def test_option_flow_preview( msg = await client.receive_json() assert msg["event"] == { "attributes": {"friendly_name": "My template"} | extra_attributes, + "listeners": { + "all": False, + "domains": [], + "entities": unordered([f"{template_type}.{_id}" for _id in listeners]), + "time": False, + }, "state": template_state, } assert len(hass.states.async_all()) == 3