diff --git a/homeassistant/const.py b/homeassistant/const.py index 91fa4d628de..801f456d59c 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -227,6 +227,7 @@ CONF_SENSOR_TYPE: Final = "sensor_type" CONF_SEQUENCE: Final = "sequence" CONF_SERVICE: Final = "service" CONF_SERVICE_DATA: Final = "data" +CONF_SERVICE_DATA_TEMPLATE: Final = "data_template" CONF_SERVICE_TEMPLATE: Final = "service_template" CONF_SHOW_ON_MAP: Final = "show_on_map" CONF_SLAVE: Final = "slave" diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index 4c2fed60bb4..f6e77ef0018 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -63,6 +63,8 @@ from homeassistant.const import ( CONF_SCENE, CONF_SEQUENCE, CONF_SERVICE, + CONF_SERVICE_DATA, + CONF_SERVICE_DATA_TEMPLATE, CONF_SERVICE_TEMPLATE, CONF_STATE, CONF_STOP, @@ -1119,8 +1121,10 @@ SERVICE_SCHEMA = vol.All( vol.Exclusive(CONF_SERVICE_TEMPLATE, "service name"): vol.Any( service, dynamic_template ), - vol.Optional("data"): vol.Any(template, vol.All(dict, template_complex)), - vol.Optional("data_template"): vol.Any( + vol.Optional(CONF_SERVICE_DATA): vol.Any( + template, vol.All(dict, template_complex) + ), + vol.Optional(CONF_SERVICE_DATA_TEMPLATE): vol.Any( template, vol.All(dict, template_complex) ), vol.Optional(CONF_ENTITY_ID): comp_entity_ids, diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index e472934fc76..5fc0fdc4706 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -50,6 +50,7 @@ from homeassistant.const import ( CONF_SEQUENCE, CONF_SERVICE, CONF_SERVICE_DATA, + CONF_SERVICE_DATA_TEMPLATE, CONF_STOP, CONF_TARGET, CONF_THEN, @@ -1112,11 +1113,10 @@ async def _async_stop_scripts_at_shutdown(hass, event): _VarsType = Union[dict[str, Any], MappingProxyType] -def _referenced_extract_ids( - data: dict[str, Any] | None, key: str, found: set[str] -) -> None: +def _referenced_extract_ids(data: Any, key: str, found: set[str]) -> None: """Extract referenced IDs.""" - if not data: + # Data may not exist, or be a template + if not isinstance(data, dict): return item_ids = data.get(key) @@ -1300,7 +1300,7 @@ class Script: for data in ( step.get(CONF_TARGET), step.get(CONF_SERVICE_DATA), - step.get(service.CONF_SERVICE_DATA_TEMPLATE), + step.get(CONF_SERVICE_DATA_TEMPLATE), ): _referenced_extract_ids(data, ATTR_AREA_ID, referenced) @@ -1340,7 +1340,7 @@ class Script: for data in ( step.get(CONF_TARGET), step.get(CONF_SERVICE_DATA), - step.get(service.CONF_SERVICE_DATA_TEMPLATE), + step.get(CONF_SERVICE_DATA_TEMPLATE), ): _referenced_extract_ids(data, ATTR_DEVICE_ID, referenced) @@ -1391,7 +1391,7 @@ class Script: step, step.get(CONF_TARGET), step.get(CONF_SERVICE_DATA), - step.get(service.CONF_SERVICE_DATA_TEMPLATE), + step.get(CONF_SERVICE_DATA_TEMPLATE), ): _referenced_extract_ids(data, ATTR_ENTITY_ID, referenced) diff --git a/homeassistant/helpers/service.py b/homeassistant/helpers/service.py index 7675686844c..138fa739794 100644 --- a/homeassistant/helpers/service.py +++ b/homeassistant/helpers/service.py @@ -19,6 +19,7 @@ from homeassistant.const import ( CONF_ENTITY_ID, CONF_SERVICE, CONF_SERVICE_DATA, + CONF_SERVICE_DATA_TEMPLATE, CONF_SERVICE_TEMPLATE, CONF_TARGET, ENTITY_MATCH_ALL, @@ -52,7 +53,6 @@ if TYPE_CHECKING: CONF_SERVICE_ENTITY_ID = "entity_id" -CONF_SERVICE_DATA_TEMPLATE = "data_template" _LOGGER = logging.getLogger(__name__) diff --git a/tests/components/search/test_init.py b/tests/components/search/test_init.py index a728ef9b8c4..cc04680d8a4 100644 --- a/tests/components/search/test_init.py +++ b/tests/components/search/test_init.py @@ -183,6 +183,22 @@ async def test_search(hass): }, ] }, + "script_with_templated_services": { + "sequence": [ + { + "service": "test.script", + "target": "{{ {'entity_id':'test.test1'} }}", + }, + { + "service": "test.script", + "data": "{{ {'entity_id':'test.test2'} }}", + }, + { + "service": "test.script", + "data_template": "{{ {'entity_id':'test.test3'} }}", + }, + ] + }, } }, ) @@ -304,6 +320,18 @@ async def test_search(hass): searcher = search.Searcher(hass, device_reg, entity_reg, entity_sources) assert searcher.async_search(search_type, search_id) == {} + # Test search of templated script. We can't find referenced areas, devices or + # entities within templated services, but searching them should not raise or + # otherwise fail. + assert hass.states.get("script.script_with_templated_services") + for search_type, search_id in ( + ("area", "script.script_with_templated_services"), + ("device", "script.script_with_templated_services"), + ("entity", "script.script_with_templated_services"), + ): + searcher = search.Searcher(hass, device_reg, entity_reg, entity_sources) + assert searcher.async_search(search_type, search_id) == {} + searcher = search.Searcher(hass, device_reg, entity_reg, entity_sources) assert searcher.async_search("entity", "light.wled_config_entry_source") == { "config_entry": {wled_config_entry.entry_id},