Skip to content

Commit

Permalink
Add update events to registries (home-assistant#23746)
Browse files Browse the repository at this point in the history
* Add update events to registries

* Add to websocket
  • Loading branch information
balloob authored May 8, 2019
1 parent 6e7a7ba commit 07ee3b2
Show file tree
Hide file tree
Showing 8 changed files with 187 additions and 14 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
config/*
config2/*

tests/testing_config/deps
tests/testing_config/home-assistant.log
Expand Down
6 changes: 6 additions & 0 deletions homeassistant/components/websocket_api/permissions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
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.
Expand All @@ -20,4 +23,7 @@
EVENT_SERVICE_REMOVED,
EVENT_STATE_CHANGED,
EVENT_THEMES_UPDATED,
EVENT_AREA_REGISTRY_UPDATED,
EVENT_DEVICE_REGISTRY_UPDATED,
EVENT_ENTITY_REGISTRY_UPDATED,
}
26 changes: 24 additions & 2 deletions homeassistant/helpers/area_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
_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
Expand Down Expand Up @@ -58,7 +58,14 @@ def async_create(self, name: str) -> AreaEntry:
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."""
Expand All @@ -68,10 +75,25 @@ async def async_delete(self, area_id: str) -> None:

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]

Expand Down
15 changes: 13 additions & 2 deletions homeassistant/helpers/device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
_UNDEF = object()

DATA_REGISTRY = 'device_registry'

EVENT_DEVICE_REGISTRY_UPDATED = 'device_registry_updated'
STORAGE_KEY = 'core.device_registry'
STORAGE_VERSION = 1
SAVE_DELAY = 10
Expand All @@ -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):
Expand Down Expand Up @@ -111,7 +113,7 @@ def async_get_or_create(self, *, config_entry_id, connections=None,
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:
Expand Down Expand Up @@ -201,11 +203,20 @@ def _async_update_device(self, device_id, *, add_config_entry_id=_UNDEF,
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):
Expand Down
16 changes: 16 additions & 0 deletions homeassistant/helpers/entity_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

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()
Expand Down Expand Up @@ -150,12 +151,22 @@ def async_get_or_create(self, domain, platform, unique_id, *,
_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
Expand Down Expand Up @@ -234,6 +245,11 @@ def _async_update_entity(self, entity_id, *, name=_UNDEF,

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):
Expand Down
50 changes: 46 additions & 4 deletions tests/helpers/test_area_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import asynctest
import pytest

from homeassistant.core import callback
from homeassistant.helpers import area_registry
from tests.common import mock_area_registry, flush_store

Expand All @@ -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')
Expand All @@ -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()

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(registry):

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')

Expand All @@ -40,17 +63,28 @@ 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')

await registry.async_delete(area.id)

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."""
Expand All @@ -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')

Expand All @@ -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."""
Expand Down
43 changes: 41 additions & 2 deletions tests/helpers/test_device_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import asynctest
import pytest

from homeassistant.core import callback
from homeassistant.helpers import device_registry
from tests.common import mock_device_registry, flush_store

Expand All @@ -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',
Expand Down Expand Up @@ -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."""
Expand Down Expand Up @@ -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',
Expand Down Expand Up @@ -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."""
Expand Down
Loading

0 comments on commit 07ee3b2

Please sign in to comment.