Coverage for custom_components/dev_shell_server/websocket_api.py: 54%
38 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-03 09:02 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-03 09:02 +0000
1"""Websocket commands: dev_shell_server/exec, dev_shell_server/reset,
2dev_shell_server/sessions (all admin only)."""
4from __future__ import annotations
6from typing import Any
8import voluptuous as vol
9from homeassistant.components import websocket_api
10from homeassistant.core import HomeAssistant, callback
12from .const import DEFAULT_SESSION, DOMAIN
13from .session import SessionManager
16@callback
17def async_register(hass: HomeAssistant) -> None:
18 websocket_api.async_register_command(hass, ws_exec)
19 websocket_api.async_register_command(hass, ws_reset)
20 websocket_api.async_register_command(hass, ws_sessions)
23def _manager(
24 hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
25) -> SessionManager | None:
26 if (manager := hass.data.get(DOMAIN)) is None:
27 connection.send_error(
28 msg["id"], "not_loaded", "Dev Shell integration is not loaded"
29 )
30 return manager
33@websocket_api.require_admin
34# ty resolves vol.Required/vol.Optional's own Marker subclassing too narrowly
35# to see this inline schema dict as a VolDictType - a known voluptuous/
36# static-typing friction point across the HA ecosystem, not specific to this
37# schema (mypy, via homeassistant-stubs, is fine with it as-is).
38@websocket_api.websocket_command({
39 vol.Required("type"): "dev_shell_server/exec",
40 vol.Required("code"): str,
41 vol.Optional("session", default=DEFAULT_SESSION): str,
42 vol.Optional("timeout"): vol.Coerce(float),
43 vol.Optional("color", default=False): bool,
44 vol.Optional("width", default=88): vol.Coerce(int),
45})
46@websocket_api.async_response
47async def ws_exec(
48 hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
49) -> None:
50 if (manager := _manager(hass, connection, msg)) is None:
51 return
52 result = await manager.get(msg["session"]).run(
53 msg["code"], msg.get("timeout"), color=msg["color"], width=msg["width"]
54 )
55 connection.send_result(msg["id"], result.as_dict())
58@websocket_api.require_admin
59@websocket_api.websocket_command({
60 vol.Required("type"): "dev_shell_server/reset",
61 vol.Optional("session", default=DEFAULT_SESSION): str,
62})
63@callback
64def ws_reset(
65 hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
66) -> None:
67 if (manager := _manager(hass, connection, msg)) is None:
68 return
69 connection.send_result(msg["id"], {"reset": manager.reset(msg["session"])})
72@websocket_api.require_admin
73@websocket_api.websocket_command({vol.Required("type"): "dev_shell_server/sessions"})
74@callback
75def ws_sessions(
76 hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
77) -> None:
78 if (manager := _manager(hass, connection, msg)) is None:
79 return
80 connection.send_result(msg["id"], {"sessions": manager.describe()})