Allow skip parsing template result (#42401)
This commit is contained in:
parent
80e8068b46
commit
45aba9bdf2
10 changed files with 98 additions and 24 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue