Skip to content

Commit 1ebb7b8

Browse files
committed
Add core APIs to client library
1 parent 31cbaed commit 1ebb7b8

File tree

8 files changed

+305
-11
lines changed

8 files changed

+305
-11
lines changed

aiohasupervisor/homeassistant.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Home Assistant client for supervisor."""
2+
3+
from .client import _SupervisorComponentClient
4+
from .models.homeassistant import (
5+
HomeAssistantInfo,
6+
HomeAssistantOptions,
7+
HomeAssistantRestartOptions,
8+
HomeAssistantStats,
9+
HomeAssistantStopOptions,
10+
HomeAssistantUpdateOptions,
11+
)
12+
13+
14+
class HomeAssistantClient(_SupervisorComponentClient):
15+
"""Handles Home Assistant access in supervisor."""
16+
17+
async def info(self) -> HomeAssistantInfo:
18+
"""Get Home Assistant info."""
19+
result = await self._client.get("core/info")
20+
return HomeAssistantInfo.from_dict(result.data)
21+
22+
async def stats(self) -> HomeAssistantStats:
23+
"""Get Home Assistant stats."""
24+
result = await self._client.get("core/stats")
25+
return HomeAssistantStats.from_dict(result.data)
26+
27+
async def options(self, options: HomeAssistantOptions) -> None:
28+
"""Set Home Assistant options."""
29+
await self._client.post("core/options", json=options.to_dict())
30+
31+
async def update(self, options: HomeAssistantUpdateOptions | None = None) -> None:
32+
"""Update Home Assistant."""
33+
await self._client.post(
34+
"core/update", json=options.to_dict() if options else None
35+
)
36+
37+
async def restart(self, options: HomeAssistantRestartOptions | None = None) -> None:
38+
"""Restart Home Assistant."""
39+
await self._client.post(
40+
"core/restart", json=options.to_dict() if options else None
41+
)
42+
43+
async def stop(self, options: HomeAssistantStopOptions | None = None) -> None:
44+
"""Stop Home Assistant."""
45+
await self._client.post(
46+
"core/stop", json=options.to_dict() if options else None
47+
)
48+
49+
async def start(self) -> None:
50+
"""Start Home Assistant."""
51+
await self._client.post("core/start")
52+
53+
async def check_config(self) -> None:
54+
"""Check Home Assistant config."""
55+
await self._client.post("core/check")
56+
57+
async def rebuild(self, options: HomeAssistantRestartOptions | None = None) -> None:
58+
"""Rebuild Home Assistant."""
59+
await self._client.post(
60+
"core/rebuild", json=options.to_dict() if options else None
61+
)

aiohasupervisor/models/__init__.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@
2323
StoreInfo,
2424
SupervisorRole,
2525
)
26+
from aiohasupervisor.models.homeassistant import (
27+
HomeAssistantInfo,
28+
HomeAssistantOptions,
29+
HomeAssistantRestartOptions,
30+
HomeAssistantStats,
31+
HomeAssistantStopOptions,
32+
HomeAssistantUpdateOptions,
33+
)
2634
from aiohasupervisor.models.resolution import (
2735
Check,
2836
CheckOptions,
@@ -96,4 +104,10 @@
96104
"SupervisorOptions",
97105
"SupervisorStats",
98106
"SupervisorUpdateOptions",
107+
"HomeAssistantInfo",
108+
"HomeAssistantOptions",
109+
"HomeAssistantRestartOptions",
110+
"HomeAssistantStats",
111+
"HomeAssistantStopOptions",
112+
"HomeAssistantUpdateOptions",
99113
]

aiohasupervisor/models/addons.py

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
from mashumaro import field_options
1010
from mashumaro.config import TO_DICT_ADD_BY_ALIAS_FLAG, BaseConfig
1111

12-
from .base import DEFAULT, Options, Request, RequestConfig, ResponseData
12+
from .base import DEFAULT, ContainerStats, Options, Request, RequestConfig, ResponseData
1313

1414
# --- ENUMS ----
1515

@@ -296,18 +296,9 @@ class AddonsSecurityOptions(Options):
296296

297297

298298
@dataclass(frozen=True, slots=True)
299-
class AddonsStats(ResponseData):
299+
class AddonsStats(ContainerStats):
300300
"""AddonsStats model."""
301301

302-
cpu_percent: float
303-
memory_usage: int
304-
memory_limit: int
305-
memory_percent: float
306-
network_rx: int
307-
network_tx: int
308-
blk_read: int
309-
blk_write: int
310-
311302

312303
@dataclass(frozen=True, slots=True)
313304
class AddonsUninstall(Request):
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
"""Models for Home Assistant."""
2+
3+
from dataclasses import dataclass
4+
5+
from .base import DEFAULT, ContainerStats, Options, Request, ResponseData
6+
7+
# --- OBJECTS ----
8+
9+
10+
@dataclass(frozen=True, slots=True)
11+
class HomeAssistantInfo(ResponseData):
12+
"""HomeAssistantInfo model."""
13+
14+
version: str | None
15+
version_latest: str | None
16+
update_available: bool
17+
machine: str
18+
ip_address: str
19+
arch: str
20+
image: str
21+
boot: bool
22+
port: int
23+
ssl: bool
24+
watchdog: bool
25+
audio_input: str
26+
audio_output: str
27+
backups_exclude_database: bool
28+
29+
30+
@dataclass(frozen=True, slots=True)
31+
class HomeAssistantStats(ContainerStats):
32+
"""HomeAssistantStats model."""
33+
34+
35+
@dataclass(frozen=True, slots=True)
36+
class HomeAssistantOptions(Options):
37+
"""HomeAssistantOptions model."""
38+
39+
boot: bool | None = None
40+
image: str | None = DEFAULT # type: ignore[assignment]
41+
port: int | None = None
42+
ssl: bool | None = None
43+
watchdog: bool | None = None
44+
refresh_token: str | None = DEFAULT # type: ignore[assignment]
45+
audio_input: str | None = DEFAULT # type: ignore[assignment]
46+
audio_output: str | None = DEFAULT # type: ignore[assignment]
47+
backups_exclude_database: bool | None = None
48+
49+
50+
@dataclass(frozen=True, slots=True)
51+
class HomeAssistantUpdateOptions(Options):
52+
"""HomeAssistantUpdateOptions model."""
53+
54+
version: str | None = None
55+
backup: bool | None = None
56+
57+
58+
@dataclass(frozen=True, slots=True)
59+
class HomeAssistantRestartOptions(Options):
60+
"""HomeAssistantRestartOptions model."""
61+
62+
safe_mode: bool | None = None
63+
force: bool | None = None
64+
65+
66+
@dataclass(frozen=True, slots=True)
67+
class HomeAssistantStopOptions(Request):
68+
"""HomeAssistantStopOptions model."""
69+
70+
force: bool

aiohasupervisor/root.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .addons import AddonsClient
88
from .client import _SupervisorClient
9+
from .homeassistant import HomeAssistantClient
910
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
1011
from .resolution import ResolutionClient
1112
from .store import StoreClient
@@ -28,12 +29,18 @@ def __init__(
2829
self._resolution = ResolutionClient(self._client)
2930
self._store = StoreClient(self._client)
3031
self._supervisor = SupervisorManagementClient(self._client)
32+
self._homeassistant = HomeAssistantClient(self._client)
3133

3234
@property
3335
def addons(self) -> AddonsClient:
3436
"""Get addons component client."""
3537
return self._addons
3638

39+
@property
40+
def homeassistant(self) -> HomeAssistantClient:
41+
"""Get Home Assistant component client."""
42+
return self._homeassistant
43+
3744
@property
3845
def resolution(self) -> ResolutionClient:
3946
"""Get resolution center component client."""
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"version": "2024.9.0",
5+
"version_latest": "2024.9.0",
6+
"update_available": false,
7+
"machine": "odroid-n2",
8+
"ip_address": "172.30.32.1",
9+
"arch": "aarch64",
10+
"image": "ghcr.io/home-assistant/odroid-n2-homeassistant",
11+
"boot": true,
12+
"port": 8123,
13+
"ssl": false,
14+
"watchdog": true,
15+
"audio_input": null,
16+
"audio_output": null,
17+
"backups_exclude_database": false
18+
}
19+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"cpu_percent": 0.01,
5+
"memory_usage": 678883328,
6+
"memory_limit": 3899138048,
7+
"memory_percent": 17.41,
8+
"network_rx": 0,
9+
"network_tx": 0,
10+
"blk_read": 0,
11+
"blk_write": 0
12+
}
13+
}

tests/test_homeassistant.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
"""Test Home Assistant supervisor client."""
2+
3+
from aioresponses import aioresponses
4+
from yarl import URL
5+
6+
from aiohasupervisor import SupervisorClient
7+
from aiohasupervisor.models import HomeAssistantOptions
8+
9+
from . import load_fixture
10+
from .const import SUPERVISOR_URL
11+
12+
13+
async def test_homeassistant_info(
14+
responses: aioresponses, supervisor_client: SupervisorClient
15+
) -> None:
16+
"""Test Home Assistant info API."""
17+
responses.get(
18+
f"{SUPERVISOR_URL}/core/info",
19+
status=200,
20+
body=load_fixture("homeassistant_info.json"),
21+
)
22+
info = await supervisor_client.homeassistant.info()
23+
24+
assert info.version == "2024.9.0"
25+
assert info.update_available is False
26+
assert info.arch == "aarch64"
27+
assert info.ssl is False
28+
assert info.port == 8123
29+
assert info.audio_output is None
30+
31+
32+
async def test_homeassistant_stats(
33+
responses: aioresponses, supervisor_client: SupervisorClient
34+
) -> None:
35+
"""Test Home Assistant stats API."""
36+
responses.get(
37+
f"{SUPERVISOR_URL}/core/stats",
38+
status=200,
39+
body=load_fixture("homeassistant_stats.json"),
40+
)
41+
stats = await supervisor_client.homeassistant.stats()
42+
43+
assert stats.cpu_percent == 0.01
44+
assert stats.memory_usage == 678883328
45+
assert stats.memory_percent == 17.41
46+
47+
48+
async def test_homeassistant_options(
49+
responses: aioresponses, supervisor_client: SupervisorClient
50+
) -> None:
51+
"""Test Home Assistant options API."""
52+
responses.post(f"{SUPERVISOR_URL}/core/options", status=200)
53+
assert (
54+
await supervisor_client.homeassistant.options(
55+
HomeAssistantOptions(watchdog=False, backups_exclude_database=True)
56+
)
57+
is None
58+
)
59+
assert responses.requests.keys() == {
60+
("POST", URL(f"{SUPERVISOR_URL}/core/options"))
61+
}
62+
63+
64+
async def test_homeassistant_update(
65+
responses: aioresponses, supervisor_client: SupervisorClient
66+
) -> None:
67+
"""Test Home Assistant update API."""
68+
responses.post(f"{SUPERVISOR_URL}/core/update", status=200)
69+
assert await supervisor_client.homeassistant.update() is None
70+
assert responses.requests.keys() == {("POST", URL(f"{SUPERVISOR_URL}/core/update"))}
71+
72+
73+
async def test_homeassistant_restart(
74+
responses: aioresponses, supervisor_client: SupervisorClient
75+
) -> None:
76+
"""Test Home Assistant restart API."""
77+
responses.post(f"{SUPERVISOR_URL}/core/restart", status=200)
78+
assert await supervisor_client.homeassistant.restart() is None
79+
assert responses.requests.keys() == {
80+
("POST", URL(f"{SUPERVISOR_URL}/core/restart"))
81+
}
82+
83+
84+
async def test_homeassistant_stop(
85+
responses: aioresponses, supervisor_client: SupervisorClient
86+
) -> None:
87+
"""Test Home Assistant stop API."""
88+
responses.post(f"{SUPERVISOR_URL}/core/stop", status=200)
89+
assert await supervisor_client.homeassistant.stop() is None
90+
assert responses.requests.keys() == {("POST", URL(f"{SUPERVISOR_URL}/core/stop"))}
91+
92+
93+
async def test_homeassistant_start(
94+
responses: aioresponses, supervisor_client: SupervisorClient
95+
) -> None:
96+
"""Test Home Assistant start API."""
97+
responses.post(f"{SUPERVISOR_URL}/core/start", status=200)
98+
assert await supervisor_client.homeassistant.start() is None
99+
assert responses.requests.keys() == {("POST", URL(f"{SUPERVISOR_URL}/core/start"))}
100+
101+
102+
async def test_homeassistant_check_config(
103+
responses: aioresponses, supervisor_client: SupervisorClient
104+
) -> None:
105+
"""Test Home Assistant check config API."""
106+
responses.post(f"{SUPERVISOR_URL}/core/check", status=200)
107+
assert await supervisor_client.homeassistant.check_config() is None
108+
assert responses.requests.keys() == {("POST", URL(f"{SUPERVISOR_URL}/core/check"))}
109+
110+
111+
async def test_homeassistant_rebuild(
112+
responses: aioresponses, supervisor_client: SupervisorClient
113+
) -> None:
114+
"""Test Home Assistant rebuild API."""
115+
responses.post(f"{SUPERVISOR_URL}/core/rebuild", status=200)
116+
assert await supervisor_client.homeassistant.rebuild() is None
117+
assert responses.requests.keys() == {
118+
("POST", URL(f"{SUPERVISOR_URL}/core/rebuild"))
119+
}

0 commit comments

Comments
 (0)