From e0378f79a450a76af3b41e109ea8ebad03cc5c1a Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 17 Jun 2024 12:16:36 +0200 Subject: [PATCH] Remove create_list from StorageCollectionWebsocket.async_setup (#119508) --- .../components/assist_pipeline/pipeline.py | 3 +- homeassistant/components/lovelace/__init__.py | 22 +++++++---- .../components/lovelace/dashboard.py | 22 +++++++++++ .../components/lovelace/resources.py | 37 ++++++++++++++++++ .../components/lovelace/websocket.py | 38 +++++++++---------- homeassistant/components/person/__init__.py | 37 +++++++++--------- homeassistant/helpers/collection.py | 18 ++++----- tests/components/lovelace/test_resources.py | 34 +++++++++++------ 8 files changed, 142 insertions(+), 69 deletions(-) diff --git a/homeassistant/components/assist_pipeline/pipeline.py b/homeassistant/components/assist_pipeline/pipeline.py index 1471af2ea41..ff360676cf7 100644 --- a/homeassistant/components/assist_pipeline/pipeline.py +++ b/homeassistant/components/assist_pipeline/pipeline.py @@ -1609,11 +1609,10 @@ class PipelineStorageCollectionWebsocket( self, hass: HomeAssistant, *, - create_list: bool = True, create_create: bool = True, ) -> None: """Set up the websocket commands.""" - super().async_setup(hass, create_list=create_list, create_create=create_create) + super().async_setup(hass, create_create=create_create) websocket_api.async_register_command( hass, diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 60d03717be0..d26e4f1d2d7 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -115,6 +115,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: reload_resources_service_handler, schema=RESOURCE_RELOAD_SERVICE_SCHEMA, ) + # Register lovelace/resources for backwards compatibility, remove in + # Home Assistant Core 2025.1 + for command in ("lovelace/resources", "lovelace/resources/list"): + websocket_api.async_register_command( + hass, + command, + websocket.websocket_lovelace_resources, + websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {"type": command}, + ), + ) else: default_config = dashboard.LovelaceStorage(hass, None) @@ -127,22 +138,19 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: resource_collection = resources.ResourceStorageCollection(hass, default_config) - collection.DictStorageCollectionWebsocket( + resources.ResourceStorageCollectionWebsocket( resource_collection, "lovelace/resources", "resource", RESOURCE_CREATE_FIELDS, RESOURCE_UPDATE_FIELDS, - ).async_setup(hass, create_list=False) + ).async_setup(hass) websocket_api.async_register_command(hass, websocket.websocket_lovelace_config) websocket_api.async_register_command(hass, websocket.websocket_lovelace_save_config) websocket_api.async_register_command( hass, websocket.websocket_lovelace_delete_config ) - websocket_api.async_register_command(hass, websocket.websocket_lovelace_resources) - - websocket_api.async_register_command(hass, websocket.websocket_lovelace_dashboards) hass.data[DOMAIN] = { # We store a dictionary mapping url_path: config. None is the default. @@ -209,13 +217,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: dashboards_collection.async_add_listener(storage_dashboard_changed) await dashboards_collection.async_load() - collection.DictStorageCollectionWebsocket( + dashboard.DashboardsCollectionWebSocket( dashboards_collection, "lovelace/dashboards", "dashboard", STORAGE_DASHBOARD_CREATE_FIELDS, STORAGE_DASHBOARD_UPDATE_FIELDS, - ).async_setup(hass, create_list=False) + ).async_setup(hass) def create_map_dashboard(): hass.async_create_task(_create_map_dashboard(hass)) diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index ef2b3075b34..db6db2fa7ef 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -11,6 +11,7 @@ from typing import Any import voluptuous as vol +from homeassistant.components import websocket_api from homeassistant.components.frontend import DATA_PANELS from homeassistant.const import CONF_FILENAME from homeassistant.core import HomeAssistant, callback @@ -297,3 +298,24 @@ class DashboardsCollection(collection.DictStorageCollection): updated.pop(CONF_ICON) return updated + + +class DashboardsCollectionWebSocket(collection.DictStorageCollectionWebsocket): + """Class to expose storage collection management over websocket.""" + + @callback + def ws_list_item( + self, + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], + ) -> None: + """Send Lovelace UI resources over WebSocket connection.""" + connection.send_result( + msg["id"], + [ + dashboard.config + for dashboard in hass.data[DOMAIN]["dashboards"].values() + if dashboard.config + ], + ) diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index 2dbbbacabea..c25c81e2c6f 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -8,6 +8,7 @@ import uuid import voluptuous as vol +from homeassistant.components import websocket_api from homeassistant.const import CONF_ID, CONF_RESOURCES, CONF_TYPE from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError @@ -21,6 +22,7 @@ from .const import ( RESOURCE_UPDATE_FIELDS, ) from .dashboard import LovelaceConfig +from .websocket import websocket_lovelace_resources_impl RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources" RESOURCES_STORAGE_VERSION = 1 @@ -125,3 +127,38 @@ class ResourceStorageCollection(collection.DictStorageCollection): update_data[CONF_TYPE] = update_data.pop(CONF_RESOURCE_TYPE_WS) return {**item, **update_data} + + +class ResourceStorageCollectionWebsocket(collection.DictStorageCollectionWebsocket): + """Class to expose storage collection management over websocket.""" + + @callback + def async_setup( + self, + hass: HomeAssistant, + *, + create_create: bool = True, + ) -> None: + """Set up the websocket commands.""" + super().async_setup(hass, create_create=create_create) + + # Register lovelace/resources for backwards compatibility, remove in + # Home Assistant Core 2025.1 + websocket_api.async_register_command( + hass, + self.api_prefix, + self.ws_list_item, + websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): f"{self.api_prefix}"} + ), + ) + + @staticmethod + @websocket_api.async_response + async def ws_list_item( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], + ) -> None: + """Send Lovelace UI resources over WebSocket connection.""" + await websocket_lovelace_resources_impl(hass, connection, msg) diff --git a/homeassistant/components/lovelace/websocket.py b/homeassistant/components/lovelace/websocket.py index 2aa55efafbd..e402ba92f16 100644 --- a/homeassistant/components/lovelace/websocket.py +++ b/homeassistant/components/lovelace/websocket.py @@ -8,7 +8,7 @@ from typing import Any import voluptuous as vol from homeassistant.components import websocket_api -from homeassistant.core import HomeAssistant, callback +from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.json import json_fragment @@ -52,14 +52,28 @@ def _handle_errors(func): return send_with_error_handling -@websocket_api.websocket_command({"type": "lovelace/resources"}) @websocket_api.async_response async def websocket_lovelace_resources( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any], ) -> None: - """Send Lovelace UI resources over WebSocket configuration.""" + """Send Lovelace UI resources over WebSocket connection. + + This function is used in YAML mode. + """ + await websocket_lovelace_resources_impl(hass, connection, msg) + + +async def websocket_lovelace_resources_impl( + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], +) -> None: + """Help send Lovelace UI resources over WebSocket connection. + + This function is called by both Storage and YAML mode WS handlers. + """ resources = hass.data[DOMAIN]["resources"] if hass.config.safe_mode: @@ -129,21 +143,3 @@ async def websocket_lovelace_delete_config( ) -> None: """Delete Lovelace UI configuration.""" await config.async_delete() - - -@websocket_api.websocket_command({"type": "lovelace/dashboards/list"}) -@callback -def websocket_lovelace_dashboards( - hass: HomeAssistant, - connection: websocket_api.ActiveConnection, - msg: dict[str, Any], -) -> None: - """Send Lovelace dashboard configuration.""" - connection.send_result( - msg["id"], - [ - dashboard.config - for dashboard in hass.data[DOMAIN]["dashboards"].values() - if dashboard.config - ], - ) diff --git a/homeassistant/components/person/__init__.py b/homeassistant/components/person/__init__.py index 175a206b38f..55c37f1c36c 100644 --- a/homeassistant/components/person/__init__.py +++ b/homeassistant/components/person/__init__.py @@ -24,7 +24,6 @@ from homeassistant.const import ( ATTR_NAME, CONF_ID, CONF_NAME, - CONF_TYPE, EVENT_HOMEASSISTANT_START, SERVICE_RELOAD, STATE_HOME, @@ -307,6 +306,23 @@ class PersonStorageCollection(collection.DictStorageCollection): raise ValueError("User already taken") +class PersonStorageCollectionWebsocket(collection.DictStorageCollectionWebsocket): + """Class to expose storage collection management over websocket.""" + + def ws_list_item( + self, + hass: HomeAssistant, + connection: websocket_api.ActiveConnection, + msg: dict[str, Any], + ) -> None: + """List persons.""" + yaml, storage, _ = hass.data[DOMAIN] + connection.send_result( + msg[ATTR_ID], + {"storage": storage.async_items(), "config": yaml.async_items()}, + ) + + async def filter_yaml_data(hass: HomeAssistant, persons: list[dict]) -> list[dict]: """Validate YAML data that we can't validate via schema.""" filtered = [] @@ -370,11 +386,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[DOMAIN] = (yaml_collection, storage_collection, entity_component) - collection.DictStorageCollectionWebsocket( + PersonStorageCollectionWebsocket( storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS - ).async_setup(hass, create_list=False) - - websocket_api.async_register_command(hass, ws_list_person) + ).async_setup(hass) async def _handle_user_removed(event: Event) -> None: """Handle a user being removed.""" @@ -570,19 +584,6 @@ class Person( self._attr_extra_state_attributes = data -@websocket_api.websocket_command({vol.Required(CONF_TYPE): "person/list"}) -def ws_list_person( - hass: HomeAssistant, - connection: websocket_api.ActiveConnection, - msg: dict[str, Any], -) -> None: - """List persons.""" - yaml, storage, _ = hass.data[DOMAIN] - connection.send_result( - msg[ATTR_ID], {"storage": storage.async_items(), "config": yaml.async_items()} - ) - - def _get_latest(prev: State | None, curr: State) -> State: """Get latest state.""" if prev is None or curr.last_updated > prev.last_updated: diff --git a/homeassistant/helpers/collection.py b/homeassistant/helpers/collection.py index 4691bc804fd..1ce4a9d092b 100644 --- a/homeassistant/helpers/collection.py +++ b/homeassistant/helpers/collection.py @@ -537,19 +537,17 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]: self, hass: HomeAssistant, *, - create_list: bool = True, create_create: bool = True, ) -> None: """Set up the websocket commands.""" - if create_list: - websocket_api.async_register_command( - hass, - f"{self.api_prefix}/list", - self.ws_list_item, - websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( - {vol.Required("type"): f"{self.api_prefix}/list"} - ), - ) + websocket_api.async_register_command( + hass, + f"{self.api_prefix}/list", + self.ws_list_item, + websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): f"{self.api_prefix}/list"} + ), + ) if create_create: websocket_api.async_register_command( diff --git a/tests/components/lovelace/test_resources.py b/tests/components/lovelace/test_resources.py index d2008ce5d41..bf6b44f0950 100644 --- a/tests/components/lovelace/test_resources.py +++ b/tests/components/lovelace/test_resources.py @@ -5,6 +5,8 @@ from typing import Any from unittest.mock import patch import uuid +import pytest + from homeassistant.components.lovelace import dashboard, resources from homeassistant.core import HomeAssistant from homeassistant.setup import async_setup_component @@ -17,8 +19,9 @@ RESOURCE_EXAMPLES = [ ] +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_yaml_resources( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, hass_ws_client: WebSocketGenerator, list_cmd: str ) -> None: """Test defining resources in configuration.yaml.""" assert await async_setup_component( @@ -28,14 +31,15 @@ async def test_yaml_resources( client = await hass_ws_client(hass) # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert response["result"] == RESOURCE_EXAMPLES +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_yaml_resources_backwards( - hass: HomeAssistant, hass_ws_client: WebSocketGenerator + hass: HomeAssistant, hass_ws_client: WebSocketGenerator, list_cmd: str ) -> None: """Test defining resources in YAML ll config (legacy).""" with patch( @@ -49,16 +53,18 @@ async def test_yaml_resources_backwards( client = await hass_ws_client(hass) # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert response["result"] == RESOURCE_EXAMPLES +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_storage_resources( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_storage: dict[str, Any], + list_cmd: str, ) -> None: """Test defining resources in storage config.""" resource_config = [{**item, "id": uuid.uuid4().hex} for item in RESOURCE_EXAMPLES] @@ -72,16 +78,18 @@ async def test_storage_resources( client = await hass_ws_client(hass) # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert response["result"] == resource_config +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_storage_resources_import( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_storage: dict[str, Any], + list_cmd: str, ) -> None: """Test importing resources from storage config.""" assert await async_setup_component(hass, "lovelace", {}) @@ -94,7 +102,7 @@ async def test_storage_resources_import( client = await hass_ws_client(hass) # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert ( @@ -118,7 +126,7 @@ async def test_storage_resources_import( response = await client.receive_json() assert response["success"] - await client.send_json({"id": 7, "type": "lovelace/resources"}) + await client.send_json({"id": 7, "type": list_cmd}) response = await client.receive_json() assert response["success"] @@ -141,7 +149,7 @@ async def test_storage_resources_import( response = await client.receive_json() assert response["success"] - await client.send_json({"id": 9, "type": "lovelace/resources"}) + await client.send_json({"id": 9, "type": list_cmd}) response = await client.receive_json() assert response["success"] @@ -160,7 +168,7 @@ async def test_storage_resources_import( response = await client.receive_json() assert response["success"] - await client.send_json({"id": 11, "type": "lovelace/resources"}) + await client.send_json({"id": 11, "type": list_cmd}) response = await client.receive_json() assert response["success"] @@ -168,10 +176,12 @@ async def test_storage_resources_import( assert first_item["id"] not in (item["id"] for item in response["result"]) +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_storage_resources_import_invalid( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_storage: dict[str, Any], + list_cmd: str, ) -> None: """Test importing resources from storage config.""" assert await async_setup_component(hass, "lovelace", {}) @@ -184,7 +194,7 @@ async def test_storage_resources_import_invalid( client = await hass_ws_client(hass) # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert response["result"] == [] @@ -194,10 +204,12 @@ async def test_storage_resources_import_invalid( ) +@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"]) async def test_storage_resources_safe_mode( hass: HomeAssistant, hass_ws_client: WebSocketGenerator, hass_storage: dict[str, Any], + list_cmd: str, ) -> None: """Test defining resources in storage config.""" @@ -213,7 +225,7 @@ async def test_storage_resources_safe_mode( hass.config.safe_mode = True # Fetch data - await client.send_json({"id": 5, "type": "lovelace/resources"}) + await client.send_json({"id": 5, "type": list_cmd}) response = await client.receive_json() assert response["success"] assert response["result"] == []