Add update events to registries (#23746)
* Add update events to registries * Add to websocketpull/23732/merge
parent
6e7a7ba4a0
commit
07ee3b2eb9
|
@ -1,4 +1,5 @@
|
|||
config/*
|
||||
config2/*
|
||||
|
||||
tests/testing_config/deps
|
||||
tests/testing_config/home-assistant.log
|
||||
|
|
|
@ -10,6 +10,9 @@ from homeassistant.const import (
|
|||
EVENT_THEMES_UPDATED)
|
||||
from homeassistant.components.persistent_notification import (
|
||||
EVENT_PERSISTENT_NOTIFICATIONS_UPDATED)
|
||||
from homeassistant.helpers.area_registry import EVENT_AREA_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.device_registry import EVENT_DEVICE_REGISTRY_UPDATED
|
||||
from homeassistant.helpers.entity_registry import EVENT_ENTITY_REGISTRY_UPDATED
|
||||
|
||||
# These are events that do not contain any sensitive data
|
||||
# Except for state_changed, which is handled accordingly.
|
||||
|
@ -20,4 +23,7 @@ SUBSCRIBE_WHITELIST = {
|
|||
EVENT_SERVICE_REMOVED,
|
||||
EVENT_STATE_CHANGED,
|
||||
EVENT_THEMES_UPDATED,
|
||||
EVENT_AREA_REGISTRY_UPDATED,
|
||||
EVENT_DEVICE_REGISTRY_UPDATED,
|
||||
EVENT_ENTITY_REGISTRY_UPDATED,
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ from .typing import HomeAssistantType
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DATA_REGISTRY = 'area_registry'
|
||||
|
||||
EVENT_AREA_REGISTRY_UPDATED = 'area_registry_updated'
|
||||
STORAGE_KEY = 'core.area_registry'
|
||||
STORAGE_VERSION = 1
|
||||
SAVE_DELAY = 10
|
||||
|
@ -58,7 +58,14 @@ class AreaRegistry:
|
|||
area = AreaEntry()
|
||||
self.areas[area.id] = area
|
||||
|
||||
return self.async_update(area.id, name=name)
|
||||
created = self._async_update(area.id, name=name)
|
||||
|
||||
self.hass.bus.async_fire(EVENT_AREA_REGISTRY_UPDATED, {
|
||||
'action': 'create',
|
||||
'area_id': created.id,
|
||||
})
|
||||
|
||||
return created
|
||||
|
||||
async def async_delete(self, area_id: str) -> None:
|
||||
"""Delete area."""
|
||||
|
@ -68,10 +75,25 @@ class AreaRegistry:
|
|||
|
||||
del self.areas[area_id]
|
||||
|
||||
self.hass.bus.async_fire(EVENT_AREA_REGISTRY_UPDATED, {
|
||||
'action': 'remove',
|
||||
'area_id': area_id,
|
||||
})
|
||||
|
||||
self.async_schedule_save()
|
||||
|
||||
@callback
|
||||
def async_update(self, area_id: str, name: str) -> AreaEntry:
|
||||
"""Update name of area."""
|
||||
updated = self._async_update(area_id, name)
|
||||
self.hass.bus.async_fire(EVENT_AREA_REGISTRY_UPDATED, {
|
||||
'action': 'update',
|
||||
'area_id': area_id,
|
||||
})
|
||||
return updated
|
||||
|
||||
@callback
|
||||
def _async_update(self, area_id: str, name: str) -> AreaEntry:
|
||||
"""Update name of area."""
|
||||
old = self.areas[area_id]
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ _LOGGER = logging.getLogger(__name__)
|
|||
_UNDEF = object()
|
||||
|
||||
DATA_REGISTRY = 'device_registry'
|
||||
|
||||
EVENT_DEVICE_REGISTRY_UPDATED = 'device_registry_updated'
|
||||
STORAGE_KEY = 'core.device_registry'
|
||||
STORAGE_VERSION = 1
|
||||
SAVE_DELAY = 10
|
||||
|
@ -42,6 +42,8 @@ class DeviceEntry:
|
|||
area_id = attr.ib(type=str, default=None)
|
||||
name_by_user = attr.ib(type=str, default=None)
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
# This value is not stored, just used to keep track of events to fire.
|
||||
is_new = attr.ib(type=bool, default=False)
|
||||
|
||||
|
||||
def format_mac(mac):
|
||||
|
@ -111,7 +113,7 @@ class DeviceRegistry:
|
|||
device = self.async_get_device(identifiers, connections)
|
||||
|
||||
if device is None:
|
||||
device = DeviceEntry()
|
||||
device = DeviceEntry(is_new=True)
|
||||
self.devices[device.id] = device
|
||||
|
||||
if via_hub is not None:
|
||||
|
@ -201,11 +203,20 @@ class DeviceRegistry:
|
|||
name_by_user != old.name_by_user):
|
||||
changes['name_by_user'] = name_by_user
|
||||
|
||||
if old.is_new:
|
||||
changes['is_new'] = False
|
||||
|
||||
if not changes:
|
||||
return old
|
||||
|
||||
new = self.devices[device_id] = attr.evolve(old, **changes)
|
||||
self.async_schedule_save()
|
||||
|
||||
self.hass.bus.async_fire(EVENT_DEVICE_REGISTRY_UPDATED, {
|
||||
'action': 'create' if 'is_new' in changes else 'update',
|
||||
'device_id': new.id,
|
||||
})
|
||||
|
||||
return new
|
||||
|
||||
async def async_load(self):
|
||||
|
|
|
@ -25,6 +25,7 @@ from .typing import HomeAssistantType
|
|||
|
||||
PATH_REGISTRY = 'entity_registry.yaml'
|
||||
DATA_REGISTRY = 'entity_registry'
|
||||
EVENT_ENTITY_REGISTRY_UPDATED = 'entity_registry_updated'
|
||||
SAVE_DELAY = 10
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
_UNDEF = object()
|
||||
|
@ -150,12 +151,22 @@ class EntityRegistry:
|
|||
_LOGGER.info('Registered new %s.%s entity: %s',
|
||||
domain, platform, entity_id)
|
||||
self.async_schedule_save()
|
||||
|
||||
self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {
|
||||
'action': 'create',
|
||||
'entity_id': entity_id
|
||||
})
|
||||
|
||||
return entity
|
||||
|
||||
@callback
|
||||
def async_remove(self, entity_id):
|
||||
"""Remove an entity from registry."""
|
||||
self.entities.pop(entity_id)
|
||||
self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {
|
||||
'action': 'remove',
|
||||
'entity_id': entity_id
|
||||
})
|
||||
self.async_schedule_save()
|
||||
|
||||
@callback
|
||||
|
@ -234,6 +245,11 @@ class EntityRegistry:
|
|||
|
||||
self.async_schedule_save()
|
||||
|
||||
self.hass.bus.async_fire(EVENT_ENTITY_REGISTRY_UPDATED, {
|
||||
'action': 'update',
|
||||
'entity_id': entity_id
|
||||
})
|
||||
|
||||
return new
|
||||
|
||||
async def async_load(self):
|
||||
|
|
|
@ -4,6 +4,7 @@ import asyncio
|
|||
import asynctest
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import area_registry
|
||||
from tests.common import mock_area_registry, flush_store
|
||||
|
||||
|
@ -14,6 +15,21 @@ def registry(hass):
|
|||
return mock_area_registry(hass)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def update_events(hass):
|
||||
"""Capture update events."""
|
||||
events = []
|
||||
|
||||
@callback
|
||||
def async_capture(event):
|
||||
events.append(event.data)
|
||||
|
||||
hass.bus.async_listen(area_registry.EVENT_AREA_REGISTRY_UPDATED,
|
||||
async_capture)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
async def test_list_areas(registry):
|
||||
"""Make sure that we can read areas."""
|
||||
registry.async_create('mock')
|
||||
|
@ -23,15 +39,22 @@ async def test_list_areas(registry):
|
|||
assert len(areas) == len(registry.areas)
|
||||
|
||||
|
||||
async def test_create_area(registry):
|
||||
async def test_create_area(hass, registry, update_events):
|
||||
"""Make sure that we can create an area."""
|
||||
area = registry.async_create('mock')
|
||||
|
||||
assert area.name == 'mock'
|
||||
assert len(registry.areas) == 1
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
async def test_create_area_with_name_already_in_use(registry):
|
||||
assert len(update_events) == 1
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['area_id'] == area.id
|
||||
|
||||
|
||||
async def test_create_area_with_name_already_in_use(hass, registry,
|
||||
update_events):
|
||||
"""Make sure that we can't create an area with a name already in use."""
|
||||
area1 = registry.async_create('mock')
|
||||
|
||||
|
@ -40,10 +63,13 @@ async def test_create_area_with_name_already_in_use(registry):
|
|||
assert area1 != area2
|
||||
assert e_info == "Name is already in use"
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(registry.areas) == 1
|
||||
assert len(update_events) == 1
|
||||
|
||||
|
||||
async def test_delete_area(registry):
|
||||
async def test_delete_area(hass, registry, update_events):
|
||||
"""Make sure that we can delete an area."""
|
||||
area = registry.async_create('mock')
|
||||
|
||||
|
@ -51,6 +77,14 @@ async def test_delete_area(registry):
|
|||
|
||||
assert not registry.areas
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(update_events) == 2
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['area_id'] == area.id
|
||||
assert update_events[1]['action'] == 'remove'
|
||||
assert update_events[1]['area_id'] == area.id
|
||||
|
||||
|
||||
async def test_delete_non_existing_area(registry):
|
||||
"""Make sure that we can't delete an area that doesn't exist."""
|
||||
|
@ -62,7 +96,7 @@ async def test_delete_non_existing_area(registry):
|
|||
assert len(registry.areas) == 1
|
||||
|
||||
|
||||
async def test_update_area(registry):
|
||||
async def test_update_area(hass, registry, update_events):
|
||||
"""Make sure that we can read areas."""
|
||||
area = registry.async_create('mock')
|
||||
|
||||
|
@ -72,6 +106,14 @@ async def test_update_area(registry):
|
|||
assert updated_area.name == 'mock1'
|
||||
assert len(registry.areas) == 1
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(update_events) == 2
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['area_id'] == area.id
|
||||
assert update_events[1]['action'] == 'update'
|
||||
assert update_events[1]['area_id'] == area.id
|
||||
|
||||
|
||||
async def test_update_area_with_same_name(registry):
|
||||
"""Make sure that we can reapply the same name to the area."""
|
||||
|
|
|
@ -5,6 +5,7 @@ from unittest.mock import patch
|
|||
import asynctest
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers import device_registry
|
||||
from tests.common import mock_device_registry, flush_store
|
||||
|
||||
|
@ -15,7 +16,22 @@ def registry(hass):
|
|||
return mock_device_registry(hass)
|
||||
|
||||
|
||||
async def test_get_or_create_returns_same_entry(registry):
|
||||
@pytest.fixture
|
||||
def update_events(hass):
|
||||
"""Capture update events."""
|
||||
events = []
|
||||
|
||||
@callback
|
||||
def async_capture(event):
|
||||
events.append(event.data)
|
||||
|
||||
hass.bus.async_listen(device_registry.EVENT_DEVICE_REGISTRY_UPDATED,
|
||||
async_capture)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
async def test_get_or_create_returns_same_entry(hass, registry, update_events):
|
||||
"""Make sure we do not duplicate entries."""
|
||||
entry = registry.async_get_or_create(
|
||||
config_entry_id='1234',
|
||||
|
@ -51,6 +67,15 @@ async def test_get_or_create_returns_same_entry(registry):
|
|||
assert entry3.name == 'name'
|
||||
assert entry3.sw_version == 'sw-version'
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Only 2 update events. The third entry did not generate any changes.
|
||||
assert len(update_events) == 2
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['device_id'] == entry.id
|
||||
assert update_events[1]['action'] == 'update'
|
||||
assert update_events[1]['device_id'] == entry.id
|
||||
|
||||
|
||||
async def test_requirement_for_identifier_or_connection(registry):
|
||||
"""Make sure we do require some descriptor of device."""
|
||||
|
@ -155,7 +180,7 @@ async def test_loading_from_storage(hass, hass_storage):
|
|||
assert isinstance(entry.config_entries, set)
|
||||
|
||||
|
||||
async def test_removing_config_entries(registry):
|
||||
async def test_removing_config_entries(hass, registry, update_events):
|
||||
"""Make sure we do not get duplicate entries."""
|
||||
entry = registry.async_get_or_create(
|
||||
config_entry_id='123',
|
||||
|
@ -191,6 +216,20 @@ async def test_removing_config_entries(registry):
|
|||
assert entry.config_entries == {'456'}
|
||||
assert entry3.config_entries == set()
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(update_events) == 5
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['device_id'] == entry.id
|
||||
assert update_events[1]['action'] == 'update'
|
||||
assert update_events[1]['device_id'] == entry2.id
|
||||
assert update_events[2]['action'] == 'create'
|
||||
assert update_events[2]['device_id'] == entry3.id
|
||||
assert update_events[3]['action'] == 'update'
|
||||
assert update_events[3]['device_id'] == entry.id
|
||||
assert update_events[4]['action'] == 'update'
|
||||
assert update_events[4]['device_id'] == entry3.id
|
||||
|
||||
|
||||
async def test_removing_area_id(registry):
|
||||
"""Make sure we can clear area id."""
|
||||
|
|
|
@ -5,7 +5,7 @@ from unittest.mock import patch
|
|||
import asynctest
|
||||
import pytest
|
||||
|
||||
from homeassistant.core import valid_entity_id
|
||||
from homeassistant.core import valid_entity_id, callback
|
||||
from homeassistant.helpers import entity_registry
|
||||
|
||||
from tests.common import mock_registry, flush_store
|
||||
|
@ -20,14 +20,34 @@ def registry(hass):
|
|||
return mock_registry(hass)
|
||||
|
||||
|
||||
def test_get_or_create_returns_same_entry(registry):
|
||||
@pytest.fixture
|
||||
def update_events(hass):
|
||||
"""Capture update events."""
|
||||
events = []
|
||||
|
||||
@callback
|
||||
def async_capture(event):
|
||||
events.append(event.data)
|
||||
|
||||
hass.bus.async_listen(entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
|
||||
async_capture)
|
||||
|
||||
return events
|
||||
|
||||
|
||||
async def test_get_or_create_returns_same_entry(hass, registry, update_events):
|
||||
"""Make sure we do not duplicate entries."""
|
||||
entry = registry.async_get_or_create('light', 'hue', '1234')
|
||||
entry2 = registry.async_get_or_create('light', 'hue', '1234')
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(registry.entities) == 1
|
||||
assert entry is entry2
|
||||
assert entry.entity_id == 'light.hue_1234'
|
||||
assert len(update_events) == 1
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['entity_id'] == entry.entity_id
|
||||
|
||||
|
||||
def test_get_or_create_suggested_object_id(registry):
|
||||
|
@ -168,7 +188,7 @@ def test_async_get_entity_id(registry):
|
|||
assert registry.async_get_entity_id('light', 'hue', '123') is None
|
||||
|
||||
|
||||
def test_updating_config_entry_id(registry):
|
||||
async def test_updating_config_entry_id(hass, registry, update_events):
|
||||
"""Test that we update config entry id in registry."""
|
||||
entry = registry.async_get_or_create(
|
||||
'light', 'hue', '5678', config_entry_id='mock-id-1')
|
||||
|
@ -177,8 +197,16 @@ def test_updating_config_entry_id(registry):
|
|||
assert entry.entity_id == entry2.entity_id
|
||||
assert entry2.config_entry_id == 'mock-id-2'
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
def test_removing_config_entry_id(registry):
|
||||
assert len(update_events) == 2
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['entity_id'] == entry.entity_id
|
||||
assert update_events[1]['action'] == 'update'
|
||||
assert update_events[1]['entity_id'] == entry.entity_id
|
||||
|
||||
|
||||
async def test_removing_config_entry_id(hass, registry, update_events):
|
||||
"""Test that we update config entry id in registry."""
|
||||
entry = registry.async_get_or_create(
|
||||
'light', 'hue', '5678', config_entry_id='mock-id-1')
|
||||
|
@ -188,6 +216,14 @@ def test_removing_config_entry_id(registry):
|
|||
entry = registry.entities[entry.entity_id]
|
||||
assert entry.config_entry_id is None
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert len(update_events) == 2
|
||||
assert update_events[0]['action'] == 'create'
|
||||
assert update_events[0]['entity_id'] == entry.entity_id
|
||||
assert update_events[1]['action'] == 'update'
|
||||
assert update_events[1]['entity_id'] == entry.entity_id
|
||||
|
||||
|
||||
async def test_migration(hass):
|
||||
"""Test migration from old data to new."""
|
||||
|
|
Loading…
Reference in New Issue