Enable strict typing for config (#108023)

This commit is contained in:
Marc Mueller 2024-01-18 09:20:19 +01:00 committed by GitHub
parent 26cc6a5bb4
commit afcb7a26cd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 178 additions and 76 deletions

View file

@ -122,6 +122,7 @@ homeassistant.components.climate.*
homeassistant.components.cloud.* homeassistant.components.cloud.*
homeassistant.components.co2signal.* homeassistant.components.co2signal.*
homeassistant.components.command_line.* homeassistant.components.command_line.*
homeassistant.components.config.*
homeassistant.components.configurator.* homeassistant.components.configurator.*
homeassistant.components.counter.* homeassistant.components.counter.*
homeassistant.components.cover.* homeassistant.components.cover.*

View file

@ -1,9 +1,14 @@
"""Component to configure Home Assistant via an API.""" """Component to configure Home Assistant via an API."""
from __future__ import annotations
import asyncio import asyncio
from collections.abc import Callable, Coroutine
from http import HTTPStatus from http import HTTPStatus
import importlib import importlib
import os import os
from typing import Any, Generic, TypeVar, cast
from aiohttp import web
import voluptuous as vol import voluptuous as vol
from homeassistant.components import frontend from homeassistant.components import frontend
@ -16,6 +21,9 @@ from homeassistant.helpers.typing import ConfigType
from homeassistant.setup import ATTR_COMPONENT from homeassistant.setup import ATTR_COMPONENT
from homeassistant.util.file import write_utf8_file_atomic from homeassistant.util.file import write_utf8_file_atomic
from homeassistant.util.yaml import dump, load_yaml from homeassistant.util.yaml import dump, load_yaml
from homeassistant.util.yaml.loader import JSON_TYPE
_DataT = TypeVar("_DataT", dict[str, dict[str, Any]], list[dict[str, Any]])
DOMAIN = "config" DOMAIN = "config"
SECTIONS = ( SECTIONS = (
@ -42,7 +50,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass, "config", "config", "hass:cog", require_admin=True hass, "config", "config", "hass:cog", require_admin=True
) )
async def setup_panel(panel_name): async def setup_panel(panel_name: str) -> None:
"""Set up a panel.""" """Set up a panel."""
panel = importlib.import_module(f".{panel_name}", __name__) panel = importlib.import_module(f".{panel_name}", __name__)
@ -63,20 +71,24 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
return True return True
class BaseEditConfigView(HomeAssistantView): class BaseEditConfigView(HomeAssistantView, Generic[_DataT]):
"""Configure a Group endpoint.""" """Configure a Group endpoint."""
def __init__( def __init__(
self, self,
component, component: str,
config_type, config_type: str,
path, path: str,
key_schema, key_schema: Callable[[Any], str],
data_schema, data_schema: Callable[[dict[str, Any]], Any],
*, *,
post_write_hook=None, post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None,
data_validator=None, data_validator: Callable[
): [HomeAssistant, str, dict[str, Any]],
Coroutine[Any, Any, dict[str, Any] | None],
]
| None = None,
) -> None:
"""Initialize a config view.""" """Initialize a config view."""
self.url = f"/api/config/{component}/{config_type}/{{config_key}}" self.url = f"/api/config/{component}/{config_type}/{{config_key}}"
self.name = f"api:config:{component}:{config_type}" self.name = f"api:config:{component}:{config_type}"
@ -87,26 +99,36 @@ class BaseEditConfigView(HomeAssistantView):
self.data_validator = data_validator self.data_validator = data_validator
self.mutation_lock = asyncio.Lock() self.mutation_lock = asyncio.Lock()
def _empty_config(self): def _empty_config(self) -> _DataT:
"""Empty config if file not found.""" """Empty config if file not found."""
raise NotImplementedError raise NotImplementedError
def _get_value(self, hass, data, config_key): def _get_value(
self, hass: HomeAssistant, data: _DataT, config_key: str
) -> dict[str, Any] | None:
"""Get value.""" """Get value."""
raise NotImplementedError raise NotImplementedError
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: _DataT,
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
raise NotImplementedError raise NotImplementedError
def _delete_value(self, hass, data, config_key): def _delete_value(
self, hass: HomeAssistant, data: _DataT, config_key: str
) -> dict[str, Any] | None:
"""Delete value.""" """Delete value."""
raise NotImplementedError raise NotImplementedError
@require_admin @require_admin
async def get(self, request, config_key): async def get(self, request: web.Request, config_key: str) -> web.Response:
"""Fetch device specific config.""" """Fetch device specific config."""
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
async with self.mutation_lock: async with self.mutation_lock:
current = await self.read_config(hass) current = await self.read_config(hass)
value = self._get_value(hass, current, config_key) value = self._get_value(hass, current, config_key)
@ -117,7 +139,7 @@ class BaseEditConfigView(HomeAssistantView):
return self.json(value) return self.json(value)
@require_admin @require_admin
async def post(self, request, config_key): async def post(self, request: web.Request, config_key: str) -> web.Response:
"""Validate config and return results.""" """Validate config and return results."""
try: try:
data = await request.json() data = await request.json()
@ -129,7 +151,7 @@ class BaseEditConfigView(HomeAssistantView):
except vol.Invalid as err: except vol.Invalid as err:
return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST) return self.json_message(f"Key malformed: {err}", HTTPStatus.BAD_REQUEST)
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
try: try:
# We just validate, we don't store that data because # We just validate, we don't store that data because
@ -159,9 +181,9 @@ class BaseEditConfigView(HomeAssistantView):
return self.json({"result": "ok"}) return self.json({"result": "ok"})
@require_admin @require_admin
async def delete(self, request, config_key): async def delete(self, request: web.Request, config_key: str) -> web.Response:
"""Remove an entry.""" """Remove an entry."""
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
async with self.mutation_lock: async with self.mutation_lock:
current = await self.read_config(hass) current = await self.read_config(hass)
value = self._get_value(hass, current, config_key) value = self._get_value(hass, current, config_key)
@ -178,46 +200,64 @@ class BaseEditConfigView(HomeAssistantView):
return self.json({"result": "ok"}) return self.json({"result": "ok"})
async def read_config(self, hass): async def read_config(self, hass: HomeAssistant) -> _DataT:
"""Read the config.""" """Read the config."""
current = await hass.async_add_executor_job(_read, hass.config.path(self.path)) current = await hass.async_add_executor_job(_read, hass.config.path(self.path))
if not current: if not current:
current = self._empty_config() current = self._empty_config()
return current return cast(_DataT, current)
class EditKeyBasedConfigView(BaseEditConfigView): class EditKeyBasedConfigView(BaseEditConfigView[dict[str, dict[str, Any]]]):
"""Configure a list of entries.""" """Configure a list of entries."""
def _empty_config(self): def _empty_config(self) -> dict[str, Any]:
"""Return an empty config.""" """Return an empty config."""
return {} return {}
def _get_value(self, hass, data, config_key): def _get_value(
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
) -> dict[str, Any] | None:
"""Get value.""" """Get value."""
return data.get(config_key) return data.get(config_key)
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: dict[str, dict[str, Any]],
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
data.setdefault(config_key, {}).update(new_value) data.setdefault(config_key, {}).update(new_value)
def _delete_value(self, hass, data, config_key): def _delete_value(
self, hass: HomeAssistant, data: dict[str, dict[str, Any]], config_key: str
) -> dict[str, Any]:
"""Delete value.""" """Delete value."""
return data.pop(config_key) return data.pop(config_key)
class EditIdBasedConfigView(BaseEditConfigView): class EditIdBasedConfigView(BaseEditConfigView[list[dict[str, Any]]]):
"""Configure key based config entries.""" """Configure key based config entries."""
def _empty_config(self): def _empty_config(self) -> list[Any]:
"""Return an empty config.""" """Return an empty config."""
return [] return []
def _get_value(self, hass, data, config_key): def _get_value(
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
) -> dict[str, Any] | None:
"""Get value.""" """Get value."""
return next((val for val in data if val.get(CONF_ID) == config_key), None) return next((val for val in data if val.get(CONF_ID) == config_key), None)
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: list[dict[str, Any]],
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
if (value := self._get_value(hass, data, config_key)) is None: if (value := self._get_value(hass, data, config_key)) is None:
value = {CONF_ID: config_key} value = {CONF_ID: config_key}
@ -225,7 +265,9 @@ class EditIdBasedConfigView(BaseEditConfigView):
value.update(new_value) value.update(new_value)
def _delete_value(self, hass, data, config_key): def _delete_value(
self, hass: HomeAssistant, data: list[dict[str, Any]], config_key: str
) -> None:
"""Delete value.""" """Delete value."""
index = next( index = next(
idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key idx for idx, val in enumerate(data) if val.get(CONF_ID) == config_key
@ -233,7 +275,7 @@ class EditIdBasedConfigView(BaseEditConfigView):
data.pop(index) data.pop(index)
def _read(path): def _read(path: str) -> JSON_TYPE | None:
"""Read YAML helper.""" """Read YAML helper."""
if not os.path.isfile(path): if not os.path.isfile(path):
return None return None
@ -241,7 +283,7 @@ def _read(path):
return load_yaml(path) return load_yaml(path)
def _write(path, data): def _write(path: str, data: dict | list) -> None:
"""Write YAML helper.""" """Write YAML helper."""
# Do it before opening file. If dump causes error it will now not # Do it before opening file. If dump causes error it will now not
# truncate the file. # truncate the file.

View file

@ -1,14 +1,16 @@
"""HTTP views to interact with the area registry.""" """HTTP views to interact with the area registry."""
from __future__ import annotations
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.area_registry import async_get from homeassistant.helpers.area_registry import AreaEntry, async_get
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Area Registry views.""" """Enable the Area Registry views."""
websocket_api.async_register_command(hass, websocket_list_areas) websocket_api.async_register_command(hass, websocket_list_areas)
websocket_api.async_register_command(hass, websocket_create_area) websocket_api.async_register_command(hass, websocket_create_area)
@ -126,7 +128,7 @@ def websocket_update_area(
@callback @callback
def _entry_dict(entry): def _entry_dict(entry: AreaEntry) -> dict[str, Any]:
"""Convert entry to API format.""" """Convert entry to API format."""
return { return {
"aliases": entry.aliases, "aliases": entry.aliases,

View file

@ -1,8 +1,11 @@
"""Offer API to configure Home Assistant auth.""" """Offer API to configure Home Assistant auth."""
from __future__ import annotations
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.auth.models import User
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
@ -17,7 +20,7 @@ SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend(
) )
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Home Assistant views.""" """Enable the Home Assistant views."""
websocket_api.async_register_command( websocket_api.async_register_command(
hass, WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST hass, WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST
@ -151,7 +154,7 @@ async def websocket_update(
) )
def _user_info(user): def _user_info(user: User) -> dict[str, Any]:
"""Format a user.""" """Format a user."""
ha_username = next( ha_username = next(

View file

@ -1,4 +1,6 @@
"""Offer API to configure the Home Assistant auth provider.""" """Offer API to configure the Home Assistant auth provider."""
from __future__ import annotations
from typing import Any from typing import Any
import voluptuous as vol import voluptuous as vol
@ -9,7 +11,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Home Assistant views.""" """Enable the Home Assistant views."""
websocket_api.async_register_command(hass, websocket_create) websocket_api.async_register_command(hass, websocket_create)
websocket_api.async_register_command(hass, websocket_delete) websocket_api.async_register_command(hass, websocket_delete)
@ -115,7 +117,7 @@ async def websocket_change_password(
) -> None: ) -> None:
"""Change current user password.""" """Change current user password."""
if (user := connection.user) is None: if (user := connection.user) is None:
connection.send_error(msg["id"], "user_not_found", "User not found") connection.send_error(msg["id"], "user_not_found", "User not found") # type: ignore[unreachable]
return return
provider = auth_ha.async_get_provider(hass) provider = auth_ha.async_get_provider(hass)

View file

@ -1,4 +1,7 @@
"""Provide configuration end points for Automations.""" """Provide configuration end points for Automations."""
from __future__ import annotations
from typing import Any
import uuid import uuid
from homeassistant.components.automation.config import ( from homeassistant.components.automation.config import (
@ -8,15 +11,16 @@ from homeassistant.components.automation.config import (
) )
from homeassistant.config import AUTOMATION_CONFIG_PATH from homeassistant.config import AUTOMATION_CONFIG_PATH
from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.const import CONF_ID, SERVICE_RELOAD
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers import config_validation as cv, entity_registry as er
from . import ACTION_DELETE, EditIdBasedConfigView from . import ACTION_DELETE, EditIdBasedConfigView
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Set up the Automation config API.""" """Set up the Automation config API."""
async def hook(action, config_key): async def hook(action: str, config_key: str) -> None:
"""post_write_hook for Config View that reloads automations.""" """post_write_hook for Config View that reloads automations."""
await hass.services.async_call(DOMAIN, SERVICE_RELOAD) await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@ -49,7 +53,13 @@ async def async_setup(hass):
class EditAutomationConfigView(EditIdBasedConfigView): class EditAutomationConfigView(EditIdBasedConfigView):
"""Edit automation config.""" """Edit automation config."""
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: list[dict[str, Any]],
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
updated_value = {CONF_ID: config_key} updated_value = {CONF_ID: config_key}

View file

@ -1,8 +1,9 @@
"""Http views to control the config manager.""" """Http views to control the config manager."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable
from http import HTTPStatus from http import HTTPStatus
from typing import Any from typing import Any, NoReturn
from aiohttp import web from aiohttp import web
import aiohttp.web_exceptions import aiohttp.web_exceptions
@ -29,7 +30,7 @@ from homeassistant.loader import (
) )
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Home Assistant views.""" """Enable the Home Assistant views."""
hass.http.register_view(ConfigManagerEntryIndexView) hass.http.register_view(ConfigManagerEntryIndexView)
hass.http.register_view(ConfigManagerEntryResourceView) hass.http.register_view(ConfigManagerEntryResourceView)
@ -58,7 +59,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
url = "/api/config/config_entries/entry" url = "/api/config/config_entries/entry"
name = "api:config:config_entries:entry" name = "api:config:config_entries:entry"
async def get(self, request): async def get(self, request: web.Request) -> web.Response:
"""List available config entries.""" """List available config entries."""
hass: HomeAssistant = request.app["hass"] hass: HomeAssistant = request.app["hass"]
domain = None domain = None
@ -76,12 +77,12 @@ class ConfigManagerEntryResourceView(HomeAssistantView):
url = "/api/config/config_entries/entry/{entry_id}" url = "/api/config/config_entries/entry/{entry_id}"
name = "api:config:config_entries:entry:resource" name = "api:config:config_entries:entry:resource"
async def delete(self, request, entry_id): async def delete(self, request: web.Request, entry_id: str) -> web.Response:
"""Delete a config entry.""" """Delete a config entry."""
if not request["hass_user"].is_admin: if not request["hass_user"].is_admin:
raise Unauthorized(config_entry_id=entry_id, permission="remove") raise Unauthorized(config_entry_id=entry_id, permission="remove")
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
try: try:
result = await hass.config_entries.async_remove(entry_id) result = await hass.config_entries.async_remove(entry_id)
@ -97,12 +98,12 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView):
url = "/api/config/config_entries/entry/{entry_id}/reload" url = "/api/config/config_entries/entry/{entry_id}/reload"
name = "api:config:config_entries:entry:resource:reload" name = "api:config:config_entries:entry:resource:reload"
async def post(self, request, entry_id): async def post(self, request: web.Request, entry_id: str) -> web.Response:
"""Reload a config entry.""" """Reload a config entry."""
if not request["hass_user"].is_admin: if not request["hass_user"].is_admin:
raise Unauthorized(config_entry_id=entry_id, permission="remove") raise Unauthorized(config_entry_id=entry_id, permission="remove")
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
entry = hass.config_entries.async_get_entry(entry_id) entry = hass.config_entries.async_get_entry(entry_id)
if not entry: if not entry:
return self.json_message("Invalid entry specified", HTTPStatus.NOT_FOUND) return self.json_message("Invalid entry specified", HTTPStatus.NOT_FOUND)
@ -116,7 +117,12 @@ class ConfigManagerEntryResourceReloadView(HomeAssistantView):
return self.json({"require_restart": not entry.state.recoverable}) return self.json({"require_restart": not entry.state.recoverable})
def _prepare_config_flow_result_json(result, prepare_result_json): def _prepare_config_flow_result_json(
result: data_entry_flow.FlowResult,
prepare_result_json: Callable[
[data_entry_flow.FlowResult], data_entry_flow.FlowResult
],
) -> data_entry_flow.FlowResult:
"""Convert result to JSON.""" """Convert result to JSON."""
if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY: if result["type"] != data_entry_flow.FlowResultType.CREATE_ENTRY:
return prepare_result_json(result) return prepare_result_json(result)
@ -134,14 +140,14 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
url = "/api/config/config_entries/flow" url = "/api/config/config_entries/flow"
name = "api:config:config_entries:flow" name = "api:config:config_entries:flow"
async def get(self, request): async def get(self, request: web.Request) -> NoReturn:
"""Not implemented.""" """Not implemented."""
raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"]) raise aiohttp.web_exceptions.HTTPMethodNotAllowed("GET", ["POST"])
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
) )
async def post(self, request): async def post(self, request: web.Request) -> web.Response:
"""Handle a POST request.""" """Handle a POST request."""
try: try:
return await super().post(request) return await super().post(request)
@ -151,7 +157,9 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
status=HTTPStatus.BAD_REQUEST, status=HTTPStatus.BAD_REQUEST,
) )
def _prepare_result_json(self, result): def _prepare_result_json(
self, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult:
"""Convert result to JSON.""" """Convert result to JSON."""
return _prepare_config_flow_result_json(result, super()._prepare_result_json) return _prepare_config_flow_result_json(result, super()._prepare_result_json)
@ -165,18 +173,20 @@ class ConfigManagerFlowResourceView(FlowManagerResourceView):
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
) )
async def get(self, request, /, flow_id): async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
"""Get the current state of a data_entry_flow.""" """Get the current state of a data_entry_flow."""
return await super().get(request, flow_id) return await super().get(request, flow_id)
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add") error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission="add")
) )
async def post(self, request, flow_id): async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request.""" """Handle a POST request."""
return await super().post(request, flow_id) return await super().post(request, flow_id)
def _prepare_result_json(self, result): def _prepare_result_json(
self, result: data_entry_flow.FlowResult
) -> data_entry_flow.FlowResult:
"""Convert result to JSON.""" """Convert result to JSON."""
return _prepare_config_flow_result_json(result, super()._prepare_result_json) return _prepare_config_flow_result_json(result, super()._prepare_result_json)
@ -187,10 +197,10 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
url = "/api/config/config_entries/flow_handlers" url = "/api/config/config_entries/flow_handlers"
name = "api:config:config_entries:flow_handlers" name = "api:config:config_entries:flow_handlers"
async def get(self, request): async def get(self, request: web.Request) -> web.Response:
"""List available flow handlers.""" """List available flow handlers."""
hass = request.app["hass"] hass: HomeAssistant = request.app["hass"]
kwargs = {} kwargs: dict[str, Any] = {}
if "type" in request.query: if "type" in request.query:
kwargs["type_filter"] = request.query["type"] kwargs["type_filter"] = request.query["type"]
return self.json(await async_get_config_flows(hass, **kwargs)) return self.json(await async_get_config_flows(hass, **kwargs))
@ -205,7 +215,7 @@ class OptionManagerFlowIndexView(FlowManagerIndexView):
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT) error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
) )
async def post(self, request): async def post(self, request: web.Request) -> web.Response:
"""Handle a POST request. """Handle a POST request.
handler in request is entry_id. handler in request is entry_id.
@ -222,14 +232,14 @@ class OptionManagerFlowResourceView(FlowManagerResourceView):
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT) error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
) )
async def get(self, request, /, flow_id): async def get(self, request: web.Request, /, flow_id: str) -> web.Response:
"""Get the current state of a data_entry_flow.""" """Get the current state of a data_entry_flow."""
return await super().get(request, flow_id) return await super().get(request, flow_id)
@require_admin( @require_admin(
error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT) error=Unauthorized(perm_category=CAT_CONFIG_ENTRIES, permission=POLICY_EDIT)
) )
async def post(self, request, flow_id): async def post(self, request: web.Request, flow_id: str) -> web.Response:
"""Handle a POST request.""" """Handle a POST request."""
return await super().post(request, flow_id) return await super().post(request, flow_id)
@ -535,7 +545,7 @@ async def async_matching_config_entries(
@callback @callback
def entry_json(entry: config_entries.ConfigEntry) -> dict: def entry_json(entry: config_entries.ConfigEntry) -> dict[str, Any]:
"""Return JSON value of a config entry.""" """Return JSON value of a config entry."""
handler = config_entries.HANDLERS.get(entry.domain) handler = config_entries.HANDLERS.get(entry.domain)
# work out if handler has support for options flow # work out if handler has support for options flow

View file

@ -1,7 +1,9 @@
"""Component to interact with Hassbian tools.""" """Component to interact with Hassbian tools."""
from __future__ import annotations
from typing import Any from typing import Any
from aiohttp import web
import voluptuous as vol import voluptuous as vol
from homeassistant.components import websocket_api from homeassistant.components import websocket_api
@ -13,7 +15,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import location, unit_system from homeassistant.util import location, unit_system
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Set up the Hassbian config.""" """Set up the Hassbian config."""
hass.http.register_view(CheckConfigView) hass.http.register_view(CheckConfigView)
websocket_api.async_register_command(hass, websocket_update_config) websocket_api.async_register_command(hass, websocket_update_config)
@ -28,7 +30,7 @@ class CheckConfigView(HomeAssistantView):
name = "api:config:core:check_config" name = "api:config:core:check_config"
@require_admin @require_admin
async def post(self, request): async def post(self, request: web.Request) -> web.Response:
"""Validate configuration and return results.""" """Validate configuration and return results."""
res = await check_config.async_check_ha_config_file(request.app["hass"]) res = await check_config.async_check_ha_config_file(request.app["hass"])

