mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 13:17:32 +00:00
Type frontend strictly (#52148)
This commit is contained in:
parent
afa00b7626
commit
09b3882a5b
@ -1,13 +1,14 @@
|
|||||||
"""Handle the frontend for Home Assistant."""
|
"""Handle the frontend for Home Assistant."""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Iterator
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
import mimetypes
|
||||||
import os
|
import os
|
||||||
import pathlib
|
import pathlib
|
||||||
from typing import Any
|
from typing import Any, TypedDict, cast
|
||||||
|
|
||||||
from aiohttp import hdrs, web, web_urldispatcher
|
from aiohttp import hdrs, web, web_urldispatcher
|
||||||
import jinja2
|
import jinja2
|
||||||
@ -16,18 +17,18 @@ from yarl import URL
|
|||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
from homeassistant.components.http.view import HomeAssistantView
|
from homeassistant.components.http.view import HomeAssistantView
|
||||||
|
from homeassistant.components.websocket_api.connection import ActiveConnection
|
||||||
from homeassistant.config import async_hass_config_yaml
|
from homeassistant.config import async_hass_config_yaml
|
||||||
from homeassistant.const import CONF_MODE, CONF_NAME, EVENT_THEMES_UPDATED
|
from homeassistant.const import CONF_MODE, CONF_NAME, EVENT_THEMES_UPDATED
|
||||||
from homeassistant.core import callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.helpers import service
|
from homeassistant.helpers import service
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
from homeassistant.helpers.translation import async_get_translations
|
from homeassistant.helpers.translation import async_get_translations
|
||||||
|
from homeassistant.helpers.typing import ConfigType
|
||||||
from homeassistant.loader import async_get_integration, bind_hass
|
from homeassistant.loader import async_get_integration, bind_hass
|
||||||
|
|
||||||
from .storage import async_setup_frontend_storage
|
from .storage import async_setup_frontend_storage
|
||||||
|
|
||||||
# mypy: allow-untyped-defs, no-check-untyped-defs
|
|
||||||
|
|
||||||
# Fix mimetypes for borked Windows machines
|
# Fix mimetypes for borked Windows machines
|
||||||
# https://github.com/home-assistant/frontend/issues/3336
|
# https://github.com/home-assistant/frontend/issues/3336
|
||||||
mimetypes.add_type("text/css", ".css")
|
mimetypes.add_type("text/css", ".css")
|
||||||
@ -191,15 +192,15 @@ class UrlManager:
|
|||||||
on hass.data
|
on hass.data
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, urls):
|
def __init__(self, urls: list[str]) -> None:
|
||||||
"""Init the url manager."""
|
"""Init the url manager."""
|
||||||
self.urls = frozenset(urls)
|
self.urls = frozenset(urls)
|
||||||
|
|
||||||
def add(self, url):
|
def add(self, url: str) -> None:
|
||||||
"""Add a url to the set."""
|
"""Add a url to the set."""
|
||||||
self.urls = frozenset([*self.urls, url])
|
self.urls = frozenset([*self.urls, url])
|
||||||
|
|
||||||
def remove(self, url):
|
def remove(self, url: str) -> None:
|
||||||
"""Remove a url from the set."""
|
"""Remove a url from the set."""
|
||||||
self.urls = self.urls - {url}
|
self.urls = self.urls - {url}
|
||||||
|
|
||||||
@ -208,7 +209,7 @@ class Panel:
|
|||||||
"""Abstract class for panels."""
|
"""Abstract class for panels."""
|
||||||
|
|
||||||
# Name of the webcomponent
|
# Name of the webcomponent
|
||||||
component_name: str | None = None
|
component_name: str
|
||||||
|
|
||||||
# Icon to show in the sidebar
|
# Icon to show in the sidebar
|
||||||
sidebar_icon: str | None = None
|
sidebar_icon: str | None = None
|
||||||
@ -227,13 +228,13 @@ class Panel:
|
|||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
component_name,
|
component_name: str,
|
||||||
sidebar_title,
|
sidebar_title: str | None,
|
||||||
sidebar_icon,
|
sidebar_icon: str | None,
|
||||||
frontend_url_path,
|
frontend_url_path: str | None,
|
||||||
config,
|
config: dict[str, Any] | None,
|
||||||
require_admin,
|
require_admin: bool,
|
||||||
):
|
) -> None:
|
||||||
"""Initialize a built-in panel."""
|
"""Initialize a built-in panel."""
|
||||||
self.component_name = component_name
|
self.component_name = component_name
|
||||||
self.sidebar_title = sidebar_title
|
self.sidebar_title = sidebar_title
|
||||||
@ -243,7 +244,7 @@ class Panel:
|
|||||||
self.require_admin = require_admin
|
self.require_admin = require_admin
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def to_response(self):
|
def to_response(self) -> PanelRespons:
|
||||||
"""Panel as dictionary."""
|
"""Panel as dictionary."""
|
||||||
return {
|
return {
|
||||||
"component_name": self.component_name,
|
"component_name": self.component_name,
|
||||||
@ -258,16 +259,16 @@ class Panel:
|
|||||||
@bind_hass
|
@bind_hass
|
||||||
@callback
|
@callback
|
||||||
def async_register_built_in_panel(
|
def async_register_built_in_panel(
|
||||||
hass,
|
hass: HomeAssistant,
|
||||||
component_name,
|
component_name: str,
|
||||||
sidebar_title=None,
|
sidebar_title: str | None = None,
|
||||||
sidebar_icon=None,
|
sidebar_icon: str | None = None,
|
||||||
frontend_url_path=None,
|
frontend_url_path: str | None = None,
|
||||||
config=None,
|
config: dict[str, Any] | None = None,
|
||||||
require_admin=False,
|
require_admin: bool = False,
|
||||||
*,
|
*,
|
||||||
update=False,
|
update: bool = False,
|
||||||
):
|
) -> None:
|
||||||
"""Register a built-in panel."""
|
"""Register a built-in panel."""
|
||||||
panel = Panel(
|
panel = Panel(
|
||||||
component_name,
|
component_name,
|
||||||
@ -290,7 +291,7 @@ def async_register_built_in_panel(
|
|||||||
|
|
||||||
@bind_hass
|
@bind_hass
|
||||||
@callback
|
@callback
|
||||||
def async_remove_panel(hass, frontend_url_path):
|
def async_remove_panel(hass: HomeAssistant, frontend_url_path: str) -> None:
|
||||||
"""Remove a built-in panel."""
|
"""Remove a built-in panel."""
|
||||||
panel = hass.data.get(DATA_PANELS, {}).pop(frontend_url_path, None)
|
panel = hass.data.get(DATA_PANELS, {}).pop(frontend_url_path, None)
|
||||||
|
|
||||||
@ -300,18 +301,18 @@ def async_remove_panel(hass, frontend_url_path):
|
|||||||
hass.bus.async_fire(EVENT_PANELS_UPDATED)
|
hass.bus.async_fire(EVENT_PANELS_UPDATED)
|
||||||
|
|
||||||
|
|
||||||
def add_extra_js_url(hass, url, es5=False):
|
def add_extra_js_url(hass: HomeAssistant, url: str, es5: bool = False) -> None:
|
||||||
"""Register extra js or module url to load."""
|
"""Register extra js or module url to load."""
|
||||||
key = DATA_EXTRA_JS_URL_ES5 if es5 else DATA_EXTRA_MODULE_URL
|
key = DATA_EXTRA_JS_URL_ES5 if es5 else DATA_EXTRA_MODULE_URL
|
||||||
hass.data[key].add(url)
|
hass.data[key].add(url)
|
||||||
|
|
||||||
|
|
||||||
def add_manifest_json_key(key, val):
|
def add_manifest_json_key(key: str, val: Any) -> None:
|
||||||
"""Add a keyval to the manifest.json."""
|
"""Add a keyval to the manifest.json."""
|
||||||
MANIFEST_JSON.update_key(key, val)
|
MANIFEST_JSON.update_key(key, val)
|
||||||
|
|
||||||
|
|
||||||
def _frontend_root(dev_repo_path):
|
def _frontend_root(dev_repo_path: str | None) -> pathlib.Path:
|
||||||
"""Return root path to the frontend files."""
|
"""Return root path to the frontend files."""
|
||||||
if dev_repo_path is not None:
|
if dev_repo_path is not None:
|
||||||
return pathlib.Path(dev_repo_path) / "hass_frontend"
|
return pathlib.Path(dev_repo_path) / "hass_frontend"
|
||||||
@ -319,17 +320,17 @@ def _frontend_root(dev_repo_path):
|
|||||||
# pylint: disable=import-outside-toplevel
|
# pylint: disable=import-outside-toplevel
|
||||||
import hass_frontend
|
import hass_frontend
|
||||||
|
|
||||||
return hass_frontend.where()
|
return cast(pathlib.Path, hass_frontend.where())
|
||||||
|
|
||||||
|
|
||||||
async def async_setup(hass, config):
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
||||||
"""Set up the serving of the frontend."""
|
"""Set up the serving of the frontend."""
|
||||||
await async_setup_frontend_storage(hass)
|
await async_setup_frontend_storage(hass)
|
||||||
hass.components.websocket_api.async_register_command(websocket_get_panels)
|
hass.components.websocket_api.async_register_command(websocket_get_panels)
|
||||||
hass.components.websocket_api.async_register_command(websocket_get_themes)
|
hass.components.websocket_api.async_register_command(websocket_get_themes)
|
||||||
hass.components.websocket_api.async_register_command(websocket_get_translations)
|
hass.components.websocket_api.async_register_command(websocket_get_translations)
|
||||||
hass.components.websocket_api.async_register_command(websocket_get_version)
|
hass.components.websocket_api.async_register_command(websocket_get_version)
|
||||||
hass.http.register_view(ManifestJSONView)
|
hass.http.register_view(ManifestJSONView())
|
||||||
|
|
||||||
conf = config.get(DOMAIN, {})
|
conf = config.get(DOMAIN, {})
|
||||||
|
|
||||||
@ -396,7 +397,9 @@ async def async_setup(hass, config):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def _async_setup_themes(hass, themes):
|
async def _async_setup_themes(
|
||||||
|
hass: HomeAssistant, themes: dict[str, Any] | None
|
||||||
|
) -> None:
|
||||||
"""Set up themes data and services."""
|
"""Set up themes data and services."""
|
||||||
hass.data[DATA_THEMES] = themes or {}
|
hass.data[DATA_THEMES] = themes or {}
|
||||||
|
|
||||||
@ -417,7 +420,7 @@ async def _async_setup_themes(hass, themes):
|
|||||||
hass.data[DATA_DEFAULT_DARK_THEME] = dark_theme_name
|
hass.data[DATA_DEFAULT_DARK_THEME] = dark_theme_name
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def update_theme_and_fire_event():
|
def update_theme_and_fire_event() -> None:
|
||||||
"""Update theme_color in manifest."""
|
"""Update theme_color in manifest."""
|
||||||
name = hass.data[DATA_DEFAULT_THEME]
|
name = hass.data[DATA_DEFAULT_THEME]
|
||||||
themes = hass.data[DATA_THEMES]
|
themes = hass.data[DATA_THEMES]
|
||||||
@ -434,7 +437,7 @@ async def _async_setup_themes(hass, themes):
|
|||||||
hass.bus.async_fire(EVENT_THEMES_UPDATED)
|
hass.bus.async_fire(EVENT_THEMES_UPDATED)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def set_theme(call):
|
def set_theme(call: ServiceCall) -> None:
|
||||||
"""Set backend-preferred theme."""
|
"""Set backend-preferred theme."""
|
||||||
name = call.data[CONF_NAME]
|
name = call.data[CONF_NAME]
|
||||||
mode = call.data.get("mode", "light")
|
mode = call.data.get("mode", "light")
|
||||||
@ -466,7 +469,7 @@ async def _async_setup_themes(hass, themes):
|
|||||||
)
|
)
|
||||||
update_theme_and_fire_event()
|
update_theme_and_fire_event()
|
||||||
|
|
||||||
async def reload_themes(_):
|
async def reload_themes(_: ServiceCall) -> None:
|
||||||
"""Reload themes."""
|
"""Reload themes."""
|
||||||
config = await async_hass_config_yaml(hass)
|
config = await async_hass_config_yaml(hass)
|
||||||
new_themes = config[DOMAIN].get(CONF_THEMES, {})
|
new_themes = config[DOMAIN].get(CONF_THEMES, {})
|
||||||
@ -500,19 +503,19 @@ async def _async_setup_themes(hass, themes):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
@lru_cache(maxsize=1)
|
@lru_cache(maxsize=1)
|
||||||
def _async_render_index_cached(template, **kwargs):
|
def _async_render_index_cached(template: jinja2.Template, **kwargs: Any) -> str:
|
||||||
return template.render(**kwargs)
|
return template.render(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
class IndexView(web_urldispatcher.AbstractResource):
|
class IndexView(web_urldispatcher.AbstractResource):
|
||||||
"""Serve the frontend."""
|
"""Serve the frontend."""
|
||||||
|
|
||||||
def __init__(self, repo_path, hass):
|
def __init__(self, repo_path: str | None, hass: HomeAssistant) -> None:
|
||||||
"""Initialize the frontend view."""
|
"""Initialize the frontend view."""
|
||||||
super().__init__(name="frontend:index")
|
super().__init__(name="frontend:index")
|
||||||
self.repo_path = repo_path
|
self.repo_path = repo_path
|
||||||
self.hass = hass
|
self.hass = hass
|
||||||
self._template_cache = None
|
self._template_cache: jinja2.Template | None = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def canonical(self) -> str:
|
def canonical(self) -> str:
|
||||||
@ -520,7 +523,7 @@ class IndexView(web_urldispatcher.AbstractResource):
|
|||||||
return "/"
|
return "/"
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def _route(self):
|
def _route(self) -> web_urldispatcher.ResourceRoute:
|
||||||
"""Return the index route."""
|
"""Return the index route."""
|
||||||
return web_urldispatcher.ResourceRoute("GET", self.get, self)
|
return web_urldispatcher.ResourceRoute("GET", self.get, self)
|
||||||
|
|
||||||
@ -552,7 +555,7 @@ class IndexView(web_urldispatcher.AbstractResource):
|
|||||||
Required for subapplications support.
|
Required for subapplications support.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def get_info(self):
|
def get_info(self) -> dict[str, list[str]]: # type: ignore[override]
|
||||||
"""Return a dict with additional info useful for introspection."""
|
"""Return a dict with additional info useful for introspection."""
|
||||||
return {"panels": list(self.hass.data[DATA_PANELS])}
|
return {"panels": list(self.hass.data[DATA_PANELS])}
|
||||||
|
|
||||||
@ -562,7 +565,7 @@ class IndexView(web_urldispatcher.AbstractResource):
|
|||||||
def raw_match(self, path: str) -> bool:
|
def raw_match(self, path: str) -> bool:
|
||||||
"""Perform a raw match against path."""
|
"""Perform a raw match against path."""
|
||||||
|
|
||||||
def get_template(self):
|
def get_template(self) -> jinja2.Template:
|
||||||
"""Get template."""
|
"""Get template."""
|
||||||
tpl = self._template_cache
|
tpl = self._template_cache
|
||||||
if tpl is None:
|
if tpl is None:
|
||||||
@ -600,7 +603,7 @@ class IndexView(web_urldispatcher.AbstractResource):
|
|||||||
"""Return length of resource."""
|
"""Return length of resource."""
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self) -> Iterator[web_urldispatcher.ResourceRoute]:
|
||||||
"""Iterate over routes."""
|
"""Iterate over routes."""
|
||||||
return iter([self._route])
|
return iter([self._route])
|
||||||
|
|
||||||
@ -613,7 +616,7 @@ class ManifestJSONView(HomeAssistantView):
|
|||||||
name = "manifestjson"
|
name = "manifestjson"
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
def get(self, request): # pylint: disable=no-self-use
|
def get(self, request: web.Request) -> web.Response: # pylint: disable=no-self-use
|
||||||
"""Return the manifest.json."""
|
"""Return the manifest.json."""
|
||||||
return web.Response(
|
return web.Response(
|
||||||
text=MANIFEST_JSON.json, content_type="application/manifest+json"
|
text=MANIFEST_JSON.json, content_type="application/manifest+json"
|
||||||
@ -622,7 +625,9 @@ class ManifestJSONView(HomeAssistantView):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
@websocket_api.websocket_command({"type": "get_panels"})
|
@websocket_api.websocket_command({"type": "get_panels"})
|
||||||
def websocket_get_panels(hass, connection, msg):
|
def websocket_get_panels(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
"""Handle get panels command."""
|
"""Handle get panels command."""
|
||||||
user_is_admin = connection.user.is_admin
|
user_is_admin = connection.user.is_admin
|
||||||
panels = {
|
panels = {
|
||||||
@ -636,7 +641,9 @@ def websocket_get_panels(hass, connection, msg):
|
|||||||
|
|
||||||
@callback
|
@callback
|
||||||
@websocket_api.websocket_command({"type": "frontend/get_themes"})
|
@websocket_api.websocket_command({"type": "frontend/get_themes"})
|
||||||
def websocket_get_themes(hass, connection, msg):
|
def websocket_get_themes(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
"""Handle get themes command."""
|
"""Handle get themes command."""
|
||||||
if hass.config.safe_mode:
|
if hass.config.safe_mode:
|
||||||
connection.send_message(
|
connection.send_message(
|
||||||
@ -677,7 +684,9 @@ def websocket_get_themes(hass, connection, msg):
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def websocket_get_translations(hass, connection, msg):
|
async def websocket_get_translations(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
"""Handle get translations command."""
|
"""Handle get translations command."""
|
||||||
resources = await async_get_translations(
|
resources = await async_get_translations(
|
||||||
hass,
|
hass,
|
||||||
@ -693,7 +702,9 @@ async def websocket_get_translations(hass, connection, msg):
|
|||||||
|
|
||||||
@websocket_api.websocket_command({"type": "frontend/get_version"})
|
@websocket_api.websocket_command({"type": "frontend/get_version"})
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
async def websocket_get_version(hass, connection, msg):
|
async def websocket_get_version(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
"""Handle get version command."""
|
"""Handle get version command."""
|
||||||
integration = await async_get_integration(hass, "frontend")
|
integration = await async_get_integration(hass, "frontend")
|
||||||
|
|
||||||
@ -707,3 +718,14 @@ async def websocket_get_version(hass, connection, msg):
|
|||||||
connection.send_error(msg["id"], "unknown_version", "Version not found")
|
connection.send_error(msg["id"], "unknown_version", "Version not found")
|
||||||
else:
|
else:
|
||||||
connection.send_result(msg["id"], {"version": frontend})
|
connection.send_result(msg["id"], {"version": frontend})
|
||||||
|
|
||||||
|
|
||||||
|
class PanelRespons(TypedDict):
|
||||||
|
"""Represent the panel response type."""
|
||||||
|
|
||||||
|
component_name: str
|
||||||
|
icon: str | None
|
||||||
|
title: str | None
|
||||||
|
config: dict[str, Any] | None
|
||||||
|
url_path: str | None
|
||||||
|
require_admin: bool
|
||||||
|
@ -1,28 +1,34 @@
|
|||||||
"""API for persistent storage for the frontend."""
|
"""API for persistent storage for the frontend."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.components import websocket_api
|
from homeassistant.components import websocket_api
|
||||||
|
from homeassistant.components.websocket_api.connection import ActiveConnection
|
||||||
# mypy: allow-untyped-calls, allow-untyped-defs
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.storage import Store
|
||||||
|
|
||||||
DATA_STORAGE = "frontend_storage"
|
DATA_STORAGE = "frontend_storage"
|
||||||
STORAGE_VERSION_USER_DATA = 1
|
STORAGE_VERSION_USER_DATA = 1
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_frontend_storage(hass):
|
async def async_setup_frontend_storage(hass: HomeAssistant) -> None:
|
||||||
"""Set up frontend storage."""
|
"""Set up frontend storage."""
|
||||||
hass.data[DATA_STORAGE] = ({}, {})
|
hass.data[DATA_STORAGE] = ({}, {})
|
||||||
hass.components.websocket_api.async_register_command(websocket_set_user_data)
|
hass.components.websocket_api.async_register_command(websocket_set_user_data)
|
||||||
hass.components.websocket_api.async_register_command(websocket_get_user_data)
|
hass.components.websocket_api.async_register_command(websocket_get_user_data)
|
||||||
|
|
||||||
|
|
||||||
def with_store(orig_func):
|
def with_store(orig_func: Callable) -> Callable:
|
||||||
"""Decorate function to provide data."""
|
"""Decorate function to provide data."""
|
||||||
|
|
||||||
@wraps(orig_func)
|
@wraps(orig_func)
|
||||||
async def with_store_func(hass, connection, msg):
|
async def with_store_func(
|
||||||
|
hass: HomeAssistant, connection: ActiveConnection, msg: dict
|
||||||
|
) -> None:
|
||||||
"""Provide user specific data and store to function."""
|
"""Provide user specific data and store to function."""
|
||||||
stores, data = hass.data[DATA_STORAGE]
|
stores, data = hass.data[DATA_STORAGE]
|
||||||
user_id = connection.user.id
|
user_id = connection.user.id
|
||||||
@ -50,7 +56,13 @@ def with_store(orig_func):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
@with_store
|
@with_store
|
||||||
async def websocket_set_user_data(hass, connection, msg, store, data):
|
async def websocket_set_user_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
store: Store,
|
||||||
|
data: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
"""Handle set global data command.
|
"""Handle set global data command.
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
@ -65,7 +77,13 @@ async def websocket_set_user_data(hass, connection, msg, store, data):
|
|||||||
)
|
)
|
||||||
@websocket_api.async_response
|
@websocket_api.async_response
|
||||||
@with_store
|
@with_store
|
||||||
async def websocket_get_user_data(hass, connection, msg, store, data):
|
async def websocket_get_user_data(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
connection: ActiveConnection,
|
||||||
|
msg: dict,
|
||||||
|
store: Store,
|
||||||
|
data: dict[str, Any],
|
||||||
|
) -> None:
|
||||||
"""Handle get global data command.
|
"""Handle get global data command.
|
||||||
|
|
||||||
Async friendly.
|
Async friendly.
|
||||||
|
Loading…
x
Reference in New Issue
Block a user