From 05c07171674ee9f40c8ae29709d04d6b3d24db3c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Sep 2018 11:57:18 +0200 Subject: [PATCH] Add websocket list APIs for the registries (#16597) * Add websocket list APIs for the registries * Remove identifiers * Fix tests * Use ordered dict --- homeassistant/components/config/__init__.py | 13 +++- .../components/config/device_registry.py | 46 ++++++++++++++ .../components/config/entity_registry.py | 32 ++++++++++ tests/common.py | 15 ++++- .../components/config/test_device_registry.py | 60 +++++++++++++++++++ .../components/config/test_entity_registry.py | 45 ++++++++++++++ tests/helpers/test_device_registry.py | 18 +----- 7 files changed, 210 insertions(+), 19 deletions(-) create mode 100644 homeassistant/components/config/device_registry.py create mode 100644 tests/components/config/test_device_registry.py diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index 581d8fc3f7b9..df0e2f13ac1d 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -13,8 +13,17 @@ from homeassistant.util.yaml import load_yaml, dump DOMAIN = 'config' DEPENDENCIES = ['http'] -SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script', - 'entity_registry', 'config_entries') +SECTIONS = ( + 'automation', + 'config_entries', + 'core', + 'customize', + 'device_registry', + 'entity_registry', + 'group', + 'hassbian', + 'script', +) ON_DEMAND = ('zwave',) diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py new file mode 100644 index 000000000000..8383e0cdc7da --- /dev/null +++ b/homeassistant/components/config/device_registry.py @@ -0,0 +1,46 @@ +"""HTTP views to interact with the device registry.""" +import voluptuous as vol + +from homeassistant.core import callback +from homeassistant.helpers.device_registry import async_get_registry +from homeassistant.components import websocket_api + +DEPENDENCIES = ['websocket_api'] + +WS_TYPE_LIST = 'config/device_registry/list' +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_LIST, +}) + + +async def async_setup(hass): + """Enable the Entity Registry views.""" + hass.components.websocket_api.async_register_command( + WS_TYPE_LIST, websocket_list_devices, + SCHEMA_WS_LIST + ) + return True + + +@callback +def websocket_list_devices(hass, connection, msg): + """Handle list devices command. + + Async friendly. + """ + async def retrieve_entities(): + """Get devices from registry.""" + registry = await async_get_registry(hass) + connection.send_message_outside(websocket_api.result_message( + msg['id'], [{ + 'config_entries': list(entry.config_entries), + 'connections': list(entry.connections), + 'manufacturer': entry.manufacturer, + 'model': entry.model, + 'name': entry.name, + 'sw_version': entry.sw_version, + 'id': entry.id, + } for entry in registry.devices.values()] + )) + + hass.async_add_job(retrieve_entities()) diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 2fac420c39c9..0f9abf167e51 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -8,6 +8,11 @@ from homeassistant.helpers import config_validation as cv DEPENDENCIES = ['websocket_api'] +WS_TYPE_LIST = 'config/entity_registry/list' +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ + vol.Required('type'): WS_TYPE_LIST, +}) + WS_TYPE_GET = 'config/entity_registry/get' SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ vol.Required('type'): WS_TYPE_GET, @@ -26,6 +31,10 @@ SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ async def async_setup(hass): """Enable the Entity Registry views.""" + hass.components.websocket_api.async_register_command( + WS_TYPE_LIST, websocket_list_entities, + SCHEMA_WS_LIST + ) hass.components.websocket_api.async_register_command( WS_TYPE_GET, websocket_get_entity, SCHEMA_WS_GET @@ -37,6 +46,29 @@ async def async_setup(hass): return True +@callback +def websocket_list_entities(hass, connection, msg): + """Handle list registry entries command. + + Async friendly. + """ + async def retrieve_entities(): + """Get entities from registry.""" + registry = await async_get_registry(hass) + connection.send_message_outside(websocket_api.result_message( + msg['id'], [{ + 'config_entry_id': entry.config_entry_id, + 'device_id': entry.device_id, + 'disabled_by': entry.disabled_by, + 'entity_id': entry.entity_id, + 'name': entry.name, + 'platform': entry.platform, + } for entry in registry.entities.values()] + )) + + hass.async_add_job(retrieve_entities()) + + @callback def websocket_get_entity(hass, connection, msg): """Handle get entity registry entry command. diff --git a/tests/common.py b/tests/common.py index 738c51fb3f09..26cd7743accb 100644 --- a/tests/common.py +++ b/tests/common.py @@ -19,7 +19,7 @@ from homeassistant.setup import setup_component, async_setup_component from homeassistant.config import async_process_component_config from homeassistant.helpers import ( intent, entity, restore_state, entity_registry, - entity_platform, storage) + entity_platform, storage, device_registry) from homeassistant.util.unit_system import METRIC_SYSTEM import homeassistant.util.dt as date_util import homeassistant.util.yaml as yaml @@ -332,6 +332,19 @@ def mock_registry(hass, mock_entries=None): return registry +def mock_device_registry(hass, mock_entries=None): + """Mock the Device Registry.""" + registry = device_registry.DeviceRegistry(hass) + registry.devices = mock_entries or OrderedDict() + + async def _get_reg(): + return registry + + hass.data[device_registry.DATA_REGISTRY] = \ + hass.loop.create_task(_get_reg()) + return registry + + class MockUser(auth_models.User): """Mock a user in Home Assistant.""" diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py new file mode 100644 index 000000000000..491319bf9279 --- /dev/null +++ b/tests/components/config/test_device_registry.py @@ -0,0 +1,60 @@ +"""Test entity_registry API.""" +import pytest + +from homeassistant.components.config import device_registry +from tests.common import mock_device_registry + + +@pytest.fixture +def client(hass, hass_ws_client): + """Fixture that can interact with the config manager API.""" + hass.loop.run_until_complete(device_registry.async_setup(hass)) + yield hass.loop.run_until_complete(hass_ws_client(hass)) + + +@pytest.fixture +def registry(hass): + """Return an empty, loaded, registry.""" + return mock_device_registry(hass) + + +async def test_list_devices(hass, client, registry): + """Test list entries.""" + registry.async_get_or_create( + config_entry='1234', + connections={('ethernet', '12:34:56:78:90:AB:CD:EF')}, + identifiers={('bridgeid', '0123')}, + manufacturer='manufacturer', model='model') + registry.async_get_or_create( + config_entry='1234', + connections={}, + identifiers={('bridgeid', '1234')}, + manufacturer='manufacturer', model='model') + + await client.send_json({ + 'id': 5, + 'type': 'config/device_registry/list', + }) + msg = await client.receive_json() + + for entry in msg['result']: + entry.pop('id') + + assert msg['result'] == [ + { + 'config_entries': ['1234'], + 'connections': [['ethernet', '12:34:56:78:90:AB:CD:EF']], + 'manufacturer': 'manufacturer', + 'model': 'model', + 'name': None, + 'sw_version': None + }, + { + 'config_entries': ['1234'], + 'connections': [], + 'manufacturer': 'manufacturer', + 'model': 'model', + 'name': None, + 'sw_version': None + } + ] diff --git a/tests/components/config/test_entity_registry.py b/tests/components/config/test_entity_registry.py index 559f29372de4..cd74faf18434 100644 --- a/tests/components/config/test_entity_registry.py +++ b/tests/components/config/test_entity_registry.py @@ -1,4 +1,6 @@ """Test entity_registry API.""" +from collections import OrderedDict + import pytest from homeassistant.helpers.entity_registry import RegistryEntry @@ -13,6 +15,49 @@ def client(hass, hass_ws_client): yield hass.loop.run_until_complete(hass_ws_client(hass)) +async def test_list_entities(hass, client): + """Test list entries.""" + entities = OrderedDict() + entities['test_domain.name'] = RegistryEntry( + entity_id='test_domain.name', + unique_id='1234', + platform='test_platform', + name='Hello World' + ) + entities['test_domain.no_name'] = RegistryEntry( + entity_id='test_domain.no_name', + unique_id='6789', + platform='test_platform', + ) + + mock_registry(hass, entities) + + await client.send_json({ + 'id': 5, + 'type': 'config/entity_registry/list', + }) + msg = await client.receive_json() + + assert msg['result'] == [ + { + 'config_entry_id': None, + 'device_id': None, + 'disabled_by': None, + 'entity_id': 'test_domain.name', + 'name': 'Hello World', + 'platform': 'test_platform', + }, + { + 'config_entry_id': None, + 'device_id': None, + 'disabled_by': None, + 'entity_id': 'test_domain.no_name', + 'name': None, + 'platform': 'test_platform', + } + ] + + async def test_get_entity(hass, client): """Test get entry.""" mock_registry(hass, { diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py index a9132529bc3b..5ae6b4df651f 100644 --- a/tests/helpers/test_device_registry.py +++ b/tests/helpers/test_device_registry.py @@ -1,28 +1,14 @@ """Tests for the Device Registry.""" import pytest -from collections import OrderedDict - from homeassistant.helpers import device_registry - - -def mock_registry(hass, mock_entries=None): - """Mock the Device Registry.""" - registry = device_registry.DeviceRegistry(hass) - registry.devices = mock_entries or OrderedDict() - - async def _get_reg(): - return registry - - hass.data[device_registry.DATA_REGISTRY] = \ - hass.loop.create_task(_get_reg()) - return registry +from tests.common import mock_device_registry @pytest.fixture def registry(hass): """Return an empty, loaded, registry.""" - return mock_registry(hass) + return mock_device_registry(hass) async def test_get_or_create_returns_same_entry(registry):