View file

@ -17,7 +17,7 @@ from homeassistant.helpers.device_registry import (
) )
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Enable the Device Registry views.""" """Enable the Device Registry views."""
websocket_api.async_register_command(hass, websocket_list_devices) websocket_api.async_register_command(hass, websocket_list_devices)

View file

@ -1,19 +1,22 @@
"""Provide configuration end points for Scenes.""" """Provide configuration end points for Scenes."""
from __future__ import annotations
from typing import Any
import uuid import uuid
from homeassistant.components.scene import DOMAIN, PLATFORM_SCHEMA from homeassistant.components.scene import DOMAIN, PLATFORM_SCHEMA
from homeassistant.config import SCENE_CONFIG_PATH from homeassistant.config import SCENE_CONFIG_PATH
from homeassistant.const import CONF_ID, SERVICE_RELOAD from homeassistant.const import CONF_ID, SERVICE_RELOAD
from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.core import DOMAIN as HA_DOMAIN, HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers import config_validation as cv, entity_registry as er
from . import ACTION_DELETE, EditIdBasedConfigView from . import ACTION_DELETE, EditIdBasedConfigView
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Set up the Scene config API.""" """Set up the Scene config API."""
async def hook(action, config_key): async def hook(action: str, config_key: str) -> None:
"""post_write_hook for Config View that reloads scenes.""" """post_write_hook for Config View that reloads scenes."""
if action != ACTION_DELETE: if action != ACTION_DELETE:
await hass.services.async_call(DOMAIN, SERVICE_RELOAD) await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@ -44,7 +47,13 @@ async def async_setup(hass):
class EditSceneConfigView(EditIdBasedConfigView): class EditSceneConfigView(EditIdBasedConfigView):
"""Edit scene config.""" """Edit scene config."""
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: list[dict[str, Any]],
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
updated_value = {CONF_ID: config_key} updated_value = {CONF_ID: config_key}
# Iterate through some keys that we want to have ordered in the output # Iterate through some keys that we want to have ordered in the output

View file

@ -1,4 +1,8 @@
"""Provide configuration end points for scripts.""" """Provide configuration end points for scripts."""
from __future__ import annotations
from typing import Any
from homeassistant.components.script import DOMAIN from homeassistant.components.script import DOMAIN
from homeassistant.components.script.config import ( from homeassistant.components.script.config import (
SCRIPT_ENTITY_SCHEMA, SCRIPT_ENTITY_SCHEMA,
@ -6,15 +10,16 @@ from homeassistant.components.script.config import (
) )
from homeassistant.config import SCRIPT_CONFIG_PATH from homeassistant.config import SCRIPT_CONFIG_PATH
from homeassistant.const import SERVICE_RELOAD from homeassistant.const import SERVICE_RELOAD
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_registry as er from homeassistant.helpers import config_validation as cv, entity_registry as er
from . import ACTION_DELETE, EditKeyBasedConfigView from . import ACTION_DELETE, EditKeyBasedConfigView
async def async_setup(hass): async def async_setup(hass: HomeAssistant) -> bool:
"""Set up the script config API.""" """Set up the script config API."""
async def hook(action, config_key): async def hook(action: str, config_key: str) -> None:
"""post_write_hook for Config View that reloads scripts.""" """post_write_hook for Config View that reloads scripts."""
if action != ACTION_DELETE: if action != ACTION_DELETE:
await hass.services.async_call(DOMAIN, SERVICE_RELOAD) await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
@ -46,6 +51,12 @@ async def async_setup(hass):
class EditScriptConfigView(EditKeyBasedConfigView): class EditScriptConfigView(EditKeyBasedConfigView):
"""Edit script config.""" """Edit script config."""
def _write_value(self, hass, data, config_key, new_value): def _write_value(
self,
hass: HomeAssistant,
data: dict[str, dict[str, Any]],
config_key: str,
new_value: dict[str, Any],
) -> None:
"""Set value.""" """Set value."""
data[config_key] = new_value data[config_key] = new_value

View file

@ -17,7 +17,7 @@ except ImportError:
) )
def dump(_dict: dict) -> str: def dump(_dict: dict | list) -> str:
"""Dump YAML to a string and remove null.""" """Dump YAML to a string and remove null."""
return yaml.dump( return yaml.dump(
_dict, _dict,

View file

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