Skip to content

Commit 43e96bc

Browse files
committed
Add discovery APIs from supervisor
1 parent 00c4938 commit 43e96bc

File tree

8 files changed

+233
-0
lines changed

8 files changed

+233
-0
lines changed

aiohasupervisor/discovery.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Discovery client for supervisor."""
2+
3+
from uuid import UUID
4+
5+
from .client import _SupervisorComponentClient
6+
from .const import ResponseType
7+
from .models.discovery import Discovery, DiscoveryConfig, DiscoveryList, SetDiscovery
8+
9+
10+
class DiscoveryClient(_SupervisorComponentClient):
11+
"""Handles discovery access in supervisor."""
12+
13+
async def list(self) -> DiscoveryList:
14+
"""List discovered and available services."""
15+
result = await self._client.get("discovery")
16+
return DiscoveryList.from_dict(result.data)
17+
18+
async def get(self, uuid: UUID) -> Discovery:
19+
"""Get discovery details for a service."""
20+
result = await self._client.get(f"discovery/{uuid.hex}")
21+
return Discovery.from_dict(result.data)
22+
23+
async def delete(self, uuid: UUID) -> None:
24+
"""Remove discovery for a service."""
25+
await self._client.delete(f"discovery/{uuid.hex}")
26+
27+
async def set(self, config: DiscoveryConfig) -> UUID:
28+
"""Inform supervisor of an available service."""
29+
result = await self._client.post(
30+
"discovery", json=config.to_dict(), response_type=ResponseType.JSON
31+
)
32+
return SetDiscovery.from_dict(result.data).uuid

aiohasupervisor/models/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
StoreInfo,
2424
SupervisorRole,
2525
)
26+
from aiohasupervisor.models.discovery import (
27+
Discovery,
28+
DiscoveryConfig,
29+
DiscoveryList,
30+
)
2631
from aiohasupervisor.models.resolution import (
2732
Check,
2833
CheckOptions,
@@ -86,4 +91,7 @@
8691
"SuggestionType",
8792
"UnhealthyReason",
8893
"UnsupportedReason",
94+
"Discovery",
95+
"DiscoveryConfig",
96+
"DiscoveryList",
8997
]
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"""Models for discovery component."""
2+
3+
from dataclasses import dataclass
4+
from typing import Any
5+
from uuid import UUID
6+
7+
from .base import Request, ResponseData
8+
9+
10+
@dataclass(frozen=True, slots=True)
11+
class DiscoveryConfig(Request):
12+
"""DiscoveryConfig model."""
13+
14+
service: str
15+
config: dict[str, Any]
16+
17+
18+
@dataclass(frozen=True, slots=True)
19+
class Discovery(ResponseData):
20+
"""Discovery model."""
21+
22+
addon: str
23+
service: str
24+
uuid: UUID
25+
config: dict[str, Any]
26+
27+
28+
@dataclass(frozen=True, slots=True)
29+
class DiscoveryList(ResponseData):
30+
"""DiscoveryList model."""
31+
32+
discovery: list[Discovery]
33+
services: dict[str, list[str]]
34+
35+
36+
@dataclass(frozen=True, slots=True)
37+
class SetDiscovery(ResponseData):
38+
"""SetDiscovery model."""
39+
40+
uuid: UUID

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 .discovery import DiscoveryClient
910
from .models.root import AvailableUpdate, AvailableUpdates, RootInfo
1011
from .resolution import ResolutionClient
1112
from .store import StoreClient
@@ -24,6 +25,7 @@ def __init__(
2425
"""Initialize client."""
2526
self._client = _SupervisorClient(api_host, token, request_timeout, session)
2627
self._addons = AddonsClient(self._client)
28+
self._discovery = DiscoveryClient(self._client)
2729
self._resolution = ResolutionClient(self._client)
2830
self._store = StoreClient(self._client)
2931

@@ -32,6 +34,11 @@ def addons(self) -> AddonsClient:
3234
"""Get addons component client."""
3335
return self._addons
3436

37+
@property
38+
def discovery(self) -> DiscoveryClient:
39+
"""Get discovery component client."""
40+
return self._discovery
41+
3542
@property
3643
def resolution(self) -> ResolutionClient:
3744
"""Get resolution center component client."""

tests/fixtures/discovery_get.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"addon": "core_mosquitto",
5+
"service": "mqtt",
6+
"uuid": "889ca604cff84004894e53d181655b3a",
7+
"config": {
8+
"host": "core-mosquitto",
9+
"port": 1883,
10+
"ssl": false,
11+
"protocol": "3.1.1",
12+
"username": "homeassistant",
13+
"password": "abc123"
14+
}
15+
}
16+
}

tests/fixtures/discovery_list.json

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"discovery": [
5+
{
6+
"addon": "core_mosquitto",
7+
"service": "mqtt",
8+
"uuid": "889ca604cff84004894e53d181655b3a",
9+
"config": {
10+
"host": "core-mosquitto",
11+
"port": 1883,
12+
"ssl": false,
13+
"protocol": "3.1.1",
14+
"username": "homeassistant",
15+
"password": "abc123"
16+
}
17+
}
18+
],
19+
"services": {
20+
"vlc_telnet": ["core_vlc"],
21+
"esphome": [
22+
"5c53de3b_esphome-beta",
23+
"5c53de3b_esphome-dev",
24+
"5c53de3b_esphome"
25+
],
26+
"zwave_js": [
27+
"core_zwave_js",
28+
"a0d7b954_zwavejs2mqtt",
29+
"77f1785d_zwave_mock_server"
30+
],
31+
"matter": ["core_matter_server"],
32+
"wyoming": [
33+
"core_whisper",
34+
"core_assist_microphone",
35+
"core_piper",
36+
"core_openwakeword"
37+
],
38+
"otbr": ["core_openthread_border_router", "core_silabs_multiprotocol"],
39+
"mqtt": ["core_mosquitto"],
40+
"motioneye": ["a0d7b954_motioneye"],
41+
"deconz": ["core_deconz"],
42+
"adguard": ["a0d7b954_adguard"]
43+
}
44+
}
45+
}

tests/fixtures/discovery_set.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"result": "ok",
3+
"data": {
4+
"uuid": "889ca604cff84004894e53d181655b3a"
5+
}
6+
}

tests/test_discovery.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
"""Test discovery supervisor client."""
2+
3+
from uuid import UUID
4+
5+
from aioresponses import aioresponses
6+
from yarl import URL
7+
8+
from aiohasupervisor import SupervisorClient
9+
from aiohasupervisor.models import DiscoveryConfig
10+
11+
from . import load_fixture
12+
from .const import SUPERVISOR_URL
13+
14+
15+
async def test_discovery_list(
16+
responses: aioresponses, supervisor_client: SupervisorClient
17+
) -> None:
18+
"""Test Discovery list API."""
19+
responses.get(
20+
f"{SUPERVISOR_URL}/discovery",
21+
status=200,
22+
body=load_fixture("discovery_list.json"),
23+
)
24+
disc_list = await supervisor_client.discovery.list()
25+
assert disc_list.discovery[0].addon == "core_mosquitto"
26+
assert disc_list.discovery[0].service == "mqtt"
27+
assert disc_list.discovery[0].uuid.hex == "889ca604cff84004894e53d181655b3a"
28+
assert disc_list.discovery[0].config["host"] == "core-mosquitto"
29+
assert disc_list.services["vlc_telnet"] == ["core_vlc"]
30+
assert disc_list.services["zwave_js"] == [
31+
"core_zwave_js",
32+
"a0d7b954_zwavejs2mqtt",
33+
"77f1785d_zwave_mock_server",
34+
]
35+
36+
37+
async def test_get_discovery(
38+
responses: aioresponses, supervisor_client: SupervisorClient
39+
) -> None:
40+
"""Test Discovery get API."""
41+
uuid = UUID("889ca604cff84004894e53d181655b3a")
42+
responses.get(
43+
f"{SUPERVISOR_URL}/discovery/{uuid.hex}",
44+
status=200,
45+
body=load_fixture("discovery_get.json"),
46+
)
47+
discovery = await supervisor_client.discovery.get(uuid)
48+
assert discovery.addon == "core_mosquitto"
49+
assert discovery.service == "mqtt"
50+
assert discovery.uuid == uuid
51+
assert discovery.config["host"] == "core-mosquitto"
52+
assert discovery.config["port"] == 1883
53+
assert discovery.config["ssl"] is False
54+
55+
56+
async def test_delete_discovery(
57+
responses: aioresponses, supervisor_client: SupervisorClient
58+
) -> None:
59+
"""Test Discovery delete API."""
60+
uuid = UUID("889ca604cff84004894e53d181655b3a")
61+
responses.delete(f"{SUPERVISOR_URL}/discovery/{uuid.hex}", status=200)
62+
assert await supervisor_client.discovery.delete(uuid) is None
63+
assert responses.requests.keys() == {
64+
("DELETE", URL(f"{SUPERVISOR_URL}/discovery/{uuid.hex}"))
65+
}
66+
67+
68+
async def test_set_discovery(
69+
responses: aioresponses, supervisor_client: SupervisorClient
70+
) -> None:
71+
"""Test Discovery set API."""
72+
responses.post(
73+
f"{SUPERVISOR_URL}/discovery",
74+
status=200,
75+
body=load_fixture("discovery_set.json"),
76+
)
77+
assert await supervisor_client.discovery.set(
78+
DiscoveryConfig(service="mqtt", config={})
79+
) == UUID("889ca604cff84004894e53d181655b3a")

0 commit comments

Comments
 (0)