diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py index 099e113cf935b6..3d1a7cda8b0359 100644 --- a/homeassistant/components/websocket_api/commands.py +++ b/homeassistant/components/websocket_api/commands.py @@ -260,11 +260,6 @@ async def handle_call_service( hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any] ) -> None: """Handle call service command.""" - # We do not support templates. - target = msg.get("target") - if template.is_complex(target): - raise vol.Invalid("Templates are not supported here") - try: context = connection.context(msg) response = await hass.services.async_call( @@ -273,7 +268,7 @@ async def handle_call_service( service_data=msg.get("service_data"), blocking=True, context=context, - target=target, + target=msg.get("target"), return_response=msg["return_response"], ) result: dict[str, Context | ServiceResponse] = {"context": context} diff --git a/homeassistant/helpers/config_validation.py b/homeassistant/helpers/config_validation.py index cc46327c4c165e..6a922835dcbeb2 100644 --- a/homeassistant/helpers/config_validation.py +++ b/homeassistant/helpers/config_validation.py @@ -732,7 +732,7 @@ def temperature_unit(value: Any) -> UnitOfTemperature: raise vol.Invalid("invalid temperature unit (expected C or F)") -def template(value: Any | None) -> template_helper.Template: +def template(value: Any) -> template_helper.Template: """Validate a jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -750,7 +750,7 @@ def template(value: Any | None) -> template_helper.Template: return template_value -def dynamic_template(value: Any | None) -> template_helper.Template: +def dynamic_template(value: Any) -> template_helper.Template: """Validate a dynamic (non static) jinja2 template.""" if value is None: raise vol.Invalid("template value is None") @@ -1319,34 +1319,44 @@ def platform_only_config_schema(domain: str) -> Callable[[dict], dict]: } ENTITY_SERVICE_FIELDS: VolDictType = { - # Either accept static entity IDs, a single dynamic template or a mixed list - # of static and dynamic templates. While this could be solved with a single - # complex template, handling it like this, keeps config validation useful. - vol.Optional(ATTR_ENTITY_ID): vol.Any( - comp_entity_ids, dynamic_template, vol.All(list, template_complex) - ), + vol.Optional(ATTR_ENTITY_ID): comp_entity_ids, vol.Optional(ATTR_DEVICE_ID): vol.Any( - ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ENTITY_MATCH_NONE, + vol.All(ensure_list, [str]), ), vol.Optional(ATTR_AREA_ID): vol.Any( - ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ENTITY_MATCH_NONE, + vol.All(ensure_list, [str]), ), vol.Optional(ATTR_FLOOR_ID): vol.Any( - ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ENTITY_MATCH_NONE, + vol.All(ensure_list, [str]), ), vol.Optional(ATTR_LABEL_ID): vol.Any( - ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) + ENTITY_MATCH_NONE, + vol.All(ensure_list, [str]), ), } -TARGET_SERVICE_FIELDS = { - # Same as ENTITY_SERVICE_FIELDS but supports specifying entity by entity registry - # ID. +TARGET_SERVICE_FIELDS: VolDictType = { + # Same as ENTITY_SERVICE_FIELDS but supports specifying entity + # by entity registry ID. + **ENTITY_SERVICE_FIELDS, + vol.Optional(ATTR_ENTITY_ID): comp_entity_ids_or_uuids, +} + +_TARGET_SERVICE_FIELDS_TEMPLATED: VolDictType = { # Either accept static entity IDs, a single dynamic template or a mixed list # of static and dynamic templates. While this could be solved with a single # complex template, handling it like this, keeps config validation useful. + # Entity ID can be specified as either a user visible one or by entity registry ID. + # + # The schema supports templates as it is meant to be used in the initial validation + # before templates are automatically rendered by the core logic. vol.Optional(ATTR_ENTITY_ID): vol.Any( - comp_entity_ids_or_uuids, dynamic_template, vol.All(list, template_complex) + comp_entity_ids_or_uuids, + dynamic_template, + vol.All(list, template_complex), ), vol.Optional(ATTR_DEVICE_ID): vol.Any( ENTITY_MATCH_NONE, vol.All(ensure_list, [vol.Any(dynamic_template, str)]) @@ -1362,7 +1372,6 @@ def platform_only_config_schema(domain: str) -> Callable[[dict], dict]: ), } - _HAS_ENTITY_SERVICE_FIELD = has_at_least_one_key(*ENTITY_SERVICE_FIELDS) @@ -1494,7 +1503,9 @@ def _backward_compat_service_schema(value: Any | None) -> Any: template, vol.All(dict, template_complex) ), vol.Optional(CONF_ENTITY_ID): comp_entity_ids, - vol.Optional(CONF_TARGET): vol.Any(TARGET_SERVICE_FIELDS, dynamic_template), + vol.Optional(CONF_TARGET): vol.Any( + _TARGET_SERVICE_FIELDS_TEMPLATED, dynamic_template + ), vol.Optional(CONF_RESPONSE_VARIABLE): str, # The frontend stores data here. Don't use in core. vol.Remove("metadata"): dict, diff --git a/homeassistant/helpers/selector.py b/homeassistant/helpers/selector.py index 649fcb90fa879f..215a15b351271e 100644 --- a/homeassistant/helpers/selector.py +++ b/homeassistant/helpers/selector.py @@ -1463,7 +1463,8 @@ class TargetSelector(Selector[TargetSelectorConfig]): } ) - TARGET_SELECTION_SCHEMA = vol.Schema(cv.TARGET_SERVICE_FIELDS) + # We want to transition to not including templates in the target selector. + TARGET_SELECTION_SCHEMA = vol.Schema(cv._TARGET_SERVICE_FIELDS_TEMPLATED) # noqa: SLF001 def __init__(self, config: TargetSelectorConfig | None = None) -> None: """Instantiate a selector."""