Remove create_list from StorageCollectionWebsocket.async_setup (#119508)

This commit is contained in:
Erik Montnemery 2024-06-17 12:16:36 +02:00 committed by GitHub
parent 0ae4903686
commit e0378f79a4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 142 additions and 69 deletions

View File

@ -1609,11 +1609,10 @@ class PipelineStorageCollectionWebsocket(
self, self,
hass: HomeAssistant, hass: HomeAssistant,
*, *,
create_list: bool = True,
create_create: bool = True, create_create: bool = True,
) -> None: ) -> None:
"""Set up the websocket commands.""" """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( websocket_api.async_register_command(
hass, hass,

View File

@ -115,6 +115,17 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
reload_resources_service_handler, reload_resources_service_handler,
schema=RESOURCE_RELOAD_SERVICE_SCHEMA, 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: else:
default_config = dashboard.LovelaceStorage(hass, None) 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) resource_collection = resources.ResourceStorageCollection(hass, default_config)
collection.DictStorageCollectionWebsocket( resources.ResourceStorageCollectionWebsocket(
resource_collection, resource_collection,
"lovelace/resources", "lovelace/resources",
"resource", "resource",
RESOURCE_CREATE_FIELDS, RESOURCE_CREATE_FIELDS,
RESOURCE_UPDATE_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_config)
websocket_api.async_register_command(hass, websocket.websocket_lovelace_save_config) websocket_api.async_register_command(hass, websocket.websocket_lovelace_save_config)
websocket_api.async_register_command( websocket_api.async_register_command(
hass, websocket.websocket_lovelace_delete_config 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] = { hass.data[DOMAIN] = {
# We store a dictionary mapping url_path: config. None is the default. # 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) dashboards_collection.async_add_listener(storage_dashboard_changed)
await dashboards_collection.async_load() await dashboards_collection.async_load()
collection.DictStorageCollectionWebsocket( dashboard.DashboardsCollectionWebSocket(
dashboards_collection, dashboards_collection,
"lovelace/dashboards", "lovelace/dashboards",
"dashboard", "dashboard",
STORAGE_DASHBOARD_CREATE_FIELDS, STORAGE_DASHBOARD_CREATE_FIELDS,
STORAGE_DASHBOARD_UPDATE_FIELDS, STORAGE_DASHBOARD_UPDATE_FIELDS,
).async_setup(hass, create_list=False) ).async_setup(hass)
def create_map_dashboard(): def create_map_dashboard():
hass.async_create_task(_create_map_dashboard(hass)) hass.async_create_task(_create_map_dashboard(hass))

View File

@ -11,6 +11,7 @@ from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.components.frontend import DATA_PANELS from homeassistant.components.frontend import DATA_PANELS
from homeassistant.const import CONF_FILENAME from homeassistant.const import CONF_FILENAME
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -297,3 +298,24 @@ class DashboardsCollection(collection.DictStorageCollection):
updated.pop(CONF_ICON) updated.pop(CONF_ICON)
return updated 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
],
)

View File

@ -8,6 +8,7 @@ import uuid
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.const import CONF_ID, CONF_RESOURCES, CONF_TYPE from homeassistant.const import CONF_ID, CONF_RESOURCES, CONF_TYPE
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
@ -21,6 +22,7 @@ from .const import (
RESOURCE_UPDATE_FIELDS, RESOURCE_UPDATE_FIELDS,
) )
from .dashboard import LovelaceConfig from .dashboard import LovelaceConfig
from .websocket import websocket_lovelace_resources_impl
RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources" RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources"
RESOURCES_STORAGE_VERSION = 1 RESOURCES_STORAGE_VERSION = 1
@ -125,3 +127,38 @@ class ResourceStorageCollection(collection.DictStorageCollection):
update_data[CONF_TYPE] = update_data.pop(CONF_RESOURCE_TYPE_WS) update_data[CONF_TYPE] = update_data.pop(CONF_RESOURCE_TYPE_WS)
return {**item, **update_data} 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)

View File

@ -8,7 +8,7 @@ from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.json import json_fragment from homeassistant.helpers.json import json_fragment
@ -52,14 +52,28 @@ def _handle_errors(func):
return send_with_error_handling return send_with_error_handling
@websocket_api.websocket_command({"type": "lovelace/resources"})
@websocket_api.async_response @websocket_api.async_response
async def websocket_lovelace_resources( async def websocket_lovelace_resources(
hass: HomeAssistant, hass: HomeAssistant,
connection: websocket_api.ActiveConnection, connection: websocket_api.ActiveConnection,
msg: dict[str, Any], msg: dict[str, Any],
) -> None: ) -> 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"] resources = hass.data[DOMAIN]["resources"]
if hass.config.safe_mode: if hass.config.safe_mode:
@ -129,21 +143,3 @@ async def websocket_lovelace_delete_config(
) -> None: ) -> None:
"""Delete Lovelace UI configuration.""" """Delete Lovelace UI configuration."""
await config.async_delete() 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
],
)

View File

@ -24,7 +24,6 @@ from homeassistant.const import (
ATTR_NAME, ATTR_NAME,
CONF_ID, CONF_ID,
CONF_NAME, CONF_NAME,
CONF_TYPE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_START,
SERVICE_RELOAD, SERVICE_RELOAD,
STATE_HOME, STATE_HOME,
@ -307,6 +306,23 @@ class PersonStorageCollection(collection.DictStorageCollection):
raise ValueError("User already taken") 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]: async def filter_yaml_data(hass: HomeAssistant, persons: list[dict]) -> list[dict]:
"""Validate YAML data that we can't validate via schema.""" """Validate YAML data that we can't validate via schema."""
filtered = [] filtered = []
@ -370,11 +386,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.data[DOMAIN] = (yaml_collection, storage_collection, entity_component) hass.data[DOMAIN] = (yaml_collection, storage_collection, entity_component)
collection.DictStorageCollectionWebsocket( PersonStorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
).async_setup(hass, create_list=False) ).async_setup(hass)
websocket_api.async_register_command(hass, ws_list_person)
async def _handle_user_removed(event: Event) -> None: async def _handle_user_removed(event: Event) -> None:
"""Handle a user being removed.""" """Handle a user being removed."""
@ -570,19 +584,6 @@ class Person(
self._attr_extra_state_attributes = data 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: def _get_latest(prev: State | None, curr: State) -> State:
"""Get latest state.""" """Get latest state."""
if prev is None or curr.last_updated > prev.last_updated: if prev is None or curr.last_updated > prev.last_updated:

View File

@ -537,19 +537,17 @@ class StorageCollectionWebsocket[_StorageCollectionT: StorageCollection]:
self, self,
hass: HomeAssistant, hass: HomeAssistant,
*, *,
create_list: bool = True,
create_create: bool = True, create_create: bool = True,
) -> None: ) -> None:
"""Set up the websocket commands.""" """Set up the websocket commands."""
if create_list: websocket_api.async_register_command(
websocket_api.async_register_command( hass,
hass, f"{self.api_prefix}/list",
f"{self.api_prefix}/list", self.ws_list_item,
self.ws_list_item, websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( {vol.Required("type"): f"{self.api_prefix}/list"}
{vol.Required("type"): f"{self.api_prefix}/list"} ),
), )
)
if create_create: if create_create:
websocket_api.async_register_command( websocket_api.async_register_command(

View File

@ -5,6 +5,8 @@ from typing import Any
from unittest.mock import patch from unittest.mock import patch
import uuid import uuid
import pytest
from homeassistant.components.lovelace import dashboard, resources from homeassistant.components.lovelace import dashboard, resources
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component 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( async def test_yaml_resources(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator, list_cmd: str
) -> None: ) -> None:
"""Test defining resources in configuration.yaml.""" """Test defining resources in configuration.yaml."""
assert await async_setup_component( assert await async_setup_component(
@ -28,14 +31,15 @@ async def test_yaml_resources(
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert response["result"] == RESOURCE_EXAMPLES assert response["result"] == RESOURCE_EXAMPLES
@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"])
async def test_yaml_resources_backwards( async def test_yaml_resources_backwards(
hass: HomeAssistant, hass_ws_client: WebSocketGenerator hass: HomeAssistant, hass_ws_client: WebSocketGenerator, list_cmd: str
) -> None: ) -> None:
"""Test defining resources in YAML ll config (legacy).""" """Test defining resources in YAML ll config (legacy)."""
with patch( with patch(
@ -49,16 +53,18 @@ async def test_yaml_resources_backwards(
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert response["result"] == RESOURCE_EXAMPLES assert response["result"] == RESOURCE_EXAMPLES
@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"])
async def test_storage_resources( async def test_storage_resources(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
hass_storage: dict[str, Any], hass_storage: dict[str, Any],
list_cmd: str,
) -> None: ) -> None:
"""Test defining resources in storage config.""" """Test defining resources in storage config."""
resource_config = [{**item, "id": uuid.uuid4().hex} for item in RESOURCE_EXAMPLES] 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) client = await hass_ws_client(hass)
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert response["result"] == resource_config assert response["result"] == resource_config
@pytest.mark.parametrize("list_cmd", ["lovelace/resources", "lovelace/resources/list"])
async def test_storage_resources_import( async def test_storage_resources_import(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
hass_storage: dict[str, Any], hass_storage: dict[str, Any],
list_cmd: str,
) -> None: ) -> None:
"""Test importing resources from storage config.""" """Test importing resources from storage config."""
assert await async_setup_component(hass, "lovelace", {}) assert await async_setup_component(hass, "lovelace", {})
@ -94,7 +102,7 @@ async def test_storage_resources_import(
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert ( assert (
@ -118,7 +126,7 @@ async def test_storage_resources_import(
response = await client.receive_json() response = await client.receive_json()
assert response["success"] 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() response = await client.receive_json()
assert response["success"] assert response["success"]
@ -141,7 +149,7 @@ async def test_storage_resources_import(
response = await client.receive_json() response = await client.receive_json()
assert response["success"] 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() response = await client.receive_json()
assert response["success"] assert response["success"]
@ -160,7 +168,7 @@ async def test_storage_resources_import(
response = await client.receive_json() response = await client.receive_json()
assert response["success"] 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() response = await client.receive_json()
assert response["success"] 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"]) 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( async def test_storage_resources_import_invalid(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
hass_storage: dict[str, Any], hass_storage: dict[str, Any],
list_cmd: str,
) -> None: ) -> None:
"""Test importing resources from storage config.""" """Test importing resources from storage config."""
assert await async_setup_component(hass, "lovelace", {}) assert await async_setup_component(hass, "lovelace", {})
@ -184,7 +194,7 @@ async def test_storage_resources_import_invalid(
client = await hass_ws_client(hass) client = await hass_ws_client(hass)
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert response["result"] == [] 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( async def test_storage_resources_safe_mode(
hass: HomeAssistant, hass: HomeAssistant,
hass_ws_client: WebSocketGenerator, hass_ws_client: WebSocketGenerator,
hass_storage: dict[str, Any], hass_storage: dict[str, Any],
list_cmd: str,
) -> None: ) -> None:
"""Test defining resources in storage config.""" """Test defining resources in storage config."""
@ -213,7 +225,7 @@ async def test_storage_resources_safe_mode(
hass.config.safe_mode = True hass.config.safe_mode = True
# Fetch data # 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() response = await client.receive_json()
assert response["success"] assert response["success"]
assert response["result"] == [] assert response["result"] == []