diff --git a/homeassistant/components/template/entity.py b/homeassistant/components/template/entity.py index 5655545b445..7e110c6380a 100644 --- a/homeassistant/components/template/entity.py +++ b/homeassistant/components/template/entity.py @@ -104,7 +104,7 @@ class AbstractTemplateEntity(Entity): validator: Callable[[Any], Any] | None = None, on_update: Callable[[Any], None] | None = None, render_complex: bool = False, - **kwargs, + none_on_template_error: bool = True, ) -> None: """Set up a template that manages any property or attribute of the entity. @@ -124,6 +124,9 @@ class AbstractTemplateEntity(Entity): This signals trigger based template entities to render the template as a complex result. State based template entities always render complex results. + none_on_template_error (default=True) + If set to false, template errors will be supplied in the result to + on_update. """ def add_template( diff --git a/homeassistant/components/template/template_entity.py b/homeassistant/components/template/template_entity.py index 90b67dcc113..953a5a89542 100644 --- a/homeassistant/components/template/template_entity.py +++ b/homeassistant/components/template/template_entity.py @@ -305,7 +305,9 @@ class TemplateEntity(AbstractTemplateEntity): else: setattr(self, attribute, state) - self.add_template(option, attribute, on_update=_update_state) + self.add_template( + option, attribute, on_update=_update_state, none_on_template_error=False + ) def setup_template( self, @@ -314,7 +316,7 @@ class TemplateEntity(AbstractTemplateEntity): validator: Callable[[Any], Any] | None = None, on_update: Callable[[Any], None] | None = None, render_complex: bool = False, - **kwargs, + none_on_template_error: bool = True, ): """Set up a template that manages any property or attribute of the entity. @@ -334,8 +336,10 @@ class TemplateEntity(AbstractTemplateEntity): This signals trigger based template entities to render the template as a complex result. State based template entities always render complex results. + none_on_template_error (default=True) + If set to false, template errors will be supplied in the result to + on_update. """ - none_on_template_error = kwargs.get("none_on_template_error", True) self.add_template( option, attribute, validator, on_update, none_on_template_error ) diff --git a/homeassistant/components/template/trigger_entity.py b/homeassistant/components/template/trigger_entity.py index a9c3c62d2b0..734baf7904a 100644 --- a/homeassistant/components/template/trigger_entity.py +++ b/homeassistant/components/template/trigger_entity.py @@ -7,9 +7,16 @@ from typing import Any from homeassistant.const import CONF_STATE, CONF_VARIABLES from homeassistant.core import HomeAssistant, callback +from homeassistant.exceptions import TemplateError from homeassistant.helpers.script_variables import ScriptVariables -from homeassistant.helpers.template import _SENTINEL -from homeassistant.helpers.trigger_template_entity import TriggerBaseEntity +from homeassistant.helpers.template import ( + _SENTINEL, + render_complex as template_render_complex, +) +from homeassistant.helpers.trigger_template_entity import ( + TriggerBaseEntity, + log_triggered_template_error, +) from homeassistant.helpers.update_coordinator import CoordinatorEntity from . import TriggerUpdateCoordinator @@ -59,7 +66,9 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module on_update: Callable[[Any], None] | None = None, ) -> None: """Set up a template that manages the main state of the entity.""" - if self.add_template(option, attribute, validator, on_update): + if self.add_template( + option, attribute, validator, on_update, none_on_template_error=False + ): self._to_render_simple.append(option) self._parse_result.add(option) @@ -70,7 +79,7 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module validator: Callable[[Any], Any] | None = None, on_update: Callable[[Any], None] | None = None, render_complex: bool = False, - **kwargs, + none_on_template_error: bool = True, ) -> None: """Set up a template that manages any property or attribute of the entity. @@ -90,8 +99,13 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module This signals trigger based template entities to render the template as a complex result. State based template entities always render complex results. + none_on_template_error (default=True) + If set to false, template errors will be supplied in the result to + on_update. """ - if self.add_template(option, attribute, validator, on_update): + if self.add_template( + option, attribute, validator, on_update, none_on_template_error + ): if render_complex: self._to_render_complex.append(option) else: @@ -116,6 +130,33 @@ class TriggerEntity( # pylint: disable=hass-enforce-class-module """Render configured variables.""" return self._rendered_entity_variables or {} + def _render_single_template( + self, + key: str, + variables: dict[str, Any], + strict: bool = False, + ) -> Any: + """Render a single template.""" + try: + if key in self._to_render_complex: + return template_render_complex(self._config[key], variables) + + return self._config[key].async_render( + variables, parse_result=key in self._parse_result, strict=strict + ) + except TemplateError as err: + log_triggered_template_error(self.entity_id, err, key=key) + # Filter out state templates because they have unique behavior + # with none_on_template_error. + if ( + key != CONF_STATE + and key in self._templates + and not self._templates[key].none_on_template_error + ): + return err + + return _SENTINEL + def _render_templates(self, variables: dict[str, Any]) -> None: """Render templates.""" self._state_render_error = False diff --git a/tests/components/template/test_lock.py b/tests/components/template/test_lock.py index 3376dc7f7a6..8b61b9489af 100644 --- a/tests/components/template/test_lock.py +++ b/tests/components/template/test_lock.py @@ -757,16 +757,16 @@ async def test_lock_actions_fail_with_invalid_code( ], ) @pytest.mark.parametrize( - ("style", "attribute", "expected"), + ("style", "attribute"), [ - (ConfigurationStyle.LEGACY, "code_format_template", 0), - (ConfigurationStyle.MODERN, "code_format", 0), - (ConfigurationStyle.TRIGGER, "code_format", 2), + (ConfigurationStyle.LEGACY, "code_format_template"), + (ConfigurationStyle.MODERN, "code_format"), + (ConfigurationStyle.TRIGGER, "code_format"), ], ) @pytest.mark.usefixtures("setup_state_lock_with_attribute") async def test_lock_actions_dont_execute_with_code_template_rendering_error( - hass: HomeAssistant, calls: list[ServiceCall], expected: int + hass: HomeAssistant, calls: list[ServiceCall] ) -> None: """Test lock code format rendering fails block lock/unlock actions.""" @@ -786,10 +786,7 @@ async def test_lock_actions_dont_execute_with_code_template_rendering_error( ) await hass.async_block_till_done() - # Trigger expects calls here because trigger based entities don't - # allow template exception resolutions into code_format property so - # the actions will fire using the previous code_format. - assert len(calls) == expected + assert len(calls) == 0 @pytest.mark.parametrize(