Include template listener info in template preview (#99669)

pull/99710/head
Erik Montnemery 2023-09-06 09:49:42 +02:00 committed by GitHub
parent b28fda2433
commit cdca4591a4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 57 additions and 13 deletions

View File

@ -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},
)
)

View File

@ -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,

View File

@ -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 (

View File

@ -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