Enable strict-typing in lovelace (#136327)

This commit is contained in:
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.logger.*
homeassistant.components.london_underground.* homeassistant.components.london_underground.*
homeassistant.components.lookin.* homeassistant.components.lookin.*
homeassistant.components.lovelace.*
homeassistant.components.luftdaten.* homeassistant.components.luftdaten.*
homeassistant.components.madvr.* homeassistant.components.madvr.*
homeassistant.components.manual.* homeassistant.components.manual.*

View File

@ -81,7 +81,7 @@ class LovelaceData:
mode: str mode: str
dashboards: dict[str | None, dashboard.LovelaceConfig] dashboards: dict[str | None, dashboard.LovelaceConfig]
resources: resources.ResourceStorageCollection resources: resources.ResourceYAMLCollection | resources.ResourceStorageCollection
yaml_dashboards: dict[str | None, ConfigType] 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 hass.data[LOVELACE_DATA].resources = resource_collection
default_config: dashboard.LovelaceConfig default_config: dashboard.LovelaceConfig
resource_collection: (
resources.ResourceYAMLCollection | resources.ResourceStorageCollection
)
if mode == MODE_YAML: if mode == MODE_YAML:
default_config = dashboard.LovelaceYAML(hass, None, None) default_config = dashboard.LovelaceYAML(hass, None, None)
resource_collection = await create_yaml_resource_col(hass, yaml_resources) 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: if hass.config.recovery_mode:
return True 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.""" """Handle a storage dashboard change."""
url_path = item[CONF_URL_PATH] url_path = item[CONF_URL_PATH]
@ -236,7 +241,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True 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.""" """Create yaml resources collection."""
if yaml_resources is None: if yaml_resources is None:
default_config = dashboard.LovelaceYAML(hass, None, None) default_config = dashboard.LovelaceYAML(hass, None, None)
@ -256,7 +263,9 @@ async def create_yaml_resource_col(hass, yaml_resources):
@callback @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.""" """Register a panel."""
kwargs = { kwargs = {
"frontend_url_path": url_path, "frontend_url_path": url_path,

View File

@ -7,7 +7,7 @@ import logging
import os import os
from pathlib import Path from pathlib import Path
import time import time
from typing import Any from typing import TYPE_CHECKING, Any
import voluptuous as vol import voluptuous as vol
@ -67,21 +67,25 @@ class LovelaceConfig(ABC):
"""Return mode of the lovelace config.""" """Return mode of the lovelace config."""
@abstractmethod @abstractmethod
async def async_get_info(self): async def async_get_info(self) -> dict[str, Any]:
"""Return the config info.""" """Return the config info."""
@abstractmethod @abstractmethod
async def async_load(self, force: bool) -> dict[str, Any]: async def async_load(self, force: bool) -> dict[str, Any]:
"""Load config.""" """Load config."""
async def async_save(self, config): async def async_save(self, config: dict[str, Any]) -> None:
"""Save config.""" """Save config."""
raise HomeAssistantError("Not supported") raise HomeAssistantError("Not supported")
async def async_delete(self): async def async_delete(self) -> None:
"""Delete config.""" """Delete config."""
raise HomeAssistantError("Not supported") raise HomeAssistantError("Not supported")
@abstractmethod
async def async_json(self, force: bool) -> json_fragment:
"""Return JSON representation of the config."""
@callback @callback
def _config_updated(self) -> None: def _config_updated(self) -> None:
"""Fire config updated event.""" """Fire config updated event."""
@ -113,7 +117,7 @@ class LovelaceStorage(LovelaceConfig):
"""Return mode of the lovelace config.""" """Return mode of the lovelace config."""
return MODE_STORAGE return MODE_STORAGE
async def async_get_info(self): async def async_get_info(self) -> dict[str, Any]:
"""Return the Lovelace storage info.""" """Return the Lovelace storage info."""
data = self._data or await self._load() data = self._data or await self._load()
if data["config"] is None: if data["config"] is None:
@ -129,7 +133,7 @@ class LovelaceStorage(LovelaceConfig):
if (config := data["config"]) is None: if (config := data["config"]) is None:
raise ConfigNotFound raise ConfigNotFound
return config return config # type: ignore[no-any-return]
async def async_json(self, force: bool) -> json_fragment: async def async_json(self, force: bool) -> json_fragment:
"""Return JSON representation of the config.""" """Return JSON representation of the config."""
@ -139,19 +143,21 @@ class LovelaceStorage(LovelaceConfig):
await self._load() await self._load()
return self._json_config or self._async_build_json() 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.""" """Save config."""
if self.hass.config.recovery_mode: if self.hass.config.recovery_mode:
raise HomeAssistantError("Saving not supported in recovery mode") raise HomeAssistantError("Saving not supported in recovery mode")
if self._data is None: if self._data is None:
await self._load() await self._load()
if TYPE_CHECKING:
assert self._data is not None
self._data["config"] = config self._data["config"] = config
self._json_config = None self._json_config = None
self._config_updated() self._config_updated()
await self._store.async_save(self._data) await self._store.async_save(self._data)
async def async_delete(self): async def async_delete(self) -> None:
"""Delete config.""" """Delete config."""
if self.hass.config.recovery_mode: if self.hass.config.recovery_mode:
raise HomeAssistantError("Deleting not supported in 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 of the lovelace config."""
return MODE_YAML return MODE_YAML
async def async_get_info(self): async def async_get_info(self) -> dict[str, Any]:
"""Return the YAML storage mode.""" """Return the YAML storage mode."""
try: try:
config = await self.async_load(False) config = await self.async_load(False)
@ -251,7 +257,7 @@ class LovelaceYAML(LovelaceConfig):
return is_updated, config, json 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.""" """Generate info about the config."""
return { return {
"mode": mode, "mode": mode,
@ -265,7 +271,7 @@ class DashboardsCollection(collection.DictStorageCollection):
CREATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_CREATE_FIELDS) CREATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_CREATE_FIELDS)
UPDATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_UPDATE_FIELDS) UPDATE_SCHEMA = vol.Schema(STORAGE_DASHBOARD_UPDATE_FIELDS)
def __init__(self, hass): def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the dashboards collection.""" """Initialize the dashboards collection."""
super().__init__( super().__init__(
storage.Store(hass, DASHBOARDS_STORAGE_VERSION, DASHBOARDS_STORAGE_KEY), 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]: if url_path in self.hass.data[DATA_PANELS]:
raise vol.Invalid("Panel url path needs to be unique") 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 @callback
def _get_suggested_id(self, info: dict) -> str: def _get_suggested_id(self, info: dict) -> str:
"""Suggest an ID based on the config.""" """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: async def _update_data(self, item: dict, update_data: dict) -> dict:
"""Return a new updated data object.""" """Return a new updated data object."""

View File

@ -34,11 +34,11 @@ class ResourceYAMLCollection:
loaded = True loaded = True
def __init__(self, data): def __init__(self, data: list[dict[str, Any]]) -> None:
"""Initialize a resource YAML collection.""" """Initialize a resource YAML collection."""
self.data = data 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 the resources info for YAML mode."""
return {"resources": len(self.async_items() or [])} return {"resources": len(self.async_items() or [])}
@ -62,7 +62,7 @@ class ResourceStorageCollection(collection.DictStorageCollection):
) )
self.ll_config = ll_config 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.""" """Return the resources info for YAML mode."""
if not self.loaded: if not self.loaded:
await self.async_load() await self.async_load()

View File

@ -2,8 +2,9 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import Awaitable, Callable
from functools import wraps from functools import wraps
from typing import Any from typing import TYPE_CHECKING, Any
import voluptuous as vol import voluptuous as vol
@ -14,10 +15,20 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.json import json_fragment from homeassistant.helpers.json import json_fragment
from .const import CONF_URL_PATH, LOVELACE_DATA, ConfigNotFound 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.""" """Handle error with WebSocket calls."""
@wraps(func) @wraps(func)
@ -75,6 +86,8 @@ async def websocket_lovelace_resources_impl(
This function is called by both Storage and YAML mode WS handlers. This function is called by both Storage and YAML mode WS handlers.
""" """
resources = hass.data[LOVELACE_DATA].resources resources = hass.data[LOVELACE_DATA].resources
if TYPE_CHECKING:
assert isinstance(resources, ResourceStorageCollection)
if hass.config.safe_mode: if hass.config.safe_mode:
connection.send_result(msg["id"], []) connection.send_result(msg["id"], [])
@ -100,7 +113,7 @@ async def websocket_lovelace_config(
hass: HomeAssistant, hass: HomeAssistant,
connection: websocket_api.ActiveConnection, connection: websocket_api.ActiveConnection,
msg: dict[str, Any], msg: dict[str, Any],
config: LovelaceStorage, config: LovelaceConfig,
) -> json_fragment: ) -> json_fragment:
"""Send Lovelace UI config over WebSocket connection.""" """Send Lovelace UI config over WebSocket connection."""
return await config.async_json(msg["force"]) return await config.async_json(msg["force"])
@ -120,7 +133,7 @@ async def websocket_lovelace_save_config(
hass: HomeAssistant, hass: HomeAssistant,
connection: websocket_api.ActiveConnection, connection: websocket_api.ActiveConnection,
msg: dict[str, Any], msg: dict[str, Any],
config: LovelaceStorage, config: LovelaceConfig,
) -> None: ) -> None:
"""Save Lovelace UI configuration.""" """Save Lovelace UI configuration."""
await config.async_save(msg["config"]) await config.async_save(msg["config"])
@ -139,7 +152,7 @@ async def websocket_lovelace_delete_config(
hass: HomeAssistant, hass: HomeAssistant,
connection: websocket_api.ActiveConnection, connection: websocket_api.ActiveConnection,
msg: dict[str, Any], msg: dict[str, Any],
config: LovelaceStorage, config: LovelaceConfig,
) -> None: ) -> None:
"""Delete Lovelace UI configuration.""" """Delete Lovelace UI configuration."""
await config.async_delete() await config.async_delete()

10
mypy.ini generated
View File

@ -2826,6 +2826,16 @@ disallow_untyped_defs = true
warn_return_any = true warn_return_any = true
warn_unreachable = 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.*] [mypy-homeassistant.components.luftdaten.*]
check_untyped_defs = true check_untyped_defs = true
disallow_incomplete_defs = true disallow_incomplete_defs = true