Add label template functions (#111024)
parent
e136847b89
commit
5c69e0d2c6
|
@ -85,6 +85,7 @@ from . import (
|
|||
entity_registry,
|
||||
floor_registry as fr,
|
||||
issue_registry,
|
||||
label_registry,
|
||||
location as loc_helper,
|
||||
)
|
||||
from .singleton import singleton
|
||||
|
@ -1560,6 +1561,92 @@ def area_devices(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
|
|||
return [entry.id for entry in entries]
|
||||
|
||||
|
||||
def labels(hass: HomeAssistant, lookup_value: Any = None) -> Iterable[str | None]:
|
||||
"""Return all labels, or those from a area ID, device ID, or entity ID."""
|
||||
label_reg = label_registry.async_get(hass)
|
||||
if lookup_value is None:
|
||||
return [label.label_id for label in label_reg.async_list_labels()]
|
||||
|
||||
ent_reg = entity_registry.async_get(hass)
|
||||
|
||||
# Import here, not at top-level to avoid circular import
|
||||
from . import config_validation as cv # pylint: disable=import-outside-toplevel
|
||||
|
||||
lookup_value = str(lookup_value)
|
||||
|
||||
try:
|
||||
cv.entity_id(lookup_value)
|
||||
except vol.Invalid:
|
||||
pass
|
||||
else:
|
||||
if entity := ent_reg.async_get(lookup_value):
|
||||
return list(entity.labels)
|
||||
|
||||
# Check if this could be a device ID
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
if device := dev_reg.async_get(lookup_value):
|
||||
return list(device.labels)
|
||||
|
||||
# Check if this could be a area ID
|
||||
area_reg = area_registry.async_get(hass)
|
||||
if area := area_reg.async_get_area(lookup_value):
|
||||
return list(area.labels)
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def label_id(hass: HomeAssistant, lookup_value: Any) -> str | None:
|
||||
"""Get the label ID from a label name."""
|
||||
label_reg = label_registry.async_get(hass)
|
||||
if label := label_reg.async_get_label_by_name(str(lookup_value)):
|
||||
return label.label_id
|
||||
return None
|
||||
|
||||
|
||||
def label_name(hass: HomeAssistant, lookup_value: str) -> str | None:
|
||||
"""Get the label name from a label ID."""
|
||||
label_reg = label_registry.async_get(hass)
|
||||
if label := label_reg.async_get_label(lookup_value):
|
||||
return label.name
|
||||
return None
|
||||
|
||||
|
||||
def _label_id_or_name(hass: HomeAssistant, label_id_or_name: str) -> str | None:
|
||||
"""Get the label ID from a label name or ID."""
|
||||
# If label_name returns a value, we know the input was an ID, otherwise we
|
||||
# assume it's a name, and if it's neither, we return early.
|
||||
if label_name(hass, label_id_or_name) is not None:
|
||||
return label_id_or_name
|
||||
return label_id(hass, label_id_or_name)
|
||||
|
||||
|
||||
def label_areas(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
|
||||
"""Return areas for a given label ID or name."""
|
||||
if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
|
||||
return []
|
||||
area_reg = area_registry.async_get(hass)
|
||||
entries = area_registry.async_entries_for_label(area_reg, _label_id)
|
||||
return [entry.id for entry in entries]
|
||||
|
||||
|
||||
def label_devices(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
|
||||
"""Return device IDs for a given label ID or name."""
|
||||
if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
|
||||
return []
|
||||
dev_reg = device_registry.async_get(hass)
|
||||
entries = device_registry.async_entries_for_label(dev_reg, _label_id)
|
||||
return [entry.id for entry in entries]
|
||||
|
||||
|
||||
def label_entities(hass: HomeAssistant, label_id_or_name: str) -> Iterable[str]:
|
||||
"""Return entities for a given label ID or name."""
|
||||
if (_label_id := _label_id_or_name(hass, label_id_or_name)) is None:
|
||||
return []
|
||||
ent_reg = entity_registry.async_get(hass)
|
||||
entries = entity_registry.async_entries_for_label(ent_reg, _label_id)
|
||||
return [entry.entity_id for entry in entries]
|
||||
|
||||
|
||||
def closest(hass, *args):
|
||||
"""Find closest entity.
|
||||
|
||||
|
@ -2731,6 +2818,24 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
|||
self.globals["integration_entities"] = hassfunction(integration_entities)
|
||||
self.filters["integration_entities"] = self.globals["integration_entities"]
|
||||
|
||||
self.globals["labels"] = hassfunction(labels)
|
||||
self.filters["labels"] = self.globals["labels"]
|
||||
|
||||
self.globals["label_id"] = hassfunction(label_id)
|
||||
self.filters["label_id"] = self.globals["label_id"]
|
||||
|
||||
self.globals["label_name"] = hassfunction(label_name)
|
||||
self.filters["label_name"] = self.globals["label_name"]
|
||||
|
||||
self.globals["label_areas"] = hassfunction(label_areas)
|
||||
self.filters["label_areas"] = self.globals["label_areas"]
|
||||
|
||||
self.globals["label_devices"] = hassfunction(label_devices)
|
||||
self.filters["label_devices"] = self.globals["label_devices"]
|
||||
|
||||
self.globals["label_entities"] = hassfunction(label_entities)
|
||||
self.filters["label_entities"] = self.globals["label_entities"]
|
||||
|
||||
if limited:
|
||||
# Only device_entities is available to limited templates, mark other
|
||||
# functions and filters as unsupported.
|
||||
|
@ -2764,6 +2869,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
|||
"floor_name",
|
||||
"relative_time",
|
||||
"today_at",
|
||||
"label_id",
|
||||
"label_name",
|
||||
]
|
||||
hass_filters = [
|
||||
"closest",
|
||||
|
@ -2774,6 +2881,8 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
|
|||
"floor_id",
|
||||
"floor_name",
|
||||
"has_value",
|
||||
"label_id",
|
||||
"label_name",
|
||||
]
|
||||
hass_tests = [
|
||||
"has_value",
|
||||
|
|
|
@ -40,6 +40,7 @@ from homeassistant.helpers import (
|
|||
entity_registry as er,
|
||||
floor_registry as fr,
|
||||
issue_registry as ir,
|
||||
label_registry as lr,
|
||||
template,
|
||||
translation,
|
||||
)
|
||||
|
@ -5309,3 +5310,363 @@ async def test_floor_areas(
|
|||
info = render_to_info(hass, f"{{{{ '{floor.name}' | floor_areas }}}}")
|
||||
assert_result_info(info, [area.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_labels(
|
||||
hass: HomeAssistant,
|
||||
label_registry: lr.LabelRegistry,
|
||||
area_registry: ar.AreaRegistry,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
entity_registry: er.EntityRegistry,
|
||||
) -> None:
|
||||
"""Test labels function."""
|
||||
|
||||
# Test no labels
|
||||
info = render_to_info(hass, "{{ labels() }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test one label
|
||||
label1 = label_registry.async_create("label1")
|
||||
info = render_to_info(hass, "{{ labels() }}")
|
||||
assert_result_info(info, [label1.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test multiple label
|
||||
label2 = label_registry.async_create("label2")
|
||||
info = render_to_info(hass, "{{ labels() }}")
|
||||
assert_result_info(info, [label1.label_id, label2.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non-exsting entity ID
|
||||
info = render_to_info(hass, "{{ labels('sensor.fake') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'sensor.fake' | labels }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing device ID (hex value)
|
||||
info = render_to_info(hass, "{{ labels('123abc') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ '123abc' | labels }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Create a device & entity for testing
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
device_id=device_entry.id,
|
||||
)
|
||||
|
||||
# Test entity, which has no labels
|
||||
info = render_to_info(hass, f"{{{{ labels('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{entity_entry.entity_id}' | labels }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device, which has no labels
|
||||
info = render_to_info(hass, f"{{{{ labels('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | labels }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Add labels to the entity & device
|
||||
device_entry = device_registry.async_update_device(
|
||||
device_entry.id, labels=[label1.label_id]
|
||||
)
|
||||
entity_entry = entity_registry.async_update_entity(
|
||||
entity_entry.entity_id, labels=[label2.label_id]
|
||||
)
|
||||
|
||||
# Test entity, which now has a label
|
||||
info = render_to_info(hass, f"{{{{ '{entity_entry.entity_id}' | labels }}}}")
|
||||
assert_result_info(info, [label2.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ labels('{entity_entry.entity_id}') }}}}")
|
||||
assert_result_info(info, [label2.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test device, which now has a label
|
||||
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | labels }}}}")
|
||||
assert_result_info(info, [label1.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ labels('{device_entry.id}') }}}}")
|
||||
assert_result_info(info, [label1.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Create area for testing
|
||||
area = area_registry.async_create("living room")
|
||||
|
||||
# Test area, which has no labels
|
||||
info = render_to_info(hass, f"{{{{ '{area.id}' | labels }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ labels('{area.id}') }}}}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Add label to the area
|
||||
area_registry.async_update(area.id, labels=[label1.label_id, label2.label_id])
|
||||
|
||||
# Test area, which now has labels
|
||||
info = render_to_info(hass, f"{{{{ '{area.id}' | labels }}}}")
|
||||
assert_result_info(info, [label1.label_id, label2.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ labels('{area.id}') }}}}")
|
||||
assert_result_info(info, [label1.label_id, label2.label_id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_label_id(
|
||||
hass: HomeAssistant,
|
||||
label_registry: lr.LabelRegistry,
|
||||
) -> None:
|
||||
"""Test label_id function."""
|
||||
# Test non existing label name
|
||||
info = render_to_info(hass, "{{ label_id('non-existing label') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'non-existing label' | label_id }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ label_id(42) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 42 | label_id }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test with an actual label
|
||||
label = label_registry.async_create("existing label")
|
||||
info = render_to_info(hass, "{{ label_id('existing label') }}")
|
||||
assert_result_info(info, label.label_id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'existing label' | label_id }}")
|
||||
assert_result_info(info, label.label_id)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_label_name(
|
||||
hass: HomeAssistant,
|
||||
label_registry: lr.LabelRegistry,
|
||||
) -> None:
|
||||
"""Test label_name function."""
|
||||
# Test non existing label ID
|
||||
info = render_to_info(hass, "{{ label_name('1234567890') }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ '1234567890' | label_name }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ label_name(42) }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 42 | label_name }}")
|
||||
assert_result_info(info, None)
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test non existing label ID
|
||||
label = label_registry.async_create("choo choo")
|
||||
info = render_to_info(hass, f"{{{{ label_name('{label.label_id}') }}}}")
|
||||
assert_result_info(info, label.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_name }}}}")
|
||||
assert_result_info(info, label.name)
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_label_entities(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
label_registry: lr.LabelRegistry,
|
||||
) -> None:
|
||||
"""Test label_entities function."""
|
||||
|
||||
# Test non existing device ID
|
||||
info = render_to_info(hass, "{{ label_entities('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'deadbeef' | label_entities }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ label_entities(42) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 42 | label_entities }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Create a fake config entry with a entity
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
entity_entry = entity_registry.async_get_or_create(
|
||||
"light",
|
||||
"hue",
|
||||
"5678",
|
||||
config_entry=config_entry,
|
||||
)
|
||||
|
||||
# Add a label to the entity
|
||||
label = label_registry.async_create("Romantic Lights")
|
||||
entity_registry.async_update_entity(entity_entry.entity_id, labels={label.label_id})
|
||||
|
||||
# Get entities by label ID
|
||||
info = render_to_info(hass, f"{{{{ label_entities('{label.label_id}') }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Get entities by label name
|
||||
info = render_to_info(hass, f"{{{{ label_entities('{label.name}') }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.name}' | label_entities }}}}")
|
||||
assert_result_info(info, ["light.hue_5678"])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_label_devices(
|
||||
hass: HomeAssistant,
|
||||
device_registry: dr.DeviceRegistry,
|
||||
label_registry: ar.AreaRegistry,
|
||||
) -> None:
|
||||
"""Test label_devices function."""
|
||||
|
||||
# Test non existing device ID
|
||||
info = render_to_info(hass, "{{ label_devices('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'deadbeef' | label_devices }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ label_devices(42) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 42 | label_devices }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Create a fake config entry with a device
|
||||
config_entry = MockConfigEntry(domain="light")
|
||||
config_entry.add_to_hass(hass)
|
||||
device_entry = device_registry.async_get_or_create(
|
||||
config_entry_id=config_entry.entry_id,
|
||||
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
|
||||
)
|
||||
|
||||
# Add a label to it
|
||||
label = label_registry.async_create("Romantic Lights")
|
||||
device_registry.async_update_device(device_entry.id, labels=[label.label_id])
|
||||
|
||||
# Get the devices from a label by its ID
|
||||
info = render_to_info(hass, f"{{{{ label_devices('{label.label_id}') }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_devices }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Get the devices from a label by its name
|
||||
info = render_to_info(hass, f"{{{{ label_devices('{label.name}') }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.name}' | label_devices }}}}")
|
||||
assert_result_info(info, [device_entry.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
|
||||
async def test_label_areas(
|
||||
hass: HomeAssistant,
|
||||
area_registry: ar.AreaRegistry,
|
||||
label_registry: lr.LabelRegistry,
|
||||
) -> None:
|
||||
"""Test label_areas function."""
|
||||
|
||||
# Test non existing area ID
|
||||
info = render_to_info(hass, "{{ label_areas('deadbeef') }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 'deadbeef' | label_areas }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Test wrong value type
|
||||
info = render_to_info(hass, "{{ label_areas(42) }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, "{{ 42 | label_areas }}")
|
||||
assert_result_info(info, [])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Create an area with an label
|
||||
label = label_registry.async_create("Upstairs")
|
||||
master_bedroom = area_registry.async_create(
|
||||
"Master Bedroom", labels=[label.label_id]
|
||||
)
|
||||
|
||||
# Get areas by label ID
|
||||
info = render_to_info(hass, f"{{{{ label_areas('{label.label_id}') }}}}")
|
||||
assert_result_info(info, [master_bedroom.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.label_id}' | label_areas }}}}")
|
||||
assert_result_info(info, [master_bedroom.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
# Get areas by label name
|
||||
info = render_to_info(hass, f"{{{{ label_areas('{label.name}') }}}}")
|
||||
assert_result_info(info, [master_bedroom.id])
|
||||
assert info.rate_limit is None
|
||||
|
||||
info = render_to_info(hass, f"{{{{ '{label.name}' | label_areas }}}}")
|
||||
assert_result_info(info, [master_bedroom.id])
|
||||
assert info.rate_limit is None
|
||||
|
|
Loading…
Reference in New Issue