Allow skip parsing template result (#42401)

This commit is contained in:
Franck Nijhof 2020-10-26 16:01:09 +01:00 committed by GitHub
parent 80e8068b46
commit 45aba9bdf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 98 additions and 24 deletions

View file

@ -410,7 +410,7 @@ class APITemplateView(HomeAssistantView):
try: try:
data = await request.json() data = await request.json()
tpl = template.Template(data["template"], request.app["hass"]) 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: except (ValueError, TemplateError) as ex:
return self.json_message( return self.json_message(
f"Error rendering template: {ex}", HTTP_BAD_REQUEST f"Error rendering template: {ex}", HTTP_BAD_REQUEST

View file

@ -45,7 +45,9 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
if args_compiled: if args_compiled:
try: 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: except TemplateError as ex:
_LOGGER.exception("Error rendering command template: %s", ex) _LOGGER.exception("Error rendering command template: %s", ex)
return return

View file

@ -39,6 +39,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from homeassistant.helpers.template import ResultWrapper
from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS
from .template_entity import TemplateEntity from .template_entity import TemplateEntity
@ -258,7 +259,11 @@ class CoverTemplate(TemplateEntity, CoverEntity):
self._position = None self._position = None
return 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 _VALID_STATES:
if state in ("true", STATE_OPEN): if state in ("true", STATE_OPEN):
self._position = 100 self._position = 100

View file

@ -34,6 +34,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from homeassistant.helpers.template import ResultWrapper
from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS
from .template_entity import TemplateEntity from .template_entity import TemplateEntity
@ -367,7 +368,11 @@ class TemplateFan(TemplateEntity, FanEntity):
@callback @callback
def _update_speed(self, speed): def _update_speed(self, speed):
# Validate speed # Validate speed
speed = str(speed) if isinstance(speed, ResultWrapper):
speed = speed.render_result
else:
speed = str(speed)
if speed in self._speed_list: if speed in self._speed_list:
self._speed = speed self._speed = speed
elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]: elif speed in [STATE_UNAVAILABLE, STATE_UNKNOWN]:

View file

@ -33,6 +33,7 @@ from homeassistant.helpers.config_validation import PLATFORM_SCHEMA
from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.helpers.reload import async_setup_reload_service from homeassistant.helpers.reload import async_setup_reload_service
from homeassistant.helpers.script import Script from homeassistant.helpers.script import Script
from homeassistant.helpers.template import ResultWrapper
from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS from .const import CONF_AVAILABILITY_TEMPLATE, DOMAIN, PLATFORMS
from .template_entity import TemplateEntity from .template_entity import TemplateEntity
@ -405,23 +406,28 @@ class LightTemplate(TemplateEntity, LightEntity):
def _update_state(self, result): def _update_state(self, result):
"""Update the state from the template.""" """Update the state from the template."""
if isinstance(result, TemplateError): if isinstance(result, (TemplateError, ResultWrapper)):
# This behavior is legacy # This behavior is legacy
self._state = False self._state = False
if not self._availability_template: if not self._availability_template:
self._available = True self._available = True
return return
if isinstance(result, bool):
self._state = result
return
state = str(result).lower() state = str(result).lower()
if state in _VALID_STATES: if state in _VALID_STATES:
self._state = state in ("true", STATE_ON) self._state = state in ("true", STATE_ON)
else: return
_LOGGER.error(
"Received invalid light is_on state: %s. Expected: %s", _LOGGER.error(
state, "Received invalid light is_on state: %s. Expected: %s",
", ".join(_VALID_STATES), state,
) ", ".join(_VALID_STATES),
self._state = None )
self._state = None
@callback @callback
def _update_temperature(self, render): def _update_temperature(self, render):

View file

@ -461,7 +461,10 @@ def async_template(
if isinstance(value, bool): if isinstance(value, bool):
return value return value
return str(value).lower() == "true" if isinstance(value, str):
return value.lower() == "true"
return False
def async_template_from_config( def async_template_from_config(

View file

@ -4,7 +4,7 @@ import asyncio
import base64 import base64
import collections.abc import collections.abc
from datetime import datetime, timedelta from datetime import datetime, timedelta
from functools import wraps from functools import partial, wraps
import json import json
import logging import logging
import math import math
@ -360,28 +360,36 @@ class Template:
return extract_entities(self.hass, self.template, variables) 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.""" """Render given template."""
if self.is_static: 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.template
return self._parse_result(self.template) return self._parse_result(self.template)
if variables is not None:
kwargs.update(variables)
return run_callback_threadsafe( return run_callback_threadsafe(
self.hass.loop, self.async_render, kwargs self.hass.loop,
partial(self.async_render, variables, parse_result, **kwargs),
).result() ).result()
@callback @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. """Render given template.
This method must be run in the event loop. This method must be run in the event loop.
""" """
if self.is_static: 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.template
return self._parse_result(self.template) return self._parse_result(self.template)
@ -397,7 +405,7 @@ class Template:
render_result = render_result.strip() 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 render_result
return self._parse_result(render_result) return self._parse_result(render_result)

View file

@ -174,7 +174,7 @@ async def test_do_no_run_forever(hass, caplog):
assert await async_setup_component( assert await async_setup_component(
hass, hass,
shell_command.DOMAIN, shell_command.DOMAIN,
{shell_command.DOMAIN: {"test_service": "sleep 10000s"}}, {shell_command.DOMAIN: {"test_service": "sleep 10000"}},
) )
await hass.async_block_till_done() await hass.async_block_till_done()

View file

@ -915,3 +915,26 @@ async def test_condition_template_error(hass, caplog):
assert caplog.records[0].message.startswith( assert caplog.records[0].message.startswith(
"Error during template condition: UndefinedError:" "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)

View file

@ -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): async def test_is_static_still_ast_evals(hass):
"""Test is_static still convers to native type.""" """Test is_static still convers to native type."""
tpl = template.Template("[1, 2]", hass) tpl = template.Template("[1, 2]", hass)