Coverage for custom_components/ha_repl_server/__init__.py: 94%
31 statements
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-05 13:50 +0000
« prev ^ index » next coverage.py v7.15.4, created at 2026-10-05 13:50 +0000
1"""Home Assistant REPL: a live Python REPL inside Home Assistant, served over the websocket API.
3Set up from Settings > Devices & services > Add integration, or with `ha_repl_server:` in
4configuration.yaml (imported as a config entry). Executes arbitrary code as admin; never
5enable it on an instance where admin accounts are not fully trusted.
6"""
8from __future__ import annotations
10import logging
12from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
13from homeassistant.core import HomeAssistant
14from homeassistant.helpers import config_validation as cv
15from homeassistant.helpers.typing import ConfigType
17from . import websocket_api
18from .browser import open_target
19from .const import DOMAIN
20from .objtree import ObjTree
21from .rest import HassApiUnavailable, connect_hass_api
22from .session import SessionManager
24_LOGGER = logging.getLogger(__name__)
26CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
29async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
30 # Commands stay registered for the life of the process (HA has no unregister);
31 # they report an error while no config entry is loaded.
32 websocket_api.async_register(hass)
33 if DOMAIN in config: 33 ↛ 34line 33 didn't jump to line 34 because the condition on line 33 was never true
34 hass.async_create_task(
35 hass.config_entries.flow.async_init(
36 DOMAIN, context={"source": SOURCE_IMPORT}, data={}
37 )
38 )
39 return True
42async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
43 try:
44 hass_api = await connect_hass_api()
45 except HassApiUnavailable as err:
46 _LOGGER.warning("Home Assistant REPL: `hass_api` unavailable: %s", err)
47 hass_api = None
48 hass.data[DOMAIN] = SessionManager({
49 "hass": hass,
50 "obj": ObjTree(hass),
51 "open": open_target,
52 "hass_api": hass_api,
53 })
54 _LOGGER.warning(
55 "Home Assistant REPL is enabled: admin users can execute arbitrary Python in this instance"
56 )
57 return True
60async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
61 hass.data.pop(DOMAIN, None)
62 return True