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

1"""Home Assistant REPL: a live Python REPL inside Home Assistant, served over the websocket API. 

2 

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""" 

7 

8from __future__ import annotations 

9 

10import logging 

11 

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 

16 

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 

23 

24_LOGGER = logging.getLogger(__name__) 

25 

26CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) 

27 

28 

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 

40 

41 

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 

58 

59 

60async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 

61 hass.data.pop(DOMAIN, None) 

62 return True