diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 3723e7090d2..65ef0ad3ac3 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -1,5 +1,6 @@ """Support for the Lovelace UI.""" +from dataclasses import dataclass import logging import voluptuous as vol @@ -29,6 +30,7 @@ from .const import ( # noqa: F401 DEFAULT_ICON, DOMAIN, EVENT_LOVELACE_UPDATED, + LOVELACE_DATA, MODE_STORAGE, MODE_YAML, RESOURCE_CREATE_FIELDS, @@ -73,6 +75,16 @@ CONFIG_SCHEMA = vol.Schema( ) +@dataclass +class LovelaceData: + """Dataclass to store information in hass.data.""" + + mode: str + dashboards: dict[str | None, dashboard.LovelaceConfig] + resources: resources.ResourceStorageCollection + yaml_dashboards: dict[str | None, ConfigType] + + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the Lovelace commands.""" mode = config[DOMAIN][CONF_MODE] @@ -100,7 +112,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: resource_collection = await create_yaml_resource_col( hass, config[DOMAIN].get(CONF_RESOURCES) ) - hass.data[DOMAIN]["resources"] = resource_collection + hass.data[LOVELACE_DATA].resources = resource_collection default_config: dashboard.LovelaceConfig if mode == MODE_YAML: @@ -151,13 +163,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass, websocket.websocket_lovelace_delete_config ) - hass.data[DOMAIN] = { + hass.data[LOVELACE_DATA] = LovelaceData( + mode=mode, # We store a dictionary mapping url_path: config. None is the default. - "mode": mode, - "dashboards": {None: default_config}, - "resources": resource_collection, - "yaml_dashboards": config[DOMAIN].get(CONF_DASHBOARDS, {}), - } + dashboards={None: default_config}, + resources=resource_collection, + yaml_dashboards=config[DOMAIN].get(CONF_DASHBOARDS, {}), + ) if hass.config.recovery_mode: return True @@ -168,11 +180,11 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if change_type == collection.CHANGE_REMOVED: frontend.async_remove_panel(hass, url_path) - await hass.data[DOMAIN]["dashboards"].pop(url_path).async_delete() + await hass.data[LOVELACE_DATA].dashboards.pop(url_path).async_delete() return if change_type == collection.CHANGE_ADDED: - existing = hass.data[DOMAIN]["dashboards"].get(url_path) + existing = hass.data[LOVELACE_DATA].dashboards.get(url_path) if existing: _LOGGER.warning( @@ -182,13 +194,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ) return - hass.data[DOMAIN]["dashboards"][url_path] = dashboard.LovelaceStorage( + hass.data[LOVELACE_DATA].dashboards[url_path] = dashboard.LovelaceStorage( hass, item ) update = False else: - hass.data[DOMAIN]["dashboards"][url_path].config = item + hass.data[LOVELACE_DATA].dashboards[url_path].config = item update = True try: @@ -197,10 +209,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: _LOGGER.warning("Failed to %s panel %s from storage", change_type, url_path) # Process YAML dashboards - for url_path, dashboard_conf in hass.data[DOMAIN]["yaml_dashboards"].items(): + for url_path, dashboard_conf in hass.data[LOVELACE_DATA].yaml_dashboards.items(): # For now always mode=yaml lovelace_config = dashboard.LovelaceYAML(hass, url_path, dashboard_conf) - hass.data[DOMAIN]["dashboards"][url_path] = lovelace_config + hass.data[LOVELACE_DATA].dashboards[url_path] = lovelace_config try: _register_panel(hass, url_path, MODE_YAML, dashboard_conf, False) diff --git a/homeassistant/components/lovelace/cast.py b/homeassistant/components/lovelace/cast.py index c380a296fc0..635425ba3dc 100644 --- a/homeassistant/components/lovelace/cast.py +++ b/homeassistant/components/lovelace/cast.py @@ -2,6 +2,8 @@ from __future__ import annotations +from typing import Any + from pychromecast import Chromecast from pychromecast.const import CAST_TYPE_CHROMECAST @@ -23,8 +25,7 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.network import NoURLAvailableError, get_url -from .const import DOMAIN, ConfigNotFound -from .dashboard import LovelaceConfig +from .const import DOMAIN, LOVELACE_DATA, ConfigNotFound DEFAULT_DASHBOARD = "_default_" @@ -76,7 +77,7 @@ async def async_browse_media( can_expand=False, ) ] - for url_path in hass.data[DOMAIN]["dashboards"]: + for url_path in hass.data[LOVELACE_DATA].dashboards: if url_path is None: continue @@ -151,11 +152,13 @@ async def async_play_media( return True -async def _get_dashboard_info(hass, url_path): +async def _get_dashboard_info( + hass: HomeAssistant, url_path: str | None +) -> dict[str, Any]: """Load a dashboard and return info on views.""" if url_path == DEFAULT_DASHBOARD: url_path = None - dashboard: LovelaceConfig | None = hass.data[DOMAIN]["dashboards"].get(url_path) + dashboard = hass.data[LOVELACE_DATA].dashboards.get(url_path) if dashboard is None: raise ValueError("Invalid dashboard specified") @@ -172,7 +175,7 @@ async def _get_dashboard_info(hass, url_path): url_path = dashboard.url_path title = config.get("title", url_path) if config else url_path - views = [] + views: list[dict[str, Any]] = [] data = { "title": title, "url_path": url_path, diff --git a/homeassistant/components/lovelace/const.py b/homeassistant/components/lovelace/const.py index 86f47fe2b5c..0bf5973e03d 100644 --- a/homeassistant/components/lovelace/const.py +++ b/homeassistant/components/lovelace/const.py @@ -1,6 +1,8 @@ """Constants for Lovelace.""" -from typing import Any +from __future__ import annotations + +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -15,8 +17,13 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.typing import VolDictType from homeassistant.util import slugify +from homeassistant.util.hass_dict import HassKey + +if TYPE_CHECKING: + from . import LovelaceData DOMAIN = "lovelace" +LOVELACE_DATA: HassKey[LovelaceData] = HassKey(DOMAIN) DEFAULT_ICON = "hass:view-dashboard" diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 411bbae9153..25e15d524c8 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -27,6 +27,7 @@ from .const import ( DOMAIN, EVENT_LOVELACE_UPDATED, LOVELACE_CONFIG_FILE, + LOVELACE_DATA, MODE_STORAGE, MODE_YAML, STORAGE_DASHBOARD_CREATE_FIELDS, @@ -315,7 +316,7 @@ class DashboardsCollectionWebSocket(collection.DictStorageCollectionWebsocket): msg["id"], [ dashboard.config - for dashboard in hass.data[DOMAIN]["dashboards"].values() + for dashboard in hass.data[LOVELACE_DATA].dashboards.values() if dashboard.config ], ) diff --git a/homeassistant/components/lovelace/system_health.py b/homeassistant/components/lovelace/system_health.py index 1e703768ae6..b629614d10d 100644 --- a/homeassistant/components/lovelace/system_health.py +++ b/homeassistant/components/lovelace/system_health.py @@ -1,12 +1,13 @@ """Provide info to system health.""" import asyncio +from typing import Any from homeassistant.components import system_health from homeassistant.const import CONF_MODE from homeassistant.core import HomeAssistant, callback -from .const import DOMAIN, MODE_AUTO, MODE_STORAGE, MODE_YAML +from .const import LOVELACE_DATA, MODE_AUTO, MODE_STORAGE, MODE_YAML @callback @@ -17,15 +18,17 @@ def async_register( register.async_register_info(system_health_info, "/config/lovelace") -async def system_health_info(hass): +async def system_health_info(hass: HomeAssistant) -> dict[str, Any]: """Get info for the info page.""" - health_info = {"dashboards": len(hass.data[DOMAIN]["dashboards"])} - health_info.update(await hass.data[DOMAIN]["resources"].async_get_info()) + health_info: dict[str, Any] = { + "dashboards": len(hass.data[LOVELACE_DATA].dashboards) + } + health_info.update(await hass.data[LOVELACE_DATA].resources.async_get_info()) dashboards_info = await asyncio.gather( *( - hass.data[DOMAIN]["dashboards"][dashboard].async_get_info() - for dashboard in hass.data[DOMAIN]["dashboards"] + hass.data[LOVELACE_DATA].dashboards[dashboard].async_get_info() + for dashboard in hass.data[LOVELACE_DATA].dashboards ) ) @@ -39,7 +42,7 @@ async def system_health_info(hass): else: health_info[key] = dashboard[key] - if hass.data[DOMAIN][CONF_MODE] == MODE_YAML: + if hass.data[LOVELACE_DATA].mode == MODE_YAML: health_info[CONF_MODE] = MODE_YAML elif MODE_STORAGE in modes: health_info[CONF_MODE] = MODE_STORAGE diff --git a/homeassistant/components/lovelace/websocket.py b/homeassistant/components/lovelace/websocket.py index e402ba92f16..7424f551e7a 100644 --- a/homeassistant/components/lovelace/websocket.py +++ b/homeassistant/components/lovelace/websocket.py @@ -13,7 +13,7 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import config_validation as cv from homeassistant.helpers.json import json_fragment -from .const import CONF_URL_PATH, DOMAIN, ConfigNotFound +from .const import CONF_URL_PATH, LOVELACE_DATA, ConfigNotFound from .dashboard import LovelaceStorage @@ -27,7 +27,7 @@ def _handle_errors(func): msg: dict[str, Any], ) -> None: url_path = msg.get(CONF_URL_PATH) - config: LovelaceStorage | None = hass.data[DOMAIN]["dashboards"].get(url_path) + config = hass.data[LOVELACE_DATA].dashboards.get(url_path) if config is None: connection.send_error( @@ -74,7 +74,7 @@ async def websocket_lovelace_resources_impl( This function is called by both Storage and YAML mode WS handlers. """ - resources = hass.data[DOMAIN]["resources"] + resources = hass.data[LOVELACE_DATA].resources if hass.config.safe_mode: connection.send_result(msg["id"], [])