Home Assistant Unofficial Reference 2024.12.1
resources.py
Go to the documentation of this file.
1 """Lovelace resources support."""
2 
3 from __future__ import annotations
4 
5 import logging
6 from typing import Any
7 import uuid
8 
9 import voluptuous as vol
10 
11 from homeassistant.components import websocket_api
12 from homeassistant.const import CONF_ID, CONF_RESOURCES, CONF_TYPE
13 from homeassistant.core import HomeAssistant, callback
14 from homeassistant.exceptions import HomeAssistantError
15 from homeassistant.helpers import collection, storage
16 
17 from .const import (
18  CONF_RESOURCE_TYPE_WS,
19  DOMAIN,
20  RESOURCE_CREATE_FIELDS,
21  RESOURCE_SCHEMA,
22  RESOURCE_UPDATE_FIELDS,
23 )
24 from .dashboard import LovelaceConfig
25 from .websocket import websocket_lovelace_resources_impl
26 
27 RESOURCE_STORAGE_KEY = f"{DOMAIN}_resources"
28 RESOURCES_STORAGE_VERSION = 1
29 _LOGGER = logging.getLogger(__name__)
30 
31 
33  """Collection representing static YAML."""
34 
35  loaded = True
36 
37  def __init__(self, data):
38  """Initialize a resource YAML collection."""
39  self.datadata = data
40 
41  async def async_get_info(self):
42  """Return the resources info for YAML mode."""
43  return {"resources": len(self.async_itemsasync_items() or [])}
44 
45  @callback
46  def async_items(self) -> list[dict]:
47  """Return list of items in collection."""
48  return self.datadata
49 
50 
51 class ResourceStorageCollection(collection.DictStorageCollection):
52  """Collection to store resources."""
53 
54  loaded = False
55  CREATE_SCHEMA = vol.Schema(RESOURCE_CREATE_FIELDS)
56  UPDATE_SCHEMA = vol.Schema(RESOURCE_UPDATE_FIELDS)
57 
58  def __init__(self, hass: HomeAssistant, ll_config: LovelaceConfig) -> None:
59  """Initialize the storage collection."""
60  super().__init__(
61  storage.Store(hass, RESOURCES_STORAGE_VERSION, RESOURCE_STORAGE_KEY),
62  )
63  self.ll_configll_config = ll_config
64 
65  async def async_get_info(self):
66  """Return the resources info for YAML mode."""
67  if not self.loadedloadedloaded:
68  await self.async_load()
69  self.loadedloadedloaded = True
70 
71  return {"resources": len(self.async_items() or [])}
72 
73  async def _async_load_data(self) -> collection.SerializedStorageCollection | None:
74  """Load the data."""
75  if (store_data := await self.store.async_load()) is not None:
76  return store_data
77 
78  # Import it from config.
79  try:
80  conf = await self.ll_configll_config.async_load(False)
81  except HomeAssistantError:
82  return None
83 
84  if CONF_RESOURCES not in conf:
85  return None
86 
87  # Remove it from config and save both resources + config
88  resources: list[dict[str, Any]] = conf[CONF_RESOURCES]
89 
90  try:
91  vol.Schema([RESOURCE_SCHEMA])(resources)
92  except vol.Invalid as err:
93  _LOGGER.warning("Resource import failed. Data invalid: %s", err)
94  return None
95 
96  conf.pop(CONF_RESOURCES)
97 
98  for item in resources:
99  item[CONF_ID] = uuid.uuid4().hex
100 
101  data: collection.SerializedStorageCollection = {"items": resources}
102 
103  await self.store.async_save(data)
104  await self.ll_configll_config.async_save(conf)
105 
106  return data
107 
108  async def _process_create_data(self, data: dict) -> dict:
109  """Validate the config is valid."""
110  data = self.CREATE_SCHEMACREATE_SCHEMA(data)
111  data[CONF_TYPE] = data.pop(CONF_RESOURCE_TYPE_WS)
112  return data
113 
114  @callback
115  def _get_suggested_id(self, info: dict) -> str:
116  """Return unique ID."""
117  return uuid.uuid4().hex
118 
119  async def _update_data(self, item: dict, update_data: dict) -> dict:
120  """Return a new updated data object."""
121  if not self.loadedloadedloaded:
122  await self.async_load()
123  self.loadedloadedloaded = True
124 
125  update_data = self.UPDATE_SCHEMAUPDATE_SCHEMA(update_data)
126  if CONF_RESOURCE_TYPE_WS in update_data:
127  update_data[CONF_TYPE] = update_data.pop(CONF_RESOURCE_TYPE_WS)
128 
129  return {**item, **update_data}
130 
131 
132 class ResourceStorageCollectionWebsocket(collection.DictStorageCollectionWebsocket):
133  """Class to expose storage collection management over websocket."""
134 
135  @callback
136  def async_setup(self, hass: HomeAssistant) -> None:
137  """Set up the websocket commands."""
138  super().async_setup(hass)
139 
140  # Register lovelace/resources for backwards compatibility, remove in
141  # Home Assistant Core 2025.1
142  websocket_api.async_register_command(
143  hass,
144  self.api_prefix,
145  self.ws_list_itemws_list_item,
146  websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
147  {vol.Required("type"): f"{self.api_prefix}"}
148  ),
149  )
150 
151  @staticmethod
152  @websocket_api.async_response
153  async def ws_list_item(
154  hass: HomeAssistant,
155  connection: websocket_api.ActiveConnection,
156  msg: dict[str, Any],
157  ) -> None:
158  """Send Lovelace UI resources over WebSocket connection."""
159  await websocket_lovelace_resources_impl(hass, connection, msg)
None ws_list_item(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Definition: resources.py:157
None __init__(self, HomeAssistant hass, LovelaceConfig ll_config)
Definition: resources.py:58
collection.SerializedStorageCollection|None _async_load_data(self)
Definition: resources.py:73
None websocket_lovelace_resources_impl(HomeAssistant hass, websocket_api.ActiveConnection connection, dict[str, Any] msg)
Definition: websocket.py:72
None async_load(HomeAssistant hass)
None async_save(self, _T data)
Definition: storage.py:424