Include child devices when targeting a labelled parent device (#182664)

This commit is contained in:
Artur Pragacz
2026-09-22 09:05:49 +02:00
committed by GitHub
parent cb472b5cf0
commit 0c072ae625
2 changed files with 31 additions and 30 deletions
+16 -22
View File
@@ -155,6 +155,18 @@ class SelectedEntities:
)
@callback
def _add_referenced_device(
dev_reg: dr.DeviceRegistry, device_id: str, selected: SelectedEntities
) -> None:
"""Add a device and its child devices."""
selected.referenced_devices.add(device_id)
selected.referenced_devices.update(
child_device.id
for child_device in dr.async_entries_for_parent_device(dev_reg, device_id)
)
@callback
def _resolve_referenced_devices(
dev_reg: dr.DeviceRegistry, device_ids: set[str], selected: SelectedEntities
@@ -165,8 +177,6 @@ def _resolve_referenced_devices(
if device is None:
selected.missing_devices.add(device_id)
selected.referenced_devices.add(device_id)
elif isinstance(device, dr.ChildDeviceEntry):
selected.referenced_devices.add(device_id)
elif split_devices := dev_reg.async_get_devices_for_composite_device_id(
device_id
):
@@ -174,23 +184,10 @@ def _resolve_referenced_devices(
# it resolves to the devices it was split into so actions targeting it
# still trickle down. Only the splits are referenced, not the composite id,
# so a device-id consumer does not act on the same underlying device twice.
# Each split's children are included too, matching the direct-device branch.
for split_device in split_devices:
selected.referenced_devices.add(split_device.id)
selected.referenced_devices.update(
child_device.id
for child_device in dr.async_entries_for_parent_device(
dev_reg, split_device.id
)
)
_add_referenced_device(dev_reg, split_device.id, selected)
else:
selected.referenced_devices.add(device_id)
selected.referenced_devices.update(
child_device.id
for child_device in dr.async_entries_for_parent_device(
dev_reg, device_id
)
)
_add_referenced_device(dev_reg, device_id, selected)
def async_extract_referenced_entity_ids(
@@ -255,12 +252,9 @@ def async_extract_referenced_entity_ids(
if entity_entry.hidden_by is None:
selected.indirectly_referenced.add(entity_entry.entity_id)
# Labels are never inherited by child devices (see
# dr.async_entries_for_label): a labeled parent is not expanded into its
# children. Only devices that carry the label themselves are targeted,
# which is consistent with template label_devices() and search.
# Labeled devices expand like directly targeted devices, children included.
for device_entry in dr.async_entries_for_label(dev_reg, label_id):
selected.referenced_devices.add(device_entry.id)
_add_referenced_device(dev_reg, device_entry.id, selected)
for area_entry in area_reg.areas.get_areas_for_label(label_id):
selected.referenced_areas.add(area_entry.id)
+15 -8
View File
@@ -1262,21 +1262,28 @@ async def test_extract_label_with_child_devices(
hass: HomeAssistant,
child_device_setup: dict[str, str],
) -> None:
"""Test labels are not inherited by child devices when targeting.
"""Test a labeled parent device expands to its child devices when targeting.
A labeled parent targets only the parent and its own entities; a labeled child
targets only that child. This mirrors dr.async_entries_for_label, which
documents that labels are never inherited from the parent.
Labels are not inherited by child devices, but a labeled parent is targeted like
a directly targeted device, so its children are included. A labeled child
targets only that child.
"""
ids = child_device_setup
# The label is on the parent; it does not expand to the child devices, and only
# the parent's own entities are indirectly referenced.
selected = target.async_extract_referenced_entity_ids(
hass, target.TargetSelection({"label_id": "strip-label"})
)
assert selected.referenced_devices == {ids["parent"]}
assert selected.indirectly_referenced == {ids["strip_switch"]}
assert selected.referenced_devices == {
ids["parent"],
ids["outlet_1"],
ids["outlet_2"],
}
assert selected.indirectly_referenced == {
ids["strip_switch"],
ids["outlet_1_switch"],
ids["outlet_2_switch"],
ids["outlet_1_energy"],
}
# A label on a child device targets only the child device
selected = target.async_extract_referenced_entity_ids(