diff --git a/.strict-typing b/.strict-typing index 46b14f22660..ce1ea1a6838 100644 --- a/.strict-typing +++ b/.strict-typing @@ -307,6 +307,7 @@ homeassistant.components.logbook.* homeassistant.components.logger.* homeassistant.components.london_underground.* homeassistant.components.lookin.* +homeassistant.components.lovelace.* homeassistant.components.luftdaten.* homeassistant.components.madvr.* homeassistant.components.manual.* diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py index 65ef0ad3ac3..82f3987c630 100644 --- a/homeassistant/components/lovelace/__init__.py +++ b/homeassistant/components/lovelace/__init__.py @@ -81,7 +81,7 @@ class LovelaceData: mode: str dashboards: dict[str | None, dashboard.LovelaceConfig] - resources: resources.ResourceStorageCollection + resources: resources.ResourceYAMLCollection | resources.ResourceStorageCollection yaml_dashboards: dict[str | None, ConfigType] @@ -115,6 +115,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: hass.data[LOVELACE_DATA].resources = resource_collection default_config: dashboard.LovelaceConfig + resource_collection: ( + resources.ResourceYAMLCollection | resources.ResourceStorageCollection + ) if mode == MODE_YAML: default_config = dashboard.LovelaceYAML(hass, None, None) resource_collection = await create_yaml_resource_col(hass, yaml_resources) @@ -174,7 +177,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: if hass.config.recovery_mode: return True - async def storage_dashboard_changed(change_type, item_id, item): + async def storage_dashboard_changed( + change_type: str, item_id: str, item: dict + ) -> None: """Handle a storage dashboard change.""" url_path = item[CONF_URL_PATH] @@ -236,7 +241,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: return True -async def create_yaml_resource_col(hass, yaml_resources): +async def create_yaml_resource_col( + hass: HomeAssistant, yaml_resources: list[ConfigType] | None +) -> resources.ResourceYAMLCollection: """Create yaml resources collection.""" if yaml_resources is None: default_config = dashboard.LovelaceYAML(hass, None, None) @@ -256,7 +263,9 @@ async def create_yaml_resource_col(hass, yaml_resources): @callback -def _register_panel(hass, url_path, mode, config, update): +def _register_panel( + hass: HomeAssistant, url_path: str | None, mode: str, config: dict, update: bool +) -> None: """Register a panel.""" kwargs = { "frontend_url_path": url_path, diff --git a/homeassistant/components/lovelace/dashboard.py b/homeassistant/components/lovelace/dashboard.py index 25e15d524c8..ddb54e7618f 100644 --- a/homeassistant/components/lovelace/dashboard.py +++ b/homeassistant/components/lovelace/dashboard.py @@ -7,7 +7,7 @@ import logging import os from pathlib import Path import time -from typing import Any +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -67,21 +67,25 @@ class LovelaceConfig(ABC): """Return mode of the lovelace config.""" @abstractmethod - async def async_get_info(self): + async def async_get_info(self) -> dict[str, Any]: """Return the config info.""" @abstractmethod async def async_load(self, force: bool) -> dict[str, Any]: """Load config.""" - async def async_save(self, config): + async def async_save(self, config: dict[str, Any]) -> None: """Save config.""" raise HomeAssistantError("Not supported") - async def async_delete(self): + async def async_delete(self) -> None: """Delete config.""" raise HomeAssistantError("Not supported") + @abstractmethod + async def async_json(self, force: bool) -> json_fragment: + """Return JSON representation of the config.""" + @callback def _config_updated(self) -> None: """Fire config updated event.""" @@ -113,7 +117,7 @@ class LovelaceStorage(LovelaceConfig): """Return mode of the lovelace config.""" return MODE_STORAGE - async def async_get_info(self): + async def async_get_info(self) -> dict[str, Any]: """Return the Lovelace storage info.""" data = self._data or await self._load() if data["config"] is None: @@ -129,7 +133,7 @@ class LovelaceStorage(LovelaceConfig): if (config := data["config"]) is None: raise ConfigNotFound - return config + return config # type: ignore[no-any-return] async def async_json(self, force: bool) -> json_fragment: """Return JSON representation of the config.""" @@ -139,19 +143,21 @@ class LovelaceStorage(LovelaceConfig): await self._load() return self._json_config or self._async_build_json() - async def async_save(self, config): + async def async_save(self, config: dict[str, Any]) -> None: """Save config.""" if self.hass.config.recovery_mode: raise HomeAssistantError("Saving not supported in recovery mode") if self._data is None: await self._load() + if TYPE_CHECKING: + assert self._data is not None self._data["config"] = config self._json_config = None self._config_updated() await self._store.async_save(self._data) - async def async_delete(self): + async def async_delete(self) -> None: """Delete config.""" if self.hass.config.recovery_mode: raise HomeAssistantError("Deleting not supported in recovery mode") @@ -195,7 +201,7 @@ class LovelaceYAML(LovelaceConfig): """Return mode of the lovelace config.""" return MODE_YAML - async def async_get_info(self): + async def async_get_info(self) -> dict[str, Any]: """Return the YAML storage mode.""" try: config = await self.async_load(False) @@ -251,7 +257,7 @@ class LovelaceYAML(LovelaceConfig): return is_updated, config, json -def _config_info(mode, config): +def _config_info(mode: str, config: dict[str, Any]) -> dict[str, Any]: """Generate info about the config.""" return { "mode": mode, @@ -265,7 +271,7 @@ class DashboardsCollection(collection.DictStorageCollection): CREATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_CREATE_FIELDS) UPDATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_UPDATE_FIELDS) - def __init__(self, hass): + def __init__(self, hass: HomeAssistant) -> None: """Initialize the dashboards collection.""" super().__init__( storage.Store(hass, DASHBOARDS_STORAGE_VERSION, DASHBOARDS_STORAGE_KEY), @@ -283,12 +289,12 @@ class DashboardsCollection(collection.DictStorageCollection): if url_path in self.hass.data[DATA_PANELS]: raise vol.Invalid("Panel url path needs to be unique") - return self.CREATE_SCHEMA(data) + return self.CREATE_SCHEMA(data) # type: ignore[no-any-return] @callback def _get_suggested_id(self, info: dict) -> str: """Suggest an ID based on the config.""" - return info[CONF_URL_PATH] + return info[CONF_URL_PATH] # type: ignore[no-any-return] async def _update_data(self, item: dict, update_data: dict) -> dict: """Return a new updated data object.""" diff --git a/homeassistant/components/lovelace/resources.py b/homeassistant/components/lovelace/resources.py index 316a31e8e9d..96f84ccbc60 100644 --- a/homeassistant/components/lovelace/resources.py +++ b/homeassistant/components/lovelace/resources.py @@ -34,11 +34,11 @@ class ResourceYAMLCollection: loaded = True - def __init__(self, data): + def __init__(self, data: list[dict[str, Any]]) -> None: """Initialize a resource YAML collection.""" self.data = data - async def async_get_info(self): + async def async_get_info(self) -> dict[str, int]: """Return the resources info for YAML mode.""" return {"resources": len(self.async_items() or [])} @@ -62,7 +62,7 @@ class ResourceStorageCollection(collection.DictStorageCollection): ) self.ll_config = ll_config - async def async_get_info(self): + async def async_get_info(self) -> dict[str, int]: """Return the resources info for YAML mode.""" if not self.loaded: await self.async_load() diff --git a/homeassistant/components/lovelace/websocket.py b/homeassistant/components/lovelace/websocket.py index 7424f551e7a..5feb7deb449 100644 --- a/homeassistant/components/lovelace/websocket.py +++ b/homeassistant/components/lovelace/websocket.py @@ -2,8 +2,9 @@ from __future__ import annotations +from collections.abc import Awaitable, Callable from functools import wraps -from typing import Any +from typing import TYPE_CHECKING, Any import voluptuous as vol @@ -14,10 +15,20 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.json import json_fragment from .const import CONF_URL_PATH, LOVELACE_DATA, ConfigNotFound -from .dashboard import LovelaceStorage +from .dashboard import LovelaceConfig + +if TYPE_CHECKING: + from .resources import ResourceStorageCollection + +type AsyncLovelaceWebSocketCommandHandler[_R] = Callable[ + [HomeAssistant, websocket_api.ActiveConnection, dict[str, Any], LovelaceConfig], + Awaitable[_R], +] -def _handle_errors(func): +def _handle_errors[_R]( + func: AsyncLovelaceWebSocketCommandHandler[_R], +) -> websocket_api.AsyncWebSocketCommandHandler: """Handle error with WebSocket calls.""" @wraps(func) @@ -75,6 +86,8 @@ async def websocket_lovelace_resources_impl( This function is called by both Storage and YAML mode WS handlers. """ resources = hass.data[LOVELACE_DATA].resources + if TYPE_CHECKING: + assert isinstance(resources, ResourceStorageCollection) if hass.config.safe_mode: connection.send_result(msg["id"], []) @@ -100,7 +113,7 @@ async def websocket_lovelace_config( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any], - config: LovelaceStorage, + config: LovelaceConfig, ) -> json_fragment: """Send Lovelace UI config over WebSocket connection.""" return await config.async_json(msg["force"]) @@ -120,7 +133,7 @@ async def websocket_lovelace_save_config( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any], - config: LovelaceStorage, + config: LovelaceConfig, ) -> None: """Save Lovelace UI configuration.""" await config.async_save(msg["config"]) @@ -139,7 +152,7 @@ async def websocket_lovelace_delete_config( hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any], - config: LovelaceStorage, + config: LovelaceConfig, ) -> None: """Delete Lovelace UI configuration.""" await config.async_delete() diff --git a/mypy.ini b/mypy.ini index e4056203875..ccdc7c669d7 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2826,6 +2826,16 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true +[mypy-homeassistant.components.lovelace.*] +check_untyped_defs = true +disallow_incomplete_defs = true +disallow_subclassing_any = true +disallow_untyped_calls = true +disallow_untyped_decorators = true +disallow_untyped_defs = true +warn_return_any = true +warn_unreachable = true + [mypy-homeassistant.components.luftdaten.*] check_untyped_defs = true disallow_incomplete_defs = true