diff --git a/homeassistant/components/api/__init__.py b/homeassistant/components/api/__init__.py index 779df785164..72fb2636067 100644 --- a/homeassistant/components/api/__init__.py +++ b/homeassistant/components/api/__init__.py @@ -410,7 +410,7 @@ class APITemplateView(HomeAssistantView): try: data = await request.json() tpl = template.Template(data["template"], request.app["hass"]) - return str(tpl.async_render(data.get("variables"))) + return tpl.async_render(variables=data.get("variables"), parse_result=False) except (ValueError, TemplateError) as ex: return self.json_message( f"Error rendering template: {ex}", HTTP_BAD_REQUEST diff --git a/homeassistant/components/shell_command/__init__.py b/homeassistant/components/shell_command/__init__.py index bce980035dc..0d221cb6b0b 100644 --- a/homeassistant/components/shell_command/__init__.py +++ b/homeassistant/components/shell_command/__init__.py @@ -45,7 +45,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool: if args_compiled: try: - rendered_args = args_compiled.async_render(service.data) + rendered_args = args_compiled.async_render( + variables=service.data, parse_result=False + ) except TemplateError as ex: _LOGGER.exception("Error rendering command template: %s", ex) return diff --git a/homeassistant/components/template/cover.py b/homeassistant/components/template/cover.py index 7c35663bb5f..cc91bdabd7d 100644 --- a/homeassistant/components/template/cover.py +++ b/homeassistant/components/template/cover.py @@ -39,6 +39,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -258,7 +259,11 @@ class CoverTemplate(TemplateEntity, CoverEntity): self._position = None return - state = str(result).lower() + if isinstance(result, ResultWrapper): + state = result.render_result.lower() + else: + state = str(result).lower() + if state in _VALID_STATES: if state in ("true", STATE_OPEN): self._position = 100 diff --git a/homeassistant/components/template/fan.py b/homeassistant/components/template/fan.py index f0b22fb46b3..e5b3569e14b 100644 --- a/homeassistant/components/template/fan.py +++ b/homeassistant/components/template/fan.py @@ -34,6 +34,7 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -367,7 +368,11 @@ class TemplateFan(TemplateEntity, FanEntity): @callback def _update_speed(self, speed): # Validate speed - speed = str(speed) + if isinstance(speed, ResultWrapper): + speed = speed.render_result + else: + speed = str(speed) + if speed in self._speed_list: self._speed = speed elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]: diff --git a/homeassistant/components/template/light.py b/homeassistant/components/template/light.py index 5d6c1befd4a..84939beecad 100644 --- a/homeassistant/components/template/light.py +++ b/homeassistant/components/template/light.py @@ -33,6 +33,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.script import Script +from homeassistant.helpers.template import ResultWrapper from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .template_entity import TemplateEntity @@ -405,23 +406,28 @@ class LightTemplate(TemplateEntity, LightEntity): def _update_state(self, result): """Update the state from the template.""" - if isinstance(result, TemplateError): + if isinstance(result, (TemplateError, ResultWrapper)): # This behavior is legacy self._state = False if not self._availability_template: self._available = True return + if isinstance(result, bool): + self._state = result + return + state = str(result).lower() if state in _VALID_STATES: self._state = state in ("true", STATE_ON) - else: - _LOGGER.error( - "Received invalid light is_on state: %s. Expected: %s", - state, - ", ".join(_VALID_STATES), - ) - self._state = None + return + + _LOGGER.error( + "Received invalid light is_on state: %s. Expected: %s", + state, + ", ".join(_VALID_STATES), + ) + self._state = None @callback def _update_temperature(self, render): diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index 57271ccaf81..9cfa6c00996 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -461,7 +461,10 @@ def async_template( if isinstance(value, bool): return value - return str(value).lower() == "true" + if isinstance(value, str): + return value.lower() == "true" + + return False def async_template_from_config( diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py index de131d8dc5c..75846100161 100644 --- a/homeassistant/helpers/template.py +++ b/homeassistant/helpers/template.py @@ -4,7 +4,7 @@ import asyncio import base64 import collections.abc from datetime import datetime, timedelta -from functools import wraps +from functools import partial, wraps import json import logging import math @@ -360,28 +360,36 @@ class Template: return extract_entities(self.hass, self.template, variables) - def render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any: + def render( + self, + variables: TemplateVarsType = None, + parse_result: bool = True, + **kwargs: Any, + ) -> Any: """Render given template.""" if self.is_static: - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return self.template return self._parse_result(self.template) - if variables is not None: - kwargs.update(variables) - return run_callback_threadsafe( - self.hass.loop, self.async_render, kwargs + self.hass.loop, + partial(self.async_render, variables, parse_result, **kwargs), ).result() @callback - def async_render(self, variables: TemplateVarsType = None, **kwargs: Any) -> Any: + def async_render( + self, + variables: TemplateVarsType = None, + parse_result: bool = True, + **kwargs: Any, + ) -> Any: """Render given template. This method must be run in the event loop. """ if self.is_static: - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return self.template return self._parse_result(self.template) @@ -397,7 +405,7 @@ class Template: render_result = render_result.strip() - if self.hass.config.legacy_templates: + if self.hass.config.legacy_templates or not parse_result: return render_result return self._parse_result(render_result) diff --git a/tests/components/shell_command/test_init.py b/tests/components/shell_command/test_init.py index 440375438c1..f5ad37cc617 100644 --- a/tests/components/shell_command/test_init.py +++ b/tests/components/shell_command/test_init.py @@ -174,7 +174,7 @@ async def test_do_no_run_forever(hass, caplog): assert await async_setup_component( hass, shell_command.DOMAIN, - {shell_command.DOMAIN: {"test_service": "sleep 10000s"}}, + {shell_command.DOMAIN: {"test_service": "sleep 10000"}}, ) await hass.async_block_till_done() diff --git a/tests/helpers/test_condition.py b/tests/helpers/test_condition.py index 001c59e2f2c..802ad7699e3 100644 --- a/tests/helpers/test_condition.py +++ b/tests/helpers/test_condition.py @@ -915,3 +915,26 @@ async def test_condition_template_error(hass, caplog): assert caplog.records[0].message.startswith( "Error during template condition: UndefinedError:" ) + + +async def test_condition_template_invalid_results(hass): + """Test template condition render false with invalid results.""" + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 'string' }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 10.1 }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ 42 }}"} + ) + assert not test(hass) + + test = await condition.async_from_config( + hass, {"condition": "template", "value_template": "{{ [1, 2, 3] }}"} + ) + assert not test(hass) diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py index 86e17f8cb0b..750978b84ee 100644 --- a/tests/helpers/test_template.py +++ b/tests/helpers/test_template.py @@ -2653,6 +2653,28 @@ async def test_legacy_templates(hass): ) +async def test_no_result_parsing(hass): + """Test if templates results are not parsed.""" + hass.states.async_set("sensor.temperature", "12") + + assert ( + template.Template("{{ states.sensor.temperature.state }}", hass).async_render( + parse_result=False + ) + == "12" + ) + + assert ( + template.Template("{{ false }}", hass).async_render(parse_result=False) + == "False" + ) + + assert ( + template.Template("{{ [1, 2, 3] }}", hass).async_render(parse_result=False) + == "[1, 2, 3]" + ) + + async def test_is_static_still_ast_evals(hass): """Test is_static still convers to native type.""" tpl = template.Template("[1, 2]", hass)