Ensure all jinja2 errors are trapped and displayed in the developer tools (#40624)
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
This commit is contained in:
parent
3261a904da
commit
57b7559832
6 changed files with 89 additions and 22 deletions
|
@ -17,6 +17,7 @@ from homeassistant.exceptions import (
|
|||
from homeassistant.helpers import config_validation as cv, entity
|
||||
from homeassistant.helpers.event import TrackTemplate, async_track_template_result
|
||||
from homeassistant.helpers.service import async_get_all_descriptions
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.loader import IntegrationNotFound, async_get_integration
|
||||
|
||||
from . import const, decorators, messages
|
||||
|
@ -242,16 +243,15 @@ def handle_ping(hass, connection, msg):
|
|||
@decorators.websocket_command(
|
||||
{
|
||||
vol.Required("type"): "render_template",
|
||||
vol.Required("template"): cv.template,
|
||||
vol.Required("template"): str,
|
||||
vol.Optional("entity_ids"): cv.entity_ids,
|
||||
vol.Optional("variables"): dict,
|
||||
}
|
||||
)
|
||||
def handle_render_template(hass, connection, msg):
|
||||
"""Handle render_template command."""
|
||||
template = msg["template"]
|
||||
template.hass = hass
|
||||
|
||||
template_str = msg["template"]
|
||||
template = Template(template_str, hass)
|
||||
variables = msg.get("variables")
|
||||
info = None
|
||||
|
||||
|
@ -261,13 +261,8 @@ def handle_render_template(hass, connection, msg):
|
|||
track_template_result = updates.pop()
|
||||
result = track_template_result.result
|
||||
if isinstance(result, TemplateError):
|
||||
_LOGGER.error(
|
||||
"TemplateError('%s') " "while processing template '%s'",
|
||||
result,
|
||||
track_template_result.template,
|
||||
)
|
||||
|
||||
result = None
|
||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(result))
|
||||
return
|
||||
|
||||
connection.send_message(
|
||||
messages.event_message(
|
||||
|
@ -275,9 +270,16 @@ def handle_render_template(hass, connection, msg):
|
|||
)
|
||||
)
|
||||
|
||||
try:
|
||||
info = async_track_template_result(
|
||||
hass, [TrackTemplate(template, variables)], _template_listener
|
||||
hass,
|
||||
[TrackTemplate(template, variables)],
|
||||
_template_listener,
|
||||
raise_on_template_error=True,
|
||||
)
|
||||
except TemplateError as ex:
|
||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
||||
return
|
||||
|
||||
connection.subscriptions[msg["id"]] = info.async_remove
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ ERR_UNKNOWN_COMMAND = "unknown_command"
|
|||
ERR_UNKNOWN_ERROR = "unknown_error"
|
||||
ERR_UNAUTHORIZED = "unauthorized"
|
||||
ERR_TIMEOUT = "timeout"
|
||||
ERR_TEMPLATE_ERROR = "template_error"
|
||||
|
||||
TYPE_RESULT = "result"
|
||||
|
||||
|
|
|
@ -565,7 +565,7 @@ class _TrackTemplateResultInfo:
|
|||
self._last_domains: Set = set()
|
||||
self._last_entities: Set = set()
|
||||
|
||||
def async_setup(self) -> None:
|
||||
def async_setup(self, raise_on_template_error: bool) -> None:
|
||||
"""Activation of template tracking."""
|
||||
for track_template_ in self._track_templates:
|
||||
template = track_template_.template
|
||||
|
@ -573,6 +573,8 @@ class _TrackTemplateResultInfo:
|
|||
|
||||
self._info[template] = template.async_render_to_info(variables)
|
||||
if self._info[template].exception:
|
||||
if raise_on_template_error:
|
||||
raise self._info[template].exception
|
||||
_LOGGER.error(
|
||||
"Error while processing template: %s",
|
||||
track_template_.template,
|
||||
|
@ -812,6 +814,7 @@ def async_track_template_result(
|
|||
hass: HomeAssistant,
|
||||
track_templates: Iterable[TrackTemplate],
|
||||
action: TrackTemplateResultListener,
|
||||
raise_on_template_error: bool = False,
|
||||
) -> _TrackTemplateResultInfo:
|
||||
"""Add a listener that fires when a the result of a template changes.
|
||||
|
||||
|
@ -833,9 +836,13 @@ def async_track_template_result(
|
|||
Home assistant object.
|
||||
track_templates
|
||||
An iterable of TrackTemplate.
|
||||
|
||||
action
|
||||
Callable to call with results.
|
||||
raise_on_template_error
|
||||
When set to True, if there is an exception
|
||||
processing the template during setup, the system
|
||||
will raise the exception instead of setting up
|
||||
tracking.
|
||||
|
||||
Returns
|
||||
-------
|
||||
|
@ -843,7 +850,7 @@ def async_track_template_result(
|
|||
|
||||
"""
|
||||
tracker = _TrackTemplateResultInfo(hass, track_templates, action)
|
||||
tracker.async_setup()
|
||||
tracker.async_setup(raise_on_template_error)
|
||||
return tracker
|
||||
|
||||
|
||||
|
|
|
@ -266,7 +266,7 @@ class Template:
|
|||
|
||||
try:
|
||||
self._compiled_code = self._env.compile(self.template)
|
||||
except jinja2.exceptions.TemplateSyntaxError as err:
|
||||
except jinja2.TemplateError as err:
|
||||
raise TemplateError(err) from err
|
||||
|
||||
def extract_entities(
|
||||
|
|
|
@ -484,21 +484,59 @@ async def test_render_template_with_error(
|
|||
)
|
||||
|
||||
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, hass_admin_user, caplog
|
||||
):
|
||||
"""Test a template with an error that only happens after a state change."""
|
||||
hass.states.async_set("sensor.test", "on")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
template_str = """
|
||||
{% if states.sensor.test.state %}
|
||||
on
|
||||
{% else %}
|
||||
{{ explode + 1 }}
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
await websocket_client.send_json(
|
||||
{"id": 5, "type": "render_template", "template": template_str}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == const.TYPE_RESULT
|
||||
assert msg["success"]
|
||||
|
||||
hass.states.async_remove("sensor.test")
|
||||
await hass.async_block_till_done()
|
||||
|
||||
msg = await websocket_client.receive_json()
|
||||
assert msg["id"] == 5
|
||||
assert msg["type"] == "event"
|
||||
event = msg["event"]
|
||||
assert event == {
|
||||
"result": None,
|
||||
"listeners": {"all": True, "domains": [], "entities": []},
|
||||
"result": "on",
|
||||
"listeners": {"all": False, "domains": [], "entities": ["sensor.test"]},
|
||||
}
|
||||
|
||||
assert "my_unknown_var" in caplog.text
|
||||
assert "TemplateError" in caplog.text
|
||||
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_returns_with_match_all(
|
||||
|
|
|
@ -1460,6 +1460,25 @@ async def test_async_track_template_result_multiple_templates_mixing_domain(hass
|
|||
]
|
||||
|
||||
|
||||
async def test_async_track_template_result_raise_on_template_error(hass):
|
||||
"""Test that we raise as soon as we encounter a failed template."""
|
||||
|
||||
with pytest.raises(TemplateError):
|
||||
async_track_template_result(
|
||||
hass,
|
||||
[
|
||||
TrackTemplate(
|
||||
Template(
|
||||
"{{ states.switch | function_that_does_not_exist | list }}"
|
||||
),
|
||||
None,
|
||||
),
|
||||
],
|
||||
ha.callback(lambda event, updates: None),
|
||||
raise_on_template_error=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_track_same_state_simple_no_trigger(hass):
|
||||
"""Test track_same_change with no trigger."""
|
||||
callback_runs = []
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue