Coverage for custom_components/ha_repl_server/rest.py: 76%

19 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-10-05 13:50 +0000

1"""`hass_api` - an already-connected homeassistant_api.AsyncClient, bound in 

2custom mode alongside `hass`/`obj` for parity with API client mode (see 

3../../src/homeassistant_repl/rest.py). Duplicated rather than imported 

4across the packaging boundary for the same reason as objtree.py/paths.py - 

5this component is deployed via HACS with its own `manifest.json` 

6requirements, not as a dependent of the PyPI homeassistant_repl package. 

7 

8Named `hass_api`, not `api`: `obj.mode("api")` already uses "api" for the 

9cached/read-only view `obj` can switch into, and the two are easy to 

10conflate in a transcript otherwise. 

11 

12There's no notion of "the current admin's token" inside hass itself, so 

13this only resolves via the add-on supervisor proxy or an explicit 

14$HASS_SERVER/$HASS_TOKEN - see __init__.py for what happens when neither is 

15set (a warning, not a failed setup). 

16""" 

17 

18from __future__ import annotations 

19 

20import os 

21 

22from homeassistant_api import AsyncClient 

23from homeassistant_api.errors import HomeassistantAPIError 

24 

25 

26class HassApiUnavailable(Exception): 

27 """No usable $SUPERVISOR_TOKEN or $HASS_SERVER/$HASS_TOKEN, or the API 

28 didn't respond/authenticate.""" 

29 

30 

31async def connect_hass_api() -> AsyncClient: 

32 if token := os.environ.get("SUPERVISOR_TOKEN"): 32 ↛ 33line 32 didn't jump to line 33 because the condition on line 32 was never true

33 url = "http://supervisor/core/api" 

34 elif (server := os.environ.get("HASS_SERVER")) and ( 

35 token := os.environ.get("HASS_TOKEN") 

36 ): 

37 url = server.rstrip("/") + "/api" 

38 else: 

39 raise HassApiUnavailable("no $SUPERVISOR_TOKEN or $HASS_SERVER/$HASS_TOKEN set") 

40 client = AsyncClient(url, token) 

41 try: 

42 running = await client.check_api_running() 

43 except (HomeassistantAPIError, OSError) as err: 

44 raise HassApiUnavailable(f"cannot reach {url}: {err}") from err 

45 if not running: 45 ↛ 46line 45 didn't jump to line 46 because the condition on line 45 was never true

46 raise HassApiUnavailable(f"{url} is not running") 

47 return client