Enable strict-typing in lovelace (#136327)

pull/136311/head
epenet 2025-01-23 18:37:58 +01:00 committed by GitHub
parent 33ce795695
commit 83e826219a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 65 additions and 26 deletions

View File

@ -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.*

View File

@ -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,

View File

@ -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."""

View File

@ -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()

View File

@ -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()

10
mypy.ini generated
View File

@ -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