mirror of
https://github.com/home-assistant/core.git
synced 2025-07-16 17:57:11 +00:00
Don't log template errors from developer tool (#48933)
This commit is contained in:
parent
43335953a2
commit
16196e2e16
@ -290,6 +290,7 @@ def handle_ping(hass, connection, msg):
|
|||||||
vol.Optional("entity_ids"): cv.entity_ids,
|
vol.Optional("entity_ids"): cv.entity_ids,
|
||||||
vol.Optional("variables"): dict,
|
vol.Optional("variables"): dict,
|
||||||
vol.Optional("timeout"): vol.Coerce(float),
|
vol.Optional("timeout"): vol.Coerce(float),
|
||||||
|
vol.Optional("strict", default=False): bool,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@decorators.async_response
|
@decorators.async_response
|
||||||
@ -303,7 +304,9 @@ async def handle_render_template(hass, connection, msg):
|
|||||||
|
|
||||||
if timeout:
|
if timeout:
|
||||||
try:
|
try:
|
||||||
timed_out = await template_obj.async_render_will_timeout(timeout)
|
timed_out = await template_obj.async_render_will_timeout(
|
||||||
|
timeout, strict=msg["strict"]
|
||||||
|
)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
||||||
return
|
return
|
||||||
@ -337,6 +340,7 @@ async def handle_render_template(hass, connection, msg):
|
|||||||
[TrackTemplate(template_obj, variables)],
|
[TrackTemplate(template_obj, variables)],
|
||||||
_template_listener,
|
_template_listener,
|
||||||
raise_on_template_error=True,
|
raise_on_template_error=True,
|
||||||
|
strict=msg["strict"],
|
||||||
)
|
)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
connection.send_error(msg["id"], const.ERR_TEMPLATE_ERROR, str(ex))
|
||||||
|
@ -790,12 +790,14 @@ class _TrackTemplateResultInfo:
|
|||||||
self._track_state_changes: _TrackStateChangeFiltered | None = None
|
self._track_state_changes: _TrackStateChangeFiltered | None = None
|
||||||
self._time_listeners: dict[Template, Callable] = {}
|
self._time_listeners: dict[Template, Callable] = {}
|
||||||
|
|
||||||
def async_setup(self, raise_on_template_error: bool) -> None:
|
def async_setup(self, raise_on_template_error: bool, strict: bool = False) -> None:
|
||||||
"""Activation of template tracking."""
|
"""Activation of template tracking."""
|
||||||
for track_template_ in self._track_templates:
|
for track_template_ in self._track_templates:
|
||||||
template = track_template_.template
|
template = track_template_.template
|
||||||
variables = track_template_.variables
|
variables = track_template_.variables
|
||||||
self._info[template] = info = template.async_render_to_info(variables)
|
self._info[template] = info = template.async_render_to_info(
|
||||||
|
variables, strict=strict
|
||||||
|
)
|
||||||
|
|
||||||
if info.exception:
|
if info.exception:
|
||||||
if raise_on_template_error:
|
if raise_on_template_error:
|
||||||
@ -1022,6 +1024,7 @@ def async_track_template_result(
|
|||||||
track_templates: Iterable[TrackTemplate],
|
track_templates: Iterable[TrackTemplate],
|
||||||
action: TrackTemplateResultListener,
|
action: TrackTemplateResultListener,
|
||||||
raise_on_template_error: bool = False,
|
raise_on_template_error: bool = False,
|
||||||
|
strict: bool = False,
|
||||||
) -> _TrackTemplateResultInfo:
|
) -> _TrackTemplateResultInfo:
|
||||||
"""Add a listener that fires when the result of a template changes.
|
"""Add a listener that fires when the result of a template changes.
|
||||||
|
|
||||||
@ -1050,6 +1053,8 @@ def async_track_template_result(
|
|||||||
processing the template during setup, the system
|
processing the template during setup, the system
|
||||||
will raise the exception instead of setting up
|
will raise the exception instead of setting up
|
||||||
tracking.
|
tracking.
|
||||||
|
strict
|
||||||
|
When set to True, raise on undefined variables.
|
||||||
|
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
@ -1057,7 +1062,7 @@ def async_track_template_result(
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
tracker = _TrackTemplateResultInfo(hass, track_templates, action)
|
tracker = _TrackTemplateResultInfo(hass, track_templates, action)
|
||||||
tracker.async_setup(raise_on_template_error)
|
tracker.async_setup(raise_on_template_error, strict=strict)
|
||||||
return tracker
|
return tracker
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,6 +15,7 @@ import math
|
|||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
|
import sys
|
||||||
from typing import Any, Generator, Iterable, cast
|
from typing import Any, Generator, Iterable, cast
|
||||||
from urllib.parse import urlencode as urllib_urlencode
|
from urllib.parse import urlencode as urllib_urlencode
|
||||||
import weakref
|
import weakref
|
||||||
@ -57,6 +58,7 @@ DATE_STR_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|||||||
_RENDER_INFO = "template.render_info"
|
_RENDER_INFO = "template.render_info"
|
||||||
_ENVIRONMENT = "template.environment"
|
_ENVIRONMENT = "template.environment"
|
||||||
_ENVIRONMENT_LIMITED = "template.environment_limited"
|
_ENVIRONMENT_LIMITED = "template.environment_limited"
|
||||||
|
_ENVIRONMENT_STRICT = "template.environment_strict"
|
||||||
|
|
||||||
_RE_JINJA_DELIMITERS = re.compile(r"\{%|\{\{|\{#")
|
_RE_JINJA_DELIMITERS = re.compile(r"\{%|\{\{|\{#")
|
||||||
# Match "simple" ints and floats. -1.0, 1, +5, 5.0
|
# Match "simple" ints and floats. -1.0, 1, +5, 5.0
|
||||||
@ -292,7 +294,9 @@ class Template:
|
|||||||
"is_static",
|
"is_static",
|
||||||
"_compiled_code",
|
"_compiled_code",
|
||||||
"_compiled",
|
"_compiled",
|
||||||
|
"_exc_info",
|
||||||
"_limited",
|
"_limited",
|
||||||
|
"_strict",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, template, hass=None):
|
def __init__(self, template, hass=None):
|
||||||
@ -305,16 +309,23 @@ class Template:
|
|||||||
self._compiled: jinja2.Template | None = None
|
self._compiled: jinja2.Template | None = None
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.is_static = not is_template_string(template)
|
self.is_static = not is_template_string(template)
|
||||||
|
self._exc_info = None
|
||||||
self._limited = None
|
self._limited = None
|
||||||
|
self._strict = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _env(self) -> TemplateEnvironment:
|
def _env(self) -> TemplateEnvironment:
|
||||||
if self.hass is None:
|
if self.hass is None:
|
||||||
return _NO_HASS_ENV
|
return _NO_HASS_ENV
|
||||||
wanted_env = _ENVIRONMENT_LIMITED if self._limited else _ENVIRONMENT
|
if self._limited:
|
||||||
|
wanted_env = _ENVIRONMENT_LIMITED
|
||||||
|
elif self._strict:
|
||||||
|
wanted_env = _ENVIRONMENT_STRICT
|
||||||
|
else:
|
||||||
|
wanted_env = _ENVIRONMENT
|
||||||
ret: TemplateEnvironment | None = self.hass.data.get(wanted_env)
|
ret: TemplateEnvironment | None = self.hass.data.get(wanted_env)
|
||||||
if ret is None:
|
if ret is None:
|
||||||
ret = self.hass.data[wanted_env] = TemplateEnvironment(self.hass, self._limited) # type: ignore[no-untyped-call]
|
ret = self.hass.data[wanted_env] = TemplateEnvironment(self.hass, self._limited, self._strict) # type: ignore[no-untyped-call]
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def ensure_valid(self) -> None:
|
def ensure_valid(self) -> None:
|
||||||
@ -354,6 +365,7 @@ class Template:
|
|||||||
variables: TemplateVarsType = None,
|
variables: TemplateVarsType = None,
|
||||||
parse_result: bool = True,
|
parse_result: bool = True,
|
||||||
limited: bool = False,
|
limited: bool = False,
|
||||||
|
strict: bool = False,
|
||||||
**kwargs: Any,
|
**kwargs: Any,
|
||||||
) -> Any:
|
) -> Any:
|
||||||
"""Render given template.
|
"""Render given template.
|
||||||
@ -367,7 +379,7 @@ class Template:
|
|||||||
return self.template
|
return self.template
|
||||||
return self._parse_result(self.template)
|
return self._parse_result(self.template)
|
||||||
|
|
||||||
compiled = self._compiled or self._ensure_compiled(limited)
|
compiled = self._compiled or self._ensure_compiled(limited, strict)
|
||||||
|
|
||||||
if variables is not None:
|
if variables is not None:
|
||||||
kwargs.update(variables)
|
kwargs.update(variables)
|
||||||
@ -418,7 +430,11 @@ class Template:
|
|||||||
return render_result
|
return render_result
|
||||||
|
|
||||||
async def async_render_will_timeout(
|
async def async_render_will_timeout(
|
||||||
self, timeout: float, variables: TemplateVarsType = None, **kwargs: Any
|
self,
|
||||||
|
timeout: float,
|
||||||
|
variables: TemplateVarsType = None,
|
||||||
|
strict: bool = False,
|
||||||
|
**kwargs: Any,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Check to see if rendering a template will timeout during render.
|
"""Check to see if rendering a template will timeout during render.
|
||||||
|
|
||||||
@ -436,11 +452,12 @@ class Template:
|
|||||||
if self.is_static:
|
if self.is_static:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
compiled = self._compiled or self._ensure_compiled()
|
compiled = self._compiled or self._ensure_compiled(strict=strict)
|
||||||
|
|
||||||
if variables is not None:
|
if variables is not None:
|
||||||
kwargs.update(variables)
|
kwargs.update(variables)
|
||||||
|
|
||||||
|
self._exc_info = None
|
||||||
finish_event = asyncio.Event()
|
finish_event = asyncio.Event()
|
||||||
|
|
||||||
def _render_template() -> None:
|
def _render_template() -> None:
|
||||||
@ -448,6 +465,8 @@ class Template:
|
|||||||
_render_with_context(self.template, compiled, **kwargs)
|
_render_with_context(self.template, compiled, **kwargs)
|
||||||
except TimeoutError:
|
except TimeoutError:
|
||||||
pass
|
pass
|
||||||
|
except Exception: # pylint: disable=broad-except
|
||||||
|
self._exc_info = sys.exc_info()
|
||||||
finally:
|
finally:
|
||||||
run_callback_threadsafe(self.hass.loop, finish_event.set)
|
run_callback_threadsafe(self.hass.loop, finish_event.set)
|
||||||
|
|
||||||
@ -455,6 +474,8 @@ class Template:
|
|||||||
template_render_thread = ThreadWithException(target=_render_template)
|
template_render_thread = ThreadWithException(target=_render_template)
|
||||||
template_render_thread.start()
|
template_render_thread.start()
|
||||||
await asyncio.wait_for(finish_event.wait(), timeout=timeout)
|
await asyncio.wait_for(finish_event.wait(), timeout=timeout)
|
||||||
|
if self._exc_info:
|
||||||
|
raise TemplateError(self._exc_info[1].with_traceback(self._exc_info[2]))
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
template_render_thread.raise_exc(TimeoutError)
|
template_render_thread.raise_exc(TimeoutError)
|
||||||
return True
|
return True
|
||||||
@ -465,7 +486,7 @@ class Template:
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
def async_render_to_info(
|
def async_render_to_info(
|
||||||
self, variables: TemplateVarsType = None, **kwargs: Any
|
self, variables: TemplateVarsType = None, strict: bool = False, **kwargs: Any
|
||||||
) -> RenderInfo:
|
) -> RenderInfo:
|
||||||
"""Render the template and collect an entity filter."""
|
"""Render the template and collect an entity filter."""
|
||||||
assert self.hass and _RENDER_INFO not in self.hass.data
|
assert self.hass and _RENDER_INFO not in self.hass.data
|
||||||
@ -480,7 +501,7 @@ class Template:
|
|||||||
|
|
||||||
self.hass.data[_RENDER_INFO] = render_info
|
self.hass.data[_RENDER_INFO] = render_info
|
||||||
try:
|
try:
|
||||||
render_info._result = self.async_render(variables, **kwargs)
|
render_info._result = self.async_render(variables, strict=strict, **kwargs)
|
||||||
except TemplateError as ex:
|
except TemplateError as ex:
|
||||||
render_info.exception = ex
|
render_info.exception = ex
|
||||||
finally:
|
finally:
|
||||||
@ -540,7 +561,9 @@ class Template:
|
|||||||
)
|
)
|
||||||
return value if error_value is _SENTINEL else error_value
|
return value if error_value is _SENTINEL else error_value
|
||||||
|
|
||||||
def _ensure_compiled(self, limited: bool = False) -> jinja2.Template:
|
def _ensure_compiled(
|
||||||
|
self, limited: bool = False, strict: bool = False
|
||||||
|
) -> jinja2.Template:
|
||||||
"""Bind a template to a specific hass instance."""
|
"""Bind a template to a specific hass instance."""
|
||||||
self.ensure_valid()
|
self.ensure_valid()
|
||||||
|
|
||||||
@ -548,8 +571,13 @@ class Template:
|
|||||||
assert (
|
assert (
|
||||||
self._limited is None or self._limited == limited
|
self._limited is None or self._limited == limited
|
||||||
), "can't change between limited and non limited template"
|
), "can't change between limited and non limited template"
|
||||||
|
assert (
|
||||||
|
self._strict is None or self._strict == strict
|
||||||
|
), "can't change between strict and non strict template"
|
||||||
|
assert not (strict and limited), "can't combine strict and limited template"
|
||||||
|
|
||||||
self._limited = limited
|
self._limited = limited
|
||||||
|
self._strict = strict
|
||||||
env = self._env
|
env = self._env
|
||||||
|
|
||||||
self._compiled = cast(
|
self._compiled = cast(
|
||||||
@ -1369,9 +1397,13 @@ class LoggingUndefined(jinja2.Undefined):
|
|||||||
class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
||||||
"""The Home Assistant template environment."""
|
"""The Home Assistant template environment."""
|
||||||
|
|
||||||
def __init__(self, hass, limited=False):
|
def __init__(self, hass, limited=False, strict=False):
|
||||||
"""Initialise template environment."""
|
"""Initialise template environment."""
|
||||||
super().__init__(undefined=LoggingUndefined)
|
if not strict:
|
||||||
|
undefined = LoggingUndefined
|
||||||
|
else:
|
||||||
|
undefined = jinja2.StrictUndefined
|
||||||
|
super().__init__(undefined=undefined)
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self.template_cache = weakref.WeakValueDictionary()
|
self.template_cache = weakref.WeakValueDictionary()
|
||||||
self.filters["round"] = forgiving_round
|
self.filters["round"] = forgiving_round
|
||||||
|
@ -697,10 +697,19 @@ async def test_render_template_manual_entity_ids_no_longer_needed(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def test_render_template_with_error(hass, websocket_client, caplog):
|
@pytest.mark.parametrize(
|
||||||
|
"template",
|
||||||
|
[
|
||||||
|
"{{ my_unknown_func() + 1 }}",
|
||||||
|
"{{ my_unknown_var }}",
|
||||||
|
"{{ my_unknown_var + 1 }}",
|
||||||
|
"{{ now() | unknown_filter }}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_render_template_with_error(hass, websocket_client, caplog, template):
|
||||||
"""Test a template with an error."""
|
"""Test a template with an error."""
|
||||||
await websocket_client.send_json(
|
await websocket_client.send_json(
|
||||||
{"id": 5, "type": "render_template", "template": "{{ my_unknown_var() + 1 }}"}
|
{"id": 5, "type": "render_template", "template": template, "strict": True}
|
||||||
)
|
)
|
||||||
|
|
||||||
msg = await websocket_client.receive_json()
|
msg = await websocket_client.receive_json()
|
||||||
@ -709,17 +718,30 @@ async def test_render_template_with_error(hass, websocket_client, caplog):
|
|||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
||||||
|
|
||||||
|
assert "Template variable error" not in caplog.text
|
||||||
assert "TemplateError" not in caplog.text
|
assert "TemplateError" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
async def test_render_template_with_timeout_and_error(hass, websocket_client, caplog):
|
@pytest.mark.parametrize(
|
||||||
|
"template",
|
||||||
|
[
|
||||||
|
"{{ my_unknown_func() + 1 }}",
|
||||||
|
"{{ my_unknown_var }}",
|
||||||
|
"{{ my_unknown_var + 1 }}",
|
||||||
|
"{{ now() | unknown_filter }}",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
async def test_render_template_with_timeout_and_error(
|
||||||
|
hass, websocket_client, caplog, template
|
||||||
|
):
|
||||||
"""Test a template with an error with a timeout."""
|
"""Test a template with an error with a timeout."""
|
||||||
await websocket_client.send_json(
|
await websocket_client.send_json(
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"type": "render_template",
|
"type": "render_template",
|
||||||
"template": "{{ now() | rando }}",
|
"template": template,
|
||||||
"timeout": 5,
|
"timeout": 5,
|
||||||
|
"strict": True,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -729,6 +751,7 @@ async def test_render_template_with_timeout_and_error(hass, websocket_client, ca
|
|||||||
assert not msg["success"]
|
assert not msg["success"]
|
||||||
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
assert msg["error"]["code"] == const.ERR_TEMPLATE_ERROR
|
||||||
|
|
||||||
|
assert "Template variable error" not in caplog.text
|
||||||
assert "TemplateError" not in caplog.text
|
assert "TemplateError" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
@ -2267,9 +2267,6 @@ async def test_template_timeout(hass):
|
|||||||
tmp = template.Template("{{ states | count }}", hass)
|
tmp = template.Template("{{ states | count }}", hass)
|
||||||
assert await tmp.async_render_will_timeout(3) is False
|
assert await tmp.async_render_will_timeout(3) is False
|
||||||
|
|
||||||
tmp2 = template.Template("{{ error_invalid + 1 }}", hass)
|
|
||||||
assert await tmp2.async_render_will_timeout(3) is False
|
|
||||||
|
|
||||||
tmp3 = template.Template("static", hass)
|
tmp3 = template.Template("static", hass)
|
||||||
assert await tmp3.async_render_will_timeout(3) is False
|
assert await tmp3.async_render_will_timeout(3) is False
|
||||||
|
|
||||||
@ -2287,6 +2284,13 @@ async def test_template_timeout(hass):
|
|||||||
assert await tmp5.async_render_will_timeout(0.000001) is True
|
assert await tmp5.async_render_will_timeout(0.000001) is True
|
||||||
|
|
||||||
|
|
||||||
|
async def test_template_timeout_raise(hass):
|
||||||
|
"""Test we can raise from."""
|
||||||
|
tmp2 = template.Template("{{ error_invalid + 1 }}", hass)
|
||||||
|
with pytest.raises(TemplateError):
|
||||||
|
assert await tmp2.async_render_will_timeout(3) is False
|
||||||
|
|
||||||
|
|
||||||
async def test_lights(hass):
|
async def test_lights(hass):
|
||||||
"""Test we can sort lights."""
|
"""Test we can sort lights."""
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user