Ensure all template errors are caught and the websocket api reports them (#41719)
parent
f7e7f1371e
commit
b897ca7260
|
@ -257,13 +257,20 @@ async def handle_render_template(hass, connection, msg):
|
|||
timeout = msg.get("timeout")
|
||||
info = None
|
||||
|
||||
if timeout and await template.async_render_will_timeout(timeout):
|
||||
connection.send_error(
|
||||
msg["id"],
|
||||
const.ERR_TEMPLATE_ERROR,
|
||||
f"Exceeded maximum execution time of {timeout}s",
|
||||
)
|
||||
return
|
||||
if timeout:
|
||||
try:
|
||||
timed_out = await template.async_render_will_timeout(timeout)
|
||||
except TemplateError as ex:
|
||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
||||
return
|
||||
|
||||
if timed_out:
|
||||
connection.send_error(
|
||||
msg["id"],
|
||||
const.ERR_TEMPLATE_ERROR,
|
||||
f"Exceeded maximum execution time of {timeout}s",
|
||||
)
|
||||
return
|
||||
|
||||
@callback
|
||||
def _template_listener(event, updates):
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
"""The exceptions used by Home Assistant."""
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
import jinja2
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .core import Context # noqa: F401 pylint: disable=unused-import
|
||||
|
||||
|
@ -22,7 +20,7 @@ class NoEntitySpecifiedError(HomeAssistantError):
|
|||
class TemplateError(HomeAssistantError):
|
||||
"""Error during template rendering."""
|
||||
|
||||
def __init__(self, exception: jinja2.TemplateError) -> None:
|
||||
def __init__(self, exception: Exception) -> None:
|
||||
"""Init the error."""
|
||||
super().__init__(f"{exception.__class__.__name__}: {exception}")
|
||||
|
||||
|
|
|
@ -331,7 +331,7 @@ class Template:
|
|||
|
||||
try:
|
||||
render_result = compiled.render(kwargs)
|
||||
except jinja2.TemplateError as err:
|
||||
except Exception as err: # pylint: disable=broad-except
|
||||
raise TemplateError(err) from err
|
||||
|
||||
render_result = render_result.strip()
|
||||
|
|
|
@ -485,6 +485,41 @@ async def test_render_template_with_error(hass, websocket_client, caplog):
|
|||
assert "TemplateError" not in caplog.text
|
||||
|
||||
|
||||
async def test_render_template_with_timeout_and_error(hass, websocket_client, caplog):
|
||||
"""Test a template with an error with a timeout."""
|
||||
await websocket_client.send_json(
|
||||
{
|
||||
"id": 5,
|
||||
"type": "render_template",
|
||||
"template": "{{ now() | rando }}",
|
||||
"timeout": 5,
|
||||
}
|
||||
)
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
||||
|
||||
assert "TemplateError" not in caplog.text
|
||||
|
||||
|
||||
async def test_render_template_error_in_template_code(hass, websocket_client, caplog):
|
||||
"""Test a template that will throw in template.py."""
|
||||
await websocket_client.send_json(
|
||||
{"id": 5, "type": "render_template", "template": "{{ now() | random }}"}
|
||||
)
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert not msg["success"]
|
||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
||||
|
||||
assert "TemplateError" not in caplog.text
|
||||
|
||||
|
||||
async def test_render_template_with_delayed_error(hass, websocket_client, caplog):
|
||||
"""Test a template with an error that only happens after a state change."""
|
||||
hass.states.async_set("sensor.test", "on")
|
||||
|
|
|
@ -2533,6 +2533,16 @@ async def test_lights(hass):
|
|||
assert f"sensor{i}" in info.result()
|
||||
|
||||
|
||||
async def test_template_errors(hass):
|
||||
"""Test template rendering wraps exceptions with TemplateError."""
|
||||
|
||||
with pytest.raises(TemplateError):
|
||||
template.Template("{{ now() | rando }}", hass).async_render()
|
||||
|
||||
with pytest.raises(TemplateError):
|
||||
template.Template("{{ now() | random }}", hass).async_render()
|
||||
|
||||
|
||||
async def test_state_attributes(hass):
|
||||
"""Test state attributes."""
|
||||
hass.states.async_set("sensor.test", "23")
|
||||
|
|
Loading…
Reference in New Issue