Allow skip parsing template result (#42401)
parent
b7c958719e
commit
f716b7714b
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue