mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 08:47:57 +00:00
Allow templates in data & service parameters (making data_template & service_template obsolete) (#39210)
This commit is contained in:
parent
a47f73244c
commit
181709f3d2
@ -429,6 +429,7 @@ def service(value: Any) -> str:
|
||||
str_value = string(value).lower()
|
||||
if valid_entity_id(str_value):
|
||||
return str_value
|
||||
|
||||
raise vol.Invalid(f"Service {value} does not match format <domain>.<name>")
|
||||
|
||||
|
||||
@ -527,6 +528,24 @@ def template(value: Optional[Any]) -> template_helper.Template:
|
||||
raise vol.Invalid(f"invalid template ({ex})")
|
||||
|
||||
|
||||
def dynamic_template(value: Optional[Any]) -> template_helper.Template:
|
||||
"""Validate a dynamic (non static) jinja2 template."""
|
||||
|
||||
if value is None:
|
||||
raise vol.Invalid("template value is None")
|
||||
if isinstance(value, (list, dict, template_helper.Template)):
|
||||
raise vol.Invalid("template value should be a string")
|
||||
if not template_helper.is_template_string(str(value)):
|
||||
raise vol.Invalid("template value does not contain a dynmamic template")
|
||||
|
||||
template_value = template_helper.Template(str(value)) # type: ignore
|
||||
try:
|
||||
template_value.ensure_valid()
|
||||
return cast(template_helper.Template, template_value)
|
||||
except TemplateError as ex:
|
||||
raise vol.Invalid(f"invalid template ({ex})")
|
||||
|
||||
|
||||
def template_complex(value: Any) -> Any:
|
||||
"""Validate a complex jinja2 template."""
|
||||
if isinstance(value, list):
|
||||
@ -858,8 +877,8 @@ EVENT_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Required(CONF_EVENT): string,
|
||||
vol.Optional(CONF_EVENT_DATA): dict,
|
||||
vol.Optional(CONF_EVENT_DATA_TEMPLATE): template_complex,
|
||||
vol.Optional(CONF_EVENT_DATA): vol.All(dict, template_complex),
|
||||
vol.Optional(CONF_EVENT_DATA_TEMPLATE): vol.All(dict, template_complex),
|
||||
}
|
||||
)
|
||||
|
||||
@ -867,10 +886,14 @@ SERVICE_SCHEMA = vol.All(
|
||||
vol.Schema(
|
||||
{
|
||||
vol.Optional(CONF_ALIAS): string,
|
||||
vol.Exclusive(CONF_SERVICE, "service name"): service,
|
||||
vol.Exclusive(CONF_SERVICE_TEMPLATE, "service name"): template,
|
||||
vol.Optional("data"): dict,
|
||||
vol.Optional("data_template"): template_complex,
|
||||
vol.Exclusive(CONF_SERVICE, "service name"): vol.Any(
|
||||
service, dynamic_template
|
||||
),
|
||||
vol.Exclusive(CONF_SERVICE_TEMPLATE, "service name"): vol.Any(
|
||||
service, dynamic_template
|
||||
),
|
||||
vol.Optional("data"): vol.All(dict, template_complex),
|
||||
vol.Optional("data_template"): vol.All(dict, template_complex),
|
||||
vol.Optional(CONF_ENTITY_ID): comp_entity_ids,
|
||||
}
|
||||
),
|
||||
|
@ -439,17 +439,18 @@ class _ScriptRun:
|
||||
CONF_ALIAS, self._action[CONF_EVENT]
|
||||
)
|
||||
self._log("Executing step %s", self._script.last_action)
|
||||
event_data = dict(self._action.get(CONF_EVENT_DATA, {}))
|
||||
if CONF_EVENT_DATA_TEMPLATE in self._action:
|
||||
event_data = {}
|
||||
for conf in [CONF_EVENT_DATA, CONF_EVENT_DATA_TEMPLATE]:
|
||||
if conf not in self._action:
|
||||
continue
|
||||
|
||||
try:
|
||||
event_data.update(
|
||||
template.render_complex(
|
||||
self._action[CONF_EVENT_DATA_TEMPLATE], self._variables
|
||||
)
|
||||
template.render_complex(self._action[conf], self._variables)
|
||||
)
|
||||
except exceptions.TemplateError as ex:
|
||||
self._log(
|
||||
"Error rendering event data template: %s", ex, level=logging.ERROR
|
||||
"Error rendering event data template: %s", ex, level=logging.ERROR,
|
||||
)
|
||||
|
||||
self._hass.bus.async_fire(
|
||||
|
@ -35,6 +35,7 @@ from homeassistant.exceptions import (
|
||||
)
|
||||
from homeassistant.helpers import template
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.template import Template
|
||||
from homeassistant.helpers.typing import ConfigType, HomeAssistantType, TemplateVarsType
|
||||
from homeassistant.loader import async_get_integration, bind_hass
|
||||
from homeassistant.util.yaml import load_yaml
|
||||
@ -110,9 +111,12 @@ def async_prepare_call_from_config(
|
||||
if CONF_SERVICE in config:
|
||||
domain_service = config[CONF_SERVICE]
|
||||
else:
|
||||
domain_service = config[CONF_SERVICE_TEMPLATE]
|
||||
|
||||
if isinstance(domain_service, Template):
|
||||
try:
|
||||
config[CONF_SERVICE_TEMPLATE].hass = hass
|
||||
domain_service = config[CONF_SERVICE_TEMPLATE].async_render(variables)
|
||||
domain_service.hass = hass
|
||||
domain_service = domain_service.async_render(variables)
|
||||
domain_service = cv.service(domain_service)
|
||||
except TemplateError as ex:
|
||||
raise HomeAssistantError(
|
||||
@ -124,14 +128,14 @@ def async_prepare_call_from_config(
|
||||
) from ex
|
||||
|
||||
domain, service = domain_service.split(".", 1)
|
||||
service_data = dict(config.get(CONF_SERVICE_DATA, {}))
|
||||
|
||||
if CONF_SERVICE_DATA_TEMPLATE in config:
|
||||
service_data = {}
|
||||
for conf in [CONF_SERVICE_DATA, CONF_SERVICE_DATA_TEMPLATE]:
|
||||
if conf not in config:
|
||||
continue
|
||||
try:
|
||||
template.attach(hass, config[CONF_SERVICE_DATA_TEMPLATE])
|
||||
service_data.update(
|
||||
template.render_complex(config[CONF_SERVICE_DATA_TEMPLATE], variables)
|
||||
)
|
||||
template.attach(hass, config[conf])
|
||||
service_data.update(template.render_complex(config[conf], variables))
|
||||
except TemplateError as ex:
|
||||
raise HomeAssistantError(f"Error rendering data template: {ex}") from ex
|
||||
|
||||
|
@ -443,6 +443,29 @@ def test_template():
|
||||
schema(value)
|
||||
|
||||
|
||||
def test_dynamic_template():
|
||||
"""Test dynamic template validator."""
|
||||
schema = vol.Schema(cv.dynamic_template)
|
||||
|
||||
for value in (
|
||||
None,
|
||||
1,
|
||||
"{{ partial_print }",
|
||||
"{% if True %}Hello",
|
||||
["test"],
|
||||
"just a string",
|
||||
):
|
||||
with pytest.raises(vol.Invalid):
|
||||
schema(value)
|
||||
|
||||
options = (
|
||||
"{{ beer }}",
|
||||
"{% if 1 == 1 %}Hello{% else %}World{% endif %}",
|
||||
)
|
||||
for value in options:
|
||||
schema(value)
|
||||
|
||||
|
||||
def test_template_complex():
|
||||
"""Test template_complex validator."""
|
||||
schema = vol.Schema(cv.template_complex)
|
||||
|
@ -71,7 +71,7 @@ async def test_firing_event_template(hass):
|
||||
sequence = cv.SCRIPT_SCHEMA(
|
||||
{
|
||||
"event": event,
|
||||
"event_data_template": {
|
||||
"event_data": {
|
||||
"dict": {
|
||||
1: "{{ is_world }}",
|
||||
2: "{{ is_world }}{{ is_world }}",
|
||||
@ -79,6 +79,14 @@ async def test_firing_event_template(hass):
|
||||
},
|
||||
"list": ["{{ is_world }}", "{{ is_world }}{{ is_world }}"],
|
||||
},
|
||||
"event_data_template": {
|
||||
"dict2": {
|
||||
1: "{{ is_world }}",
|
||||
2: "{{ is_world }}{{ is_world }}",
|
||||
3: "{{ is_world }}{{ is_world }}{{ is_world }}",
|
||||
},
|
||||
"list2": ["{{ is_world }}", "{{ is_world }}{{ is_world }}"],
|
||||
},
|
||||
}
|
||||
)
|
||||
script_obj = script.Script(hass, sequence, "Test Name", "test_domain")
|
||||
@ -91,6 +99,8 @@ async def test_firing_event_template(hass):
|
||||
assert events[0].data == {
|
||||
"dict": {1: "yes", 2: "yesyes", 3: "yesyesyes"},
|
||||
"list": ["yes", "yesyes"],
|
||||
"dict2": {1: "yes", 2: "yesyes", 3: "yesyesyes"},
|
||||
"list2": ["yes", "yesyes"],
|
||||
}
|
||||
|
||||
|
||||
|
@ -144,16 +144,16 @@ class TestServiceHelpers(unittest.TestCase):
|
||||
"""Stop down everything that was started."""
|
||||
self.hass.stop()
|
||||
|
||||
def test_template_service_call(self):
|
||||
def test_service_call(self):
|
||||
"""Test service call with templating."""
|
||||
config = {
|
||||
"service_template": "{{ 'test_domain.test_service' }}",
|
||||
"service": "{{ 'test_domain.test_service' }}",
|
||||
"entity_id": "hello.world",
|
||||
"data_template": {
|
||||
"data": {
|
||||
"hello": "{{ 'goodbye' }}",
|
||||
"data": {"value": "{{ 'complex' }}", "simple": "simple"},
|
||||
"list": ["{{ 'list' }}", "2"],
|
||||
},
|
||||
"data_template": {"list": ["{{ 'list' }}", "2"]},
|
||||
}
|
||||
|
||||
service.call_from_config(self.hass, config)
|
||||
@ -164,6 +164,19 @@ class TestServiceHelpers(unittest.TestCase):
|
||||
assert self.calls[0].data["data"]["simple"] == "simple"
|
||||
assert self.calls[0].data["list"][0] == "list"
|
||||
|
||||
def test_service_template_service_call(self):
|
||||
"""Test legacy service_template call with templating."""
|
||||
config = {
|
||||
"service_template": "{{ 'test_domain.test_service' }}",
|
||||
"entity_id": "hello.world",
|
||||
"data": {"hello": "goodbye"},
|
||||
}
|
||||
|
||||
service.call_from_config(self.hass, config)
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert self.calls[0].data["hello"] == "goodbye"
|
||||
|
||||
def test_passing_variables_to_templates(self):
|
||||
"""Test passing variables to templates."""
|
||||
config = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user