Coverage for custom_components/dev_shell_server/__init__.py: 93%

25 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-10-03 09:02 +0000

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

2 

3Set up from Settings > Devices & services > Add integration, or with `dev_shell_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 .session import SessionManager 

22 

23_LOGGER = logging.getLogger(__name__) 

24 

25CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN) 

26 

27 

28async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 

29 # Commands stay registered for the life of the process (HA has no unregister); 

30 # they report an error while no config entry is loaded. 

31 websocket_api.async_register(hass) 

32 if DOMAIN in config: 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true

33 hass.async_create_task( 

34 hass.config_entries.flow.async_init( 

35 DOMAIN, context={"source": SOURCE_IMPORT}, data={} 

36 ) 

37 ) 

38 return True 

39 

40 

41async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: 

42 hass.data[DOMAIN] = SessionManager({ 

43 "hass": hass, 

44 "obj": ObjTree(hass), 

45 "open": open_target, 

46 }) 

47 _LOGGER.warning( 

48 "Dev Shell is enabled: admin users can execute arbitrary Python in this instance" 

49 ) 

50 return True 

51 

52 

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

54 hass.data.pop(DOMAIN, None) 

55 return True