1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Extract area template functions into an areas Jinja2 extension (#156629)

This commit is contained in:
Franck Nijhof
2025-11-21 17:56:16 +01:00
committed by GitHub
parent 4d4ad900b1
commit e5b2d44e8e
5 changed files with 476 additions and 418 deletions
+2 -116
View File
@@ -56,8 +56,6 @@ from homeassistant.core import (
)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_registry as er,
issue_registry as ir,
location as loc_helper,
@@ -78,7 +76,7 @@ from .context import (
template_context_manager,
template_cv,
)
from .helpers import raise_no_default, resolve_area_id
from .helpers import raise_no_default
from .render_info import RenderInfo, render_info_cv
if TYPE_CHECKING:
@@ -1244,103 +1242,6 @@ def issue(hass: HomeAssistant, domain: str, issue_id: str) -> dict[str, Any] | N
return None
def areas(hass: HomeAssistant) -> Iterable[str | None]:
"""Return all areas."""
return list(ar.async_get(hass).areas)
def area_id(hass: HomeAssistant, lookup_value: str) -> str | None:
"""Get the area ID from an area name, alias, device id, or entity id."""
return resolve_area_id(hass, lookup_value)
def _get_area_name(area_reg: ar.AreaRegistry, valid_area_id: str) -> str:
"""Get area name from valid area ID."""
area = area_reg.async_get_area(valid_area_id)
assert area
return area.name
def area_name(hass: HomeAssistant, lookup_value: str) -> str | None:
"""Get the area name from an area id, device id, or entity id."""
area_reg = ar.async_get(hass)
if area := area_reg.async_get_area(lookup_value):
return area.name
dev_reg = dr.async_get(hass)
ent_reg = er.async_get(hass)
# Import here, not at top-level to avoid circular import
from homeassistant.helpers import config_validation as cv # noqa: PLC0415
try:
cv.entity_id(lookup_value)
except vol.Invalid:
pass
else:
if entity := ent_reg.async_get(lookup_value):
# If entity has an area ID, get the area name for that
if entity.area_id:
return _get_area_name(area_reg, entity.area_id)
# If entity has a device ID and the device exists with an area ID, get the
# area name for that
if (
entity.device_id
and (device := dev_reg.async_get(entity.device_id))
and device.area_id
):
return _get_area_name(area_reg, device.area_id)
if (device := dev_reg.async_get(lookup_value)) and device.area_id:
return _get_area_name(area_reg, device.area_id)
return None
def area_entities(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
"""Return entities for a given area ID or name."""
_area_id: str | None
# if area_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 area_name(hass, area_id_or_name) is None:
_area_id = area_id(hass, area_id_or_name)
else:
_area_id = area_id_or_name
if _area_id is None:
return []
ent_reg = er.async_get(hass)
entity_ids = [
entry.entity_id for entry in er.async_entries_for_area(ent_reg, _area_id)
]
dev_reg = dr.async_get(hass)
# We also need to add entities tied to a device in the area that don't themselves
# have an area specified since they inherit the area from the device.
entity_ids.extend(
[
entity.entity_id
for device in dr.async_entries_for_area(dev_reg, _area_id)
for entity in er.async_entries_for_device(ent_reg, device.id)
if entity.area_id is None
]
)
return entity_ids
def area_devices(hass: HomeAssistant, area_id_or_name: str) -> Iterable[str]:
"""Return device IDs for a given area ID or name."""
_area_id: str | None
# if area_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 area_name(hass, area_id_or_name) is not None:
_area_id = area_id_or_name
else:
_area_id = area_id(hass, area_id_or_name)
if _area_id is None:
return []
dev_reg = dr.async_get(hass)
entries = dr.async_entries_for_area(dev_reg, _area_id)
return [entry.id for entry in entries]
def closest(hass: HomeAssistant, *args: Any) -> State | None:
"""Find closest entity.
@@ -2182,6 +2083,7 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
] = weakref.WeakValueDictionary()
self.add_extension("jinja2.ext.loopcontrols")
self.add_extension("jinja2.ext.do")
self.add_extension("homeassistant.helpers.template.extensions.AreaExtension")
self.add_extension("homeassistant.helpers.template.extensions.Base64Extension")
self.add_extension(
"homeassistant.helpers.template.extensions.CollectionExtension"
@@ -2276,22 +2178,6 @@ class TemplateEnvironment(ImmutableSandboxedEnvironment):
return jinja_context(wrapper)
# Area extensions
self.globals["areas"] = hassfunction(areas)
self.globals["area_id"] = hassfunction(area_id)
self.filters["area_id"] = self.globals["area_id"]
self.globals["area_name"] = hassfunction(area_name)
self.filters["area_name"] = self.globals["area_name"]
self.globals["area_entities"] = hassfunction(area_entities)
self.filters["area_entities"] = self.globals["area_entities"]
self.globals["area_devices"] = hassfunction(area_devices)
self.filters["area_devices"] = self.globals["area_devices"]
# Integration extensions
self.globals["integration_entities"] = hassfunction(integration_entities)
@@ -1,5 +1,6 @@
"""Home Assistant template extensions."""
from .areas import AreaExtension
from .base64 import Base64Extension
from .collection import CollectionExtension
from .crypto import CryptoExtension
@@ -11,6 +12,7 @@ from .regex import RegexExtension
from .string import StringExtension
__all__ = [
"AreaExtension",
"Base64Extension",
"CollectionExtension",
"CryptoExtension",
@@ -0,0 +1,159 @@
"""Area functions for Home Assistant templates."""
from __future__ import annotations
from collections.abc import Iterable
from typing import TYPE_CHECKING
import voluptuous as vol
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.template.helpers import resolve_area_id
from .base import BaseTemplateExtension, TemplateFunction
if TYPE_CHECKING:
from homeassistant.helpers.template import TemplateEnvironment
class AreaExtension(BaseTemplateExtension):
"""Extension for area-related template functions."""
def __init__(self, environment: TemplateEnvironment) -> None:
"""Initialize the area extension."""
super().__init__(
environment,
functions=[
TemplateFunction(
"areas",
self.areas,
as_global=True,
requires_hass=True,
),
TemplateFunction(
"area_id",
self.area_id,
as_global=True,
as_filter=True,
requires_hass=True,
limited_ok=False,
),
TemplateFunction(
"area_name",
self.area_name,
as_global=True,
as_filter=True,
requires_hass=True,
limited_ok=False,
),
TemplateFunction(
"area_entities",
self.area_entities,
as_global=True,
as_filter=True,
requires_hass=True,
),
TemplateFunction(
"area_devices",
self.area_devices,
as_global=True,
as_filter=True,
requires_hass=True,
),
],
)
def areas(self) -> Iterable[str | None]:
"""Return all areas."""
return list(ar.async_get(self.hass).areas)
def area_id(self, lookup_value: str) -> str | None:
"""Get the area ID from an area name, alias, device id, or entity id."""
return resolve_area_id(self.hass, lookup_value)
def _get_area_name(self, area_reg: ar.AreaRegistry, valid_area_id: str) -> str:
"""Get area name from valid area ID."""
area = area_reg.async_get_area(valid_area_id)
assert area
return area.name
def area_name(self, lookup_value: str) -> str | None:
"""Get the area name from an area id, device id, or entity id."""
area_reg = ar.async_get(self.hass)
if area := area_reg.async_get_area(lookup_value):
return area.name
dev_reg = dr.async_get(self.hass)
ent_reg = er.async_get(self.hass)
# Import here, not at top-level to avoid circular import
from homeassistant.helpers import config_validation as cv # noqa: PLC0415
try:
cv.entity_id(lookup_value)
except vol.Invalid:
pass
else:
if entity := ent_reg.async_get(lookup_value):
# If entity has an area ID, get the area name for that
if entity.area_id:
return self._get_area_name(area_reg, entity.area_id)
# If entity has a device ID and the device exists with an area ID, get the
# area name for that
if (
entity.device_id
and (device := dev_reg.async_get(entity.device_id))
and device.area_id
):
return self._get_area_name(area_reg, device.area_id)
if (device := dev_reg.async_get(lookup_value)) and device.area_id:
return self._get_area_name(area_reg, device.area_id)
return None
def area_entities(self, area_id_or_name: str) -> Iterable[str]:
"""Return entities for a given area ID or name."""
_area_id: str | None
# if area_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 self.area_name(area_id_or_name) is None:
_area_id = self.area_id(area_id_or_name)
else:
_area_id = area_id_or_name
if _area_id is None:
return []
ent_reg = er.async_get(self.hass)
entity_ids = [
entry.entity_id for entry in er.async_entries_for_area(ent_reg, _area_id)
]
dev_reg = dr.async_get(self.hass)
# We also need to add entities tied to a device in the area that don't themselves
# have an area specified since they inherit the area from the device.
entity_ids.extend(
[
entity.entity_id
for device in dr.async_entries_for_area(dev_reg, _area_id)
for entity in er.async_entries_for_device(ent_reg, device.id)
if entity.area_id is None
]
)
return entity_ids
def area_devices(self, area_id_or_name: str) -> Iterable[str]:
"""Return device IDs for a given area ID or name."""
_area_id: str | None
# if area_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 self.area_name(area_id_or_name) is not None:
_area_id = area_id_or_name
else:
_area_id = self.area_id(area_id_or_name)
if _area_id is None:
return []
dev_reg = dr.async_get(self.hass)
entries = dr.async_entries_for_area(dev_reg, _area_id)
return [entry.id for entry in entries]
@@ -0,0 +1,313 @@
"""Test area template functions."""
from __future__ import annotations
from homeassistant.core import HomeAssistant
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity_registry as er,
)
from tests.common import MockConfigEntry
from tests.helpers.template.helpers import assert_result_info, render_to_info
async def test_areas(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None:
"""Test areas function."""
# Test no areas
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test one area
area1 = area_registry.async_get_or_create("area1")
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [area1.id])
assert info.rate_limit is None
# Test multiple areas
area2 = area_registry.async_get_or_create("area2")
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [area1.id, area2.id])
assert info.rate_limit is None
async def test_area_id(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_id function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing entity id
info = render_to_info(hass, "{{ area_id('sensor.fake') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing device id (hex value)
info = render_to_info(hass, "{{ area_id('123abc') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing area name
info = render_to_info(hass, "{{ area_id('fake area name') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_id(56) }}")
assert_result_info(info, None)
assert info.rate_limit is None
area_registry.async_get_or_create("sensor.fake")
# Test device with single entity, which has no area
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,
)
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device ID, entity ID and area name as input with area name that looks like
# a device ID. Try a filter too
area_entry_hex = area_registry.async_get_or_create("123abc")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry_hex.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_hex.id
)
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_id }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{area_entry_hex.name}') }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
# Test device ID, entity ID and area name as input with area name that looks like an
# entity ID
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry_entity_id.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_entity_id.id
)
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{area_entry_entity_id.name}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
# Make sure that when entity doesn't have an area but its device does, that's what
# gets returned
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_entity_id.id
)
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
async def test_area_name(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_name function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing entity id
info = render_to_info(hass, "{{ area_name('sensor.fake') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing device id (hex value)
info = render_to_info(hass, "{{ area_name('123abc') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing area id
info = render_to_info(hass, "{{ area_name('1234567890') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_name(56) }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device with single entity, which has no area
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,
)
info = render_to_info(hass, f"{{{{ area_name('{device_entry.id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device ID, entity ID and area id as input. Try a filter too
area_entry = area_registry.async_get_or_create("123abc")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry.id
)
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_name }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{area_entry.id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
# Make sure that when entity doesn't have an area but its device does, that's what
# gets returned
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=None
)
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
async def test_area_entities(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_entities function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing device id
info = render_to_info(hass, "{{ area_entities('deadbeef') }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_entities(56) }}")
assert_result_info(info, [])
assert info.rate_limit is None
area_entry = area_registry.async_get_or_create("sensor.fake")
entity_entry = entity_registry.async_get_or_create(
"light",
"hue",
"5678",
config_entry=config_entry,
)
entity_registry.async_update_entity(entity_entry.entity_id, area_id=area_entry.id)
info = render_to_info(hass, f"{{{{ area_entities('{area_entry.id}') }}}}")
assert_result_info(info, ["light.hue_5678"])
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
assert_result_info(info, ["light.hue_5678"])
assert info.rate_limit is None
# Test for entities that inherit area from device
device_entry = device_registry.async_get_or_create(
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
config_entry_id=config_entry.entry_id,
suggested_area="sensor.fake",
)
entity_registry.async_get_or_create(
"light",
"hue_light",
"5678",
config_entry=config_entry,
device_id=device_entry.id,
)
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
assert_result_info(info, ["light.hue_5678", "light.hue_light_5678"])
assert info.rate_limit is None
async def test_area_devices(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test area_devices function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing device id
info = render_to_info(hass, "{{ area_devices('deadbeef') }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_devices(56) }}")
assert_result_info(info, [])
assert info.rate_limit is None
area_entry = area_registry.async_get_or_create("sensor.fake")
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")},
suggested_area=area_entry.name,
)
info = render_to_info(hass, f"{{{{ area_devices('{area_entry.id}') }}}}")
assert_result_info(info, [device_entry.id])
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_devices }}}}")
assert_result_info(info, [device_entry.id])
assert info.rate_limit is None
-302
View File
@@ -36,8 +36,6 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import (
area_registry as ar,
device_registry as dr,
entity,
entity_registry as er,
issue_registry as ir,
@@ -2636,306 +2634,6 @@ async def test_issue(hass: HomeAssistant, issue_registry: ir.IssueRegistry) -> N
assert info.rate_limit is None
async def test_areas(hass: HomeAssistant, area_registry: ar.AreaRegistry) -> None:
"""Test areas function."""
# Test no areas
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test one area
area1 = area_registry.async_get_or_create("area1")
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [area1.id])
assert info.rate_limit is None
# Test multiple areas
area2 = area_registry.async_get_or_create("area2")
info = render_to_info(hass, "{{ areas() }}")
assert_result_info(info, [area1.id, area2.id])
assert info.rate_limit is None
async def test_area_id(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_id function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing entity id
info = render_to_info(hass, "{{ area_id('sensor.fake') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing device id (hex value)
info = render_to_info(hass, "{{ area_id('123abc') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing area name
info = render_to_info(hass, "{{ area_id('fake area name') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_id(56) }}")
assert_result_info(info, None)
assert info.rate_limit is None
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
# Test device with single entity, which has no area
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,
)
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device ID, entity ID and area name as input with area name that looks like
# a device ID. Try a filter too
area_entry_hex = area_registry.async_get_or_create("123abc")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry_hex.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_hex.id
)
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_id }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{area_entry_hex.name}') }}}}")
assert_result_info(info, area_entry_hex.id)
assert info.rate_limit is None
# Test device ID, entity ID and area name as input with area name that looks like an
# entity ID
area_entry_entity_id = area_registry.async_get_or_create("sensor.fake")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry_entity_id.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_entity_id.id
)
info = render_to_info(hass, f"{{{{ area_id('{device_entry.id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_id('{area_entry_entity_id.name}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
# Make sure that when entity doesn't have an area but its device does, that's what
# gets returned
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry_entity_id.id
)
info = render_to_info(hass, f"{{{{ area_id('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry_entity_id.id)
assert info.rate_limit is None
async def test_area_name(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_name function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing entity id
info = render_to_info(hass, "{{ area_name('sensor.fake') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing device id (hex value)
info = render_to_info(hass, "{{ area_name('123abc') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test non existing area id
info = render_to_info(hass, "{{ area_name('1234567890') }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_name(56) }}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device with single entity, which has no area
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,
)
info = render_to_info(hass, f"{{{{ area_name('{device_entry.id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, None)
assert info.rate_limit is None
# Test device ID, entity ID and area id as input. Try a filter too
area_entry = area_registry.async_get_or_create("123abc")
device_entry = device_registry.async_update_device(
device_entry.id, area_id=area_entry.id
)
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=area_entry.id
)
info = render_to_info(hass, f"{{{{ '{device_entry.id}' | area_name }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ area_name('{area_entry.id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
# Make sure that when entity doesn't have an area but its device does, that's what
# gets returned
entity_entry = entity_registry.async_update_entity(
entity_entry.entity_id, area_id=None
)
info = render_to_info(hass, f"{{{{ area_name('{entity_entry.entity_id}') }}}}")
assert_result_info(info, area_entry.name)
assert info.rate_limit is None
async def test_area_entities(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test area_entities function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing device id
info = render_to_info(hass, "{{ area_entities('deadbeef') }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_entities(56) }}")
assert_result_info(info, [])
assert info.rate_limit is None
area_entry = area_registry.async_get_or_create("sensor.fake")
entity_entry = entity_registry.async_get_or_create(
"light",
"hue",
"5678",
config_entry=config_entry,
)
entity_registry.async_update_entity(entity_entry.entity_id, area_id=area_entry.id)
info = render_to_info(hass, f"{{{{ area_entities('{area_entry.id}') }}}}")
assert_result_info(info, ["light.hue_5678"])
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
assert_result_info(info, ["light.hue_5678"])
assert info.rate_limit is None
# Test for entities that inherit area from device
device_entry = device_registry.async_get_or_create(
connections={(dr.CONNECTION_NETWORK_MAC, "12:34:56:AB:CD:EF")},
config_entry_id=config_entry.entry_id,
suggested_area="sensor.fake",
)
entity_registry.async_get_or_create(
"light",
"hue_light",
"5678",
config_entry=config_entry,
device_id=device_entry.id,
)
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_entities }}}}")
assert_result_info(info, ["light.hue_5678", "light.hue_light_5678"])
assert info.rate_limit is None
async def test_area_devices(
hass: HomeAssistant,
area_registry: ar.AreaRegistry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test area_devices function."""
config_entry = MockConfigEntry(domain="light")
config_entry.add_to_hass(hass)
# Test non existing device id
info = render_to_info(hass, "{{ area_devices('deadbeef') }}")
assert_result_info(info, [])
assert info.rate_limit is None
# Test wrong value type
info = render_to_info(hass, "{{ area_devices(56) }}")
assert_result_info(info, [])
assert info.rate_limit is None
area_entry = area_registry.async_get_or_create("sensor.fake")
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")},
suggested_area=area_entry.name,
)
info = render_to_info(hass, f"{{{{ area_devices('{area_entry.id}') }}}}")
assert_result_info(info, [device_entry.id])
assert info.rate_limit is None
info = render_to_info(hass, f"{{{{ '{area_entry.name}' | area_devices }}}}")
assert_result_info(info, [device_entry.id])
assert info.rate_limit is None
def test_closest_function_to_coord(hass: HomeAssistant) -> None:
"""Test closest function to coord."""
hass.states.async_set(