Add entity options to entity registry (#64350)
* Initial commit for entity options * Tweak broadlink tests * Add async_update_entity_options + testpull/64437/head
parent
57bcddbba2
commit
a8c14835b9
|
@ -57,7 +57,7 @@ SAVE_DELAY = 10
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
STORAGE_VERSION_MAJOR = 1
|
||||
STORAGE_VERSION_MINOR = 4
|
||||
STORAGE_VERSION_MINOR = 5
|
||||
STORAGE_KEY = "core.entity_registry"
|
||||
|
||||
# Attributes relevant to describing entity
|
||||
|
@ -109,6 +109,9 @@ class RegistryEntry:
|
|||
icon: str | None = attr.ib(default=None)
|
||||
id: str = attr.ib(factory=uuid_util.random_uuid_hex)
|
||||
name: str | None = attr.ib(default=None)
|
||||
options: Mapping[str, Mapping[str, Any]] = attr.ib(
|
||||
default=None, converter=attr.converters.default_if_none(factory=dict) # type: ignore[misc]
|
||||
)
|
||||
# As set by integration
|
||||
original_device_class: str | None = attr.ib(default=None)
|
||||
original_icon: str | None = attr.ib(default=None)
|
||||
|
@ -560,6 +563,25 @@ class EntityRegistry:
|
|||
|
||||
return new
|
||||
|
||||
@callback
|
||||
def async_update_entity_options(
|
||||
self, entity_id: str, domain: str, options: dict[str, Any]
|
||||
) -> None:
|
||||
"""Update entity options."""
|
||||
old = self.entities[entity_id]
|
||||
new_options: Mapping[str, Mapping[str, Any]] = {**old.options, domain: options}
|
||||
self.entities[entity_id] = attr.evolve(old, options=new_options)
|
||||
|
||||
self.async_schedule_save()
|
||||
|
||||
data: dict[str, str | dict[str, Any]] = {
|
||||
"action": "update",
|
||||
"entity_id": entity_id,
|
||||
"changes": {"options": old.options},
|
||||
}
|
||||
|
||||
self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, data)
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load the entity registry."""
|
||||
async_setup_entity_restore(self.hass, self)
|
||||
|
@ -595,6 +617,7 @@ class EntityRegistry:
|
|||
icon=entity["icon"],
|
||||
id=entity["id"],
|
||||
name=entity["name"],
|
||||
options=entity["options"],
|
||||
original_device_class=entity["original_device_class"],
|
||||
original_icon=entity["original_icon"],
|
||||
original_name=entity["original_name"],
|
||||
|
@ -629,6 +652,7 @@ class EntityRegistry:
|
|||
"icon": entry.icon,
|
||||
"id": entry.id,
|
||||
"name": entry.name,
|
||||
"options": entry.options,
|
||||
"original_device_class": entry.original_device_class,
|
||||
"original_icon": entry.original_icon,
|
||||
"original_name": entry.original_name,
|
||||
|
@ -749,7 +773,7 @@ async def _async_migrate(
|
|||
old_major_version: int, old_minor_version: int, data: dict
|
||||
) -> dict:
|
||||
"""Migrate to the new version."""
|
||||
if old_major_version < 2 and old_minor_version < 2:
|
||||
if old_major_version == 1 and old_minor_version < 2:
|
||||
# From version 1.1
|
||||
for entity in data["entities"]:
|
||||
# Populate all keys
|
||||
|
@ -768,18 +792,23 @@ async def _async_migrate(
|
|||
entity["supported_features"] = entity.get("supported_features", 0)
|
||||
entity["unit_of_measurement"] = entity.get("unit_of_measurement")
|
||||
|
||||
if old_major_version < 2 and old_minor_version < 3:
|
||||
if old_major_version == 1 and old_minor_version < 3:
|
||||
# Version 1.3 adds original_device_class
|
||||
for entity in data["entities"]:
|
||||
# Move device_class to original_device_class
|
||||
entity["original_device_class"] = entity["device_class"]
|
||||
entity["device_class"] = None
|
||||
|
||||
if old_major_version < 2 and old_minor_version < 4:
|
||||
if old_major_version == 1 and old_minor_version < 4:
|
||||
# Version 1.4 adds id
|
||||
for entity in data["entities"]:
|
||||
entity["id"] = uuid_util.random_uuid_hex()
|
||||
|
||||
if old_major_version == 1 and old_minor_version < 5:
|
||||
# Version 1.5 adds entity options
|
||||
for entity in data["entities"]:
|
||||
entity["options"] = {}
|
||||
|
||||
if old_major_version > 1:
|
||||
raise NotImplementedError
|
||||
return data
|
||||
|
|
|
@ -34,10 +34,10 @@ async def test_remote_setup_works(hass):
|
|||
{(DOMAIN, mock_setup.entry.unique_id)}
|
||||
)
|
||||
entries = async_entries_for_device(entity_registry, device_entry.id)
|
||||
remotes = {entry for entry in entries if entry.domain == Platform.REMOTE}
|
||||
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
|
||||
assert len(remotes) == 1
|
||||
|
||||
remote = remotes.pop()
|
||||
remote = remotes[0]
|
||||
assert remote.original_name == f"{device.name} Remote"
|
||||
assert hass.states.get(remote.entity_id).state == STATE_ON
|
||||
assert mock_setup.api.auth.call_count == 1
|
||||
|
@ -54,10 +54,10 @@ async def test_remote_send_command(hass):
|
|||
{(DOMAIN, mock_setup.entry.unique_id)}
|
||||
)
|
||||
entries = async_entries_for_device(entity_registry, device_entry.id)
|
||||
remotes = {entry for entry in entries if entry.domain == Platform.REMOTE}
|
||||
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
|
||||
assert len(remotes) == 1
|
||||
|
||||
remote = remotes.pop()
|
||||
remote = remotes[0]
|
||||
await hass.services.async_call(
|
||||
Platform.REMOTE,
|
||||
SERVICE_SEND_COMMAND,
|
||||
|
@ -81,10 +81,10 @@ async def test_remote_turn_off_turn_on(hass):
|
|||
{(DOMAIN, mock_setup.entry.unique_id)}
|
||||
)
|
||||
entries = async_entries_for_device(entity_registry, device_entry.id)
|
||||
remotes = {entry for entry in entries if entry.domain == Platform.REMOTE}
|
||||
remotes = [entry for entry in entries if entry.domain == Platform.REMOTE]
|
||||
assert len(remotes) == 1
|
||||
|
||||
remote = remotes.pop()
|
||||
remote = remotes[0]
|
||||
await hass.services.async_call(
|
||||
Platform.REMOTE,
|
||||
SERVICE_TURN_OFF,
|
||||
|
|
|
@ -196,12 +196,16 @@ async def test_loading_saving_data(hass, registry):
|
|||
supported_features=5,
|
||||
unit_of_measurement="initial-unit_of_measurement",
|
||||
)
|
||||
orig_entry2 = registry.async_update_entity(
|
||||
registry.async_update_entity(
|
||||
orig_entry2.entity_id,
|
||||
device_class="user-class",
|
||||
name="User Name",
|
||||
icon="hass:user-icon",
|
||||
)
|
||||
registry.async_update_entity_options(
|
||||
orig_entry2.entity_id, "light", {"minimum_brightness": 20}
|
||||
)
|
||||
orig_entry2 = registry.async_get(orig_entry2.entity_id)
|
||||
|
||||
assert len(registry.entities) == 2
|
||||
|
||||
|
@ -227,6 +231,7 @@ async def test_loading_saving_data(hass, registry):
|
|||
assert new_entry2.entity_category == "config"
|
||||
assert new_entry2.icon == "hass:user-icon"
|
||||
assert new_entry2.name == "User Name"
|
||||
assert new_entry2.options == {"light": {"minimum_brightness": 20}}
|
||||
assert new_entry2.original_device_class == "mock-device-class"
|
||||
assert new_entry2.original_icon == "hass:original-icon"
|
||||
assert new_entry2.original_name == "Original Name"
|
||||
|
@ -570,6 +575,31 @@ async def test_update_entity(registry):
|
|||
entry = updated_entry
|
||||
|
||||
|
||||
async def test_update_entity_options(registry):
|
||||
"""Test updating entity."""
|
||||
mock_config = MockConfigEntry(domain="light", entry_id="mock-id-1")
|
||||
entry = registry.async_get_or_create(
|
||||
"light", "hue", "5678", config_entry=mock_config
|
||||
)
|
||||
|
||||
registry.async_update_entity_options(
|
||||
entry.entity_id, "light", {"minimum_brightness": 20}
|
||||
)
|
||||
new_entry_1 = registry.async_get(entry.entity_id)
|
||||
|
||||
assert entry.options == {}
|
||||
assert new_entry_1.options == {"light": {"minimum_brightness": 20}}
|
||||
|
||||
registry.async_update_entity_options(
|
||||
entry.entity_id, "light", {"minimum_brightness": 30}
|
||||
)
|
||||
new_entry_2 = registry.async_get(entry.entity_id)
|
||||
|
||||
assert entry.options == {}
|
||||
assert new_entry_1.options == {"light": {"minimum_brightness": 20}}
|
||||
assert new_entry_2.options == {"light": {"minimum_brightness": 30}}
|
||||
|
||||
|
||||
async def test_disabled_by(registry):
|
||||
"""Test that we can disable an entry when we create it."""
|
||||
entry = registry.async_get_or_create(
|
||||
|
|
Loading…
Reference in New Issue