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:
J. Nick Koston 2020-09-26 17:03:32 -05:00 committed by GitHub
parent 3261a904da
commit 57b7559832
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 89 additions and 22 deletions

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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(

View file

@ -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(

View file

@ -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 = []