Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions homeassistant/components/websocket_api/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
4 changes: 1 addition & 3 deletions homeassistant/exceptions.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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}")

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/helpers/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any

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()
Expand Down
35 changes: 35 additions & 0 deletions tests/components/websocket_api/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
10 changes: 10 additions & 0 deletions tests/helpers/test_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down