mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Send template render errors to template helper preview (#99716)
This commit is contained in:
parent
107ca83d42
commit
067f946129
@ -15,13 +15,11 @@ from homeassistant.const import (
|
|||||||
CONF_ICON,
|
CONF_ICON,
|
||||||
CONF_ICON_TEMPLATE,
|
CONF_ICON_TEMPLATE,
|
||||||
CONF_NAME,
|
CONF_NAME,
|
||||||
EVENT_HOMEASSISTANT_START,
|
|
||||||
STATE_UNKNOWN,
|
STATE_UNKNOWN,
|
||||||
)
|
)
|
||||||
from homeassistant.core import (
|
from homeassistant.core import (
|
||||||
CALLBACK_TYPE,
|
CALLBACK_TYPE,
|
||||||
Context,
|
Context,
|
||||||
CoreState,
|
|
||||||
HomeAssistant,
|
HomeAssistant,
|
||||||
State,
|
State,
|
||||||
callback,
|
callback,
|
||||||
@ -38,6 +36,7 @@ from homeassistant.helpers.event import (
|
|||||||
async_track_template_result,
|
async_track_template_result,
|
||||||
)
|
)
|
||||||
from homeassistant.helpers.script import Script, _VarsType
|
from homeassistant.helpers.script import Script, _VarsType
|
||||||
|
from homeassistant.helpers.start import async_at_start
|
||||||
from homeassistant.helpers.template import (
|
from homeassistant.helpers.template import (
|
||||||
Template,
|
Template,
|
||||||
TemplateStateFromEntityId,
|
TemplateStateFromEntityId,
|
||||||
@ -442,7 +441,11 @@ class TemplateEntity(Entity):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@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] = []
|
template_var_tups: list[TrackTemplate] = []
|
||||||
has_availability_template = False
|
has_availability_template = False
|
||||||
|
|
||||||
@ -467,6 +470,7 @@ class TemplateEntity(Entity):
|
|||||||
self.hass,
|
self.hass,
|
||||||
template_var_tups,
|
template_var_tups,
|
||||||
self._handle_results,
|
self._handle_results,
|
||||||
|
log_fn=log_fn,
|
||||||
has_super_template=has_availability_template,
|
has_super_template=has_availability_template,
|
||||||
)
|
)
|
||||||
self.async_on_remove(result_info.async_remove)
|
self.async_on_remove(result_info.async_remove)
|
||||||
@ -515,10 +519,13 @@ class TemplateEntity(Entity):
|
|||||||
) -> CALLBACK_TYPE:
|
) -> CALLBACK_TYPE:
|
||||||
"""Render a preview."""
|
"""Render a preview."""
|
||||||
|
|
||||||
|
def log_template_error(level: int, msg: str) -> None:
|
||||||
|
preview_callback(None, None, None, msg)
|
||||||
|
|
||||||
self._preview_callback = preview_callback
|
self._preview_callback = preview_callback
|
||||||
self._async_setup_templates()
|
self._async_setup_templates()
|
||||||
try:
|
try:
|
||||||
self._async_template_startup()
|
self._async_template_startup(None, log_template_error)
|
||||||
except Exception as err: # pylint: disable=broad-exception-caught
|
except Exception as err: # pylint: disable=broad-exception-caught
|
||||||
preview_callback(None, None, None, str(err))
|
preview_callback(None, None, None, str(err))
|
||||||
return self._call_on_remove_callbacks
|
return self._call_on_remove_callbacks
|
||||||
@ -527,13 +534,7 @@ class TemplateEntity(Entity):
|
|||||||
"""Run when entity about to be added to hass."""
|
"""Run when entity about to be added to hass."""
|
||||||
self._async_setup_templates()
|
self._async_setup_templates()
|
||||||
|
|
||||||
if self.hass.state == CoreState.running:
|
async_at_start(self.hass, self._async_template_startup)
|
||||||
self._async_template_startup()
|
|
||||||
return
|
|
||||||
|
|
||||||
self.hass.bus.async_listen_once(
|
|
||||||
EVENT_HOMEASSISTANT_START, self._async_template_startup
|
|
||||||
)
|
|
||||||
|
|
||||||
async def async_update(self) -> None:
|
async def async_update(self) -> None:
|
||||||
"""Call for forced update."""
|
"""Call for forced update."""
|
||||||
|
@ -957,11 +957,14 @@ class TrackTemplateResultInfo:
|
|||||||
if info.exception:
|
if info.exception:
|
||||||
if raise_on_template_error:
|
if raise_on_template_error:
|
||||||
raise info.exception
|
raise info.exception
|
||||||
_LOGGER.error(
|
if not log_fn:
|
||||||
"Error while processing template: %s",
|
_LOGGER.error(
|
||||||
track_template_.template,
|
"Error while processing template: %s",
|
||||||
exc_info=info.exception,
|
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._track_state_changes = async_track_state_change_filtered(
|
||||||
self.hass, _render_infos_to_track_states(self._info.values()), self._refresh
|
self.hass, _render_infos_to_track_states(self._info.values()), self._refresh
|
||||||
|
@ -272,12 +272,12 @@ async def test_options(
|
|||||||
),
|
),
|
||||||
(
|
(
|
||||||
"sensor",
|
"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"},
|
{"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(
|
@pytest.mark.parametrize(
|
||||||
(
|
(
|
||||||
"template_type",
|
"template_type",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user