From 0ea03f549c85d23c02155a3fd0acbd9193826257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Wed, 21 Jan 2026 12:01:15 +0000 Subject: [PATCH] Support target conditions in script relation extraction (#161338) --- .../components/automation/__init__.py | 6 +- homeassistant/helpers/condition.py | 25 +--- homeassistant/helpers/script.py | 7 + tests/helpers/test_script.py | 131 +++++++++++++++--- 4 files changed, 125 insertions(+), 44 deletions(-) diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 53d51354d7c..d56ca509a74 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -604,7 +604,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): if self._cond_func is not None: for conf in self._cond_func.config: - referenced |= condition.async_extract_labels(conf) + referenced |= condition.async_extract_targets(conf, ATTR_LABEL_ID) for conf in self._trigger_config: referenced |= set(_get_targets_from_trigger_config(conf, ATTR_LABEL_ID)) @@ -617,7 +617,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): if self._cond_func is not None: for conf in self._cond_func.config: - referenced |= condition.async_extract_floors(conf) + referenced |= condition.async_extract_targets(conf, ATTR_FLOOR_ID) for conf in self._trigger_config: referenced |= set(_get_targets_from_trigger_config(conf, ATTR_FLOOR_ID)) @@ -630,7 +630,7 @@ class AutomationEntity(BaseAutomationEntity, RestoreEntity): if self._cond_func is not None: for conf in self._cond_func.config: - referenced |= condition.async_extract_areas(conf) + referenced |= condition.async_extract_targets(conf, ATTR_AREA_ID) for conf in self._trigger_config: referenced |= set(_get_targets_from_trigger_config(conf, ATTR_AREA_ID)) diff --git a/homeassistant/helpers/condition.py b/homeassistant/helpers/condition.py index a1e40da66a6..2c82756c0a4 100644 --- a/homeassistant/helpers/condition.py +++ b/homeassistant/helpers/condition.py @@ -29,10 +29,7 @@ from typing import ( import voluptuous as vol from homeassistant.const import ( - ATTR_AREA_ID, ATTR_DEVICE_CLASS, - ATTR_FLOOR_ID, - ATTR_LABEL_ID, CONF_ABOVE, CONF_AFTER, CONF_ATTRIBUTE, @@ -1387,27 +1384,9 @@ def async_extract_devices(config: ConfigType | Template) -> set[str]: @callback -def async_extract_areas(config: ConfigType | Template) -> set[str]: - """Extract areas from a condition.""" - return _async_extract_targets(config, ATTR_AREA_ID) - - -@callback -def async_extract_floors(config: ConfigType | Template) -> set[str]: - """Extract floors from a condition.""" - return _async_extract_targets(config, ATTR_FLOOR_ID) - - -@callback -def async_extract_labels(config: ConfigType | Template) -> set[str]: - """Extract labels from a condition.""" - return _async_extract_targets(config, ATTR_LABEL_ID) - - -@callback -def _async_extract_targets( +def async_extract_targets( config: ConfigType | Template, - target_type: Literal["entity_id", "device_id", "area_id", "floor_id", "label_id"], + target_type: Literal["area_id", "floor_id", "label_id"], ) -> set[str]: """Extract targets from a condition.""" referenced: set[str] = set() diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py index 3d7b99d571c..81d91ac8042 100644 --- a/homeassistant/helpers/script.py +++ b/homeassistant/helpers/script.py @@ -1601,8 +1601,13 @@ class Script: ): _referenced_extract_ids(data, target, referenced) + elif action == cv.SCRIPT_ACTION_CHECK_CONDITION: + referenced |= condition.async_extract_targets(step, target) + elif action == cv.SCRIPT_ACTION_CHOOSE: for choice in step[CONF_CHOOSE]: + for cond in choice[CONF_CONDITIONS]: + referenced |= condition.async_extract_targets(cond, target) Script._find_referenced_target( target, referenced, choice[CONF_SEQUENCE] ) @@ -1612,6 +1617,8 @@ class Script: ) elif action == cv.SCRIPT_ACTION_IF: + for cond in step[CONF_IF]: + referenced |= condition.async_extract_targets(cond, target) Script._find_referenced_target(target, referenced, step[CONF_THEN]) if CONF_ELSE in step: Script._find_referenced_target(target, referenced, step[CONF_ELSE]) diff --git a/tests/helpers/test_script.py b/tests/helpers/test_script.py index fd8dde96d99..d58a5e03f42 100644 --- a/tests/helpers/test_script.py +++ b/tests/helpers/test_script.py @@ -4209,6 +4209,16 @@ async def test_referenced_labels(hass: HomeAssistant) -> None: "data_template": {"label_id": "label_in_data_template"}, }, {"action": "test.script", "data": {"without": "label_id"}}, + { + "condition": "light.is_on", + "target": {"label_id": "label_condition_target"}, + }, + { + "condition": "light.is_on", + "target": { + "label_id": ["label_condition_list_1", "label_condition_list_2"] + }, + }, { "choose": [ { @@ -4221,7 +4231,10 @@ async def test_referenced_labels(hass: HomeAssistant) -> None: ], }, { - "conditions": "{{ true == false }}", + "conditions": { + "condition": "light.is_on", + "target": {"label_id": "label_choice_2_cond"}, + }, "sequence": [ { "action": "test.script", @@ -4240,7 +4253,10 @@ async def test_referenced_labels(hass: HomeAssistant) -> None: {"event": "test_event"}, {"delay": "{{ delay_period }}"}, { - "if": [], + "if": { + "condition": "light.is_on", + "target": {"label_id": "label_if_cond"}, + }, "then": [ { "action": "test.script", @@ -4277,17 +4293,22 @@ async def test_referenced_labels(hass: HomeAssistant) -> None: ) assert script_obj.referenced_labels == { "label_choice_1_seq", + "label_choice_2_cond", "label_choice_2_seq", + "label_condition_list_1", + "label_condition_list_2", + "label_condition_target", "label_default_seq", + "label_if_cond", + "label_if_else", + "label_if_then", "label_in_data_template", "label_in_target", + "label_parallel", + "label_sequence", "label_service_list_1", "label_service_list_2", "label_service_not_list", - "label_if_then", - "label_if_else", - "label_parallel", - "label_sequence", } # Test we cache results. assert script_obj.referenced_labels is script_obj.referenced_labels @@ -4320,6 +4341,16 @@ async def test_referenced_floors(hass: HomeAssistant) -> None: "data_template": {"floor_id": "floor_in_data_template"}, }, {"action": "test.script", "data": {"without": "floor_id"}}, + { + "condition": "light.is_on", + "target": {"floor_id": "floor_condition_target"}, + }, + { + "condition": "light.is_on", + "target": { + "floor_id": ["floor_condition_list_1", "floor_condition_list_2"] + }, + }, { "choose": [ { @@ -4332,7 +4363,10 @@ async def test_referenced_floors(hass: HomeAssistant) -> None: ], }, { - "conditions": "{{ true == false }}", + "conditions": { + "condition": "light.is_on", + "target": {"floor_id": "floor_choice_2_cond"}, + }, "sequence": [ { "action": "test.script", @@ -4351,7 +4385,10 @@ async def test_referenced_floors(hass: HomeAssistant) -> None: {"event": "test_event"}, {"delay": "{{ delay_period }}"}, { - "if": [], + "if": { + "condition": "light.is_on", + "target": {"floor_id": "floor_if_cond"}, + }, "then": [ { "action": "test.script", @@ -4388,16 +4425,21 @@ async def test_referenced_floors(hass: HomeAssistant) -> None: ) assert script_obj.referenced_floors == { "floor_choice_1_seq", + "floor_choice_2_cond", "floor_choice_2_seq", + "floor_condition_list_1", + "floor_condition_list_2", + "floor_condition_target", "floor_default_seq", + "floor_if_cond", + "floor_if_else", + "floor_if_then", "floor_in_data_template", "floor_in_target", - "floor_service_list", - "floor_service_not_list", - "floor_if_then", - "floor_if_else", "floor_parallel", "floor_sequence", + "floor_service_list", + "floor_service_not_list", } # Test we cache results. assert script_obj.referenced_floors is script_obj.referenced_floors @@ -4430,6 +4472,16 @@ async def test_referenced_areas(hass: HomeAssistant) -> None: "data_template": {"area_id": "area_in_data_template"}, }, {"action": "test.script", "data": {"without": "area_id"}}, + { + "condition": "light.is_on", + "target": {"area_id": "area_condition_target"}, + }, + { + "condition": "light.is_on", + "target": { + "area_id": ["area_condition_list_1", "area_condition_list_2"] + }, + }, { "choose": [ { @@ -4442,7 +4494,10 @@ async def test_referenced_areas(hass: HomeAssistant) -> None: ], }, { - "conditions": "{{ true == false }}", + "conditions": { + "condition": "light.is_on", + "target": {"area_id": "area_choice_2_cond"}, + }, "sequence": [ { "action": "test.script", @@ -4461,7 +4516,10 @@ async def test_referenced_areas(hass: HomeAssistant) -> None: {"event": "test_event"}, {"delay": "{{ delay_period }}"}, { - "if": [], + "if": { + "condition": "light.is_on", + "target": {"area_id": "area_if_cond"}, + }, "then": [ { "action": "test.script", @@ -4498,16 +4556,21 @@ async def test_referenced_areas(hass: HomeAssistant) -> None: ) assert script_obj.referenced_areas == { "area_choice_1_seq", + "area_choice_2_cond", "area_choice_2_seq", + "area_condition_list_1", + "area_condition_list_2", + "area_condition_target", "area_default_seq", + "area_if_cond", + "area_if_else", + "area_if_then", "area_in_data_template", "area_in_target", - "area_service_list", - "area_service_not_list", - "area_if_then", - "area_if_else", "area_parallel", "area_sequence", + "area_service_list", + "area_service_not_list", # 'area_service_template', # no area extraction from template } # Test we cache results. @@ -4608,6 +4671,19 @@ async def test_referenced_entities(hass: HomeAssistant) -> None: } ], }, + { + "condition": "light.is_on", + "target": {"entity_id": "light.condition_target"}, + }, + { + "condition": "light.is_on", + "target": { + "entity_id": [ + "light.condition_list_1", + "light.condition_list_2", + ] + }, + }, { "sequence": [ { @@ -4626,6 +4702,9 @@ async def test_referenced_entities(hass: HomeAssistant) -> None: "light.choice_1_seq", "light.choice_2_cond", "light.choice_2_seq", + "light.condition_list_1", + "light.condition_list_2", + "light.condition_target", "light.default_seq", "light.direct_entity_referenced", "light.entity_in_data_template", @@ -4656,6 +4735,19 @@ async def test_referenced_devices(hass: HomeAssistant) -> None: "device_id": "condition-dev-id", "domain": "switch", }, + { + "condition": "light.is_on", + "target": {"device_id": "condition-target-dev-id"}, + }, + { + "condition": "light.is_on", + "target": { + "device_id": [ + "condition-target-list-1", + "condition-target-list-2", + ] + }, + }, { "action": "test.script", "data": {"device_id": "data-string-id"}, @@ -4753,6 +4845,9 @@ async def test_referenced_devices(hass: HomeAssistant) -> None: "choice-2-cond-dev-id", "choice-2-seq-device-target", "condition-dev-id", + "condition-target-dev-id", + "condition-target-list-1", + "condition-target-list-2", "data-string-id", "data-template-string-id", "default-device-target",