Send template render errors to template helper preview (#99716)
parent
e1ea53e72f
commit
c9a6ea94a7
|
@ -15,13 +15,11 @@ from homeassistant.const import (
|
|||
CONF_ICON,
|
||||
CONF_ICON_TEMPLATE,
|
||||
CONF_NAME,
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
STATE_UNKNOWN,
|
||||
)
|
||||
from homeassistant.core import (
|
||||
CALLBACK_TYPE,
|
||||
Context,
|
||||
CoreState,
|
||||
HomeAssistant,
|
||||
State,
|
||||
callback,
|
||||
|
@ -38,6 +36,7 @@ from homeassistant.helpers.event import (
|
|||
async_track_template_result,
|
||||
)
|
||||
from homeassistant.helpers.script import Script, _VarsType
|
||||
from homeassistant.helpers.start import async_at_start
|
||||
from homeassistant.helpers.template import (
|
||||
Template,
|
||||
TemplateStateFromEntityId,
|
||||
|
@ -442,7 +441,11 @@ class TemplateEntity(Entity):
|
|||
)
|
||||
|
||||
@callback
|
||||
def _async_template_startup(self, *_: Any) -> None:
|
||||
def _async_template_startup(
|
||||
self,
|
||||
_hass: HomeAssistant | None,
|
||||
log_fn: Callable[[int, str], None] | None = None,
|
||||
) -> None:
|
||||
template_var_tups: list[TrackTemplate] = []
|
||||
has_availability_template = False
|
||||
|
||||
|
@ -467,6 +470,7 @@ class TemplateEntity(Entity):
|
|||
self.hass,
|
||||
template_var_tups,
|
||||
self._handle_results,
|
||||
log_fn=log_fn,
|
||||
has_super_template=has_availability_template,
|
||||
)
|
||||
self.async_on_remove(result_info.async_remove)
|
||||
|
@ -515,10 +519,13 @@ class TemplateEntity(Entity):
|
|||
) -> CALLBACK_TYPE:
|
||||
"""Render a preview."""
|
||||
|
||||
def log_template_error(level: int, msg: str) -> None:
|
||||
preview_callback(None, None, None, msg)
|
||||
|
||||
self._preview_callback = preview_callback
|
||||
self._async_setup_templates()
|
||||
try:
|
||||
self._async_template_startup()
|
||||
self._async_template_startup(None, log_template_error)
|
||||
except Exception as err: # pylint: disable=broad-exception-caught
|
||||
preview_callback(None, None, None, str(err))
|
||||
return self._call_on_remove_callbacks
|
||||
|
@ -527,13 +534,7 @@ class TemplateEntity(Entity):
|
|||
"""Run when entity about to be added to hass."""
|
||||
self._async_setup_templates()
|
||||
|
||||
if self.hass.state == CoreState.running:
|
||||
self._async_template_startup()
|
||||
return
|
||||
|
||||
self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START, self._async_template_startup
|
||||
)
|
||||
async_at_start(self.hass, self._async_template_startup)
|
||||
|
||||
async def async_update(self) -> None:
|
||||
"""Call for forced update."""
|
||||
|
|
|
@ -957,11 +957,14 @@ class TrackTemplateResultInfo:
|
|||
if info.exception:
|
||||
if raise_on_template_error:
|
||||
raise info.exception
|
||||
_LOGGER.error(
|
||||
"Error while processing template: %s",
|
||||
track_template_.template,
|
||||
exc_info=info.exception,
|
||||
)
|
||||
if not log_fn:
|
||||
_LOGGER.error(
|
||||
"Error while processing template: %s",
|
||||
track_template_.template,
|
||||
exc_info=info.exception,
|
||||
)
|
||||
else:
|
||||
log_fn(logging.ERROR, str(info.exception))
|
||||
|
||||
self._track_state_changes = async_track_state_change_filtered(
|
||||
self.hass, _render_infos_to_track_states(self._info.values()), self._refresh
|
||||
|
|
|
@ -272,12 +272,12 @@ async def test_options(
|
|||
),
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
"{{ float(states('sensor.one'), default='') + float(states('sensor.two'), default='') }}",
|
||||
{},
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
["unavailable", "50.0"],
|
||||
["", "50.0"],
|
||||
[{}, {}],
|
||||
[["one"], ["one", "two"]],
|
||||
[["one", "two"], ["one", "two"]],
|
||||
),
|
||||
),
|
||||
)
|
||||
|
@ -470,6 +470,173 @@ async def test_config_flow_preview_bad_input(
|
|||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"template_type",
|
||||
"state_template",
|
||||
"input_states",
|
||||
"template_states",
|
||||
"error_events",
|
||||
),
|
||||
[
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) + float(states('sensor.two')) }}",
|
||||
{"one": "30.0", "two": "20.0"},
|
||||
["unavailable", "50.0"],
|
||||
[
|
||||
(
|
||||
"ValueError: Template error: float got invalid input 'unknown' "
|
||||
"when rendering template '{{ float(states('sensor.one')) + "
|
||||
"float(states('sensor.two')) }}' but no default was specified"
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_config_flow_preview_template_startup_error(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
template_type: str,
|
||||
state_template: str,
|
||||
input_states: dict[str, str],
|
||||
template_states: list[str],
|
||||
error_events: list[str],
|
||||
) -> None:
|
||||
"""Test the config flow preview."""
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
input_entities = ["one", "two"]
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.MENU
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": template_type},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == template_type
|
||||
assert result["errors"] is None
|
||||
assert result["preview"] == "template"
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "template/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": {"name": "My template", "state": state_template},
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "result"
|
||||
assert msg["success"]
|
||||
|
||||
for error_event in error_events:
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"] == {"error": error_event}
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[0]
|
||||
|
||||
for input_entity in input_entities:
|
||||
hass.states.async_set(
|
||||
f"{template_type}.{input_entity}", input_states[input_entity], {}
|
||||
)
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"template_type",
|
||||
"state_template",
|
||||
"input_states",
|
||||
"template_states",
|
||||
"error_events",
|
||||
),
|
||||
[
|
||||
(
|
||||
"sensor",
|
||||
"{{ float(states('sensor.one')) > 30 and undefined_function() }}",
|
||||
[{"one": "30.0", "two": "20.0"}, {"one": "35.0", "two": "20.0"}],
|
||||
["False", "unavailable"],
|
||||
["'undefined_function' is undefined"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_config_flow_preview_template_error(
|
||||
hass: HomeAssistant,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
template_type: str,
|
||||
state_template: str,
|
||||
input_states: list[dict[str, str]],
|
||||
template_states: list[str],
|
||||
error_events: list[str],
|
||||
) -> None:
|
||||
"""Test the config flow preview."""
|
||||
client = await hass_ws_client(hass)
|
||||
|
||||
input_entities = ["one", "two"]
|
||||
|
||||
for input_entity in input_entities:
|
||||
hass.states.async_set(
|
||||
f"{template_type}.{input_entity}", input_states[0][input_entity], {}
|
||||
)
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] == FlowResultType.MENU
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"next_step_id": template_type},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == template_type
|
||||
assert result["errors"] is None
|
||||
assert result["preview"] == "template"
|
||||
|
||||
await client.send_json_auto_id(
|
||||
{
|
||||
"type": "template/start_preview",
|
||||
"flow_id": result["flow_id"],
|
||||
"flow_type": "config_flow",
|
||||
"user_input": {"name": "My template", "state": state_template},
|
||||
}
|
||||
)
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "result"
|
||||
assert msg["success"]
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[0]
|
||||
|
||||
for input_entity in input_entities:
|
||||
hass.states.async_set(
|
||||
f"{template_type}.{input_entity}", input_states[1][input_entity], {}
|
||||
)
|
||||
|
||||
for error_event in error_events:
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"] == {"error": error_event}
|
||||
|
||||
msg = await client.receive_json()
|
||||
assert msg["type"] == "event"
|
||||
assert msg["event"]["state"] == template_states[1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
(
|
||||
"template_type",
|
||||
|
|
Loading…
Reference in New Issue