Skip to content

Commit 66bddeb

Browse files
authored
Add subscribe preview feature endpoint to labs (home-assistant#157976)
1 parent 2280d77 commit 66bddeb

File tree

4 files changed

+312
-54
lines changed

4 files changed

+312
-54
lines changed

homeassistant/components/labs/__init__.py

Lines changed: 2 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,18 @@
77

88
from __future__ import annotations
99

10-
from collections.abc import Callable
1110
import logging
1211

1312
from homeassistant.const import EVENT_LABS_UPDATED
14-
from homeassistant.core import Event, HomeAssistant, callback
13+
from homeassistant.core import HomeAssistant
1514
from homeassistant.generated.labs import LABS_PREVIEW_FEATURES
1615
from homeassistant.helpers import config_validation as cv
1716
from homeassistant.helpers.storage import Store
1817
from homeassistant.helpers.typing import ConfigType
1918
from homeassistant.loader import async_get_custom_components
2019

2120
from .const import DOMAIN, LABS_DATA, STORAGE_KEY, STORAGE_VERSION
21+
from .helpers import async_is_preview_feature_enabled, async_listen
2222
from .models import (
2323
EventLabsUpdatedData,
2424
LabPreviewFeature,
@@ -135,55 +135,3 @@ async def _async_scan_all_preview_features(
135135

136136
_LOGGER.debug("Loaded %d total lab preview features", len(preview_features))
137137
return preview_features
138-
139-
140-
@callback
141-
def async_is_preview_feature_enabled(
142-
hass: HomeAssistant, domain: str, preview_feature: str
143-
) -> bool:
144-
"""Check if a lab preview feature is enabled.
145-
146-
Args:
147-
hass: HomeAssistant instance
148-
domain: Integration domain
149-
preview_feature: Preview feature name
150-
151-
Returns:
152-
True if the preview feature is enabled, False otherwise
153-
"""
154-
if LABS_DATA not in hass.data:
155-
return False
156-
157-
labs_data = hass.data[LABS_DATA]
158-
return (domain, preview_feature) in labs_data.data.preview_feature_status
159-
160-
161-
@callback
162-
def async_listen(
163-
hass: HomeAssistant,
164-
domain: str,
165-
preview_feature: str,
166-
listener: Callable[[], None],
167-
) -> Callable[[], None]:
168-
"""Listen for changes to a specific preview feature.
169-
170-
Args:
171-
hass: HomeAssistant instance
172-
domain: Integration domain
173-
preview_feature: Preview feature name
174-
listener: Callback to invoke when the preview feature is toggled
175-
176-
Returns:
177-
Callable to unsubscribe from the listener
178-
"""
179-
180-
@callback
181-
def _async_feature_updated(event: Event[EventLabsUpdatedData]) -> None:
182-
"""Handle labs feature update event."""
183-
if (
184-
event.data["domain"] == domain
185-
and event.data["preview_feature"] == preview_feature
186-
):
187-
listener()
188-
189-
return hass.bus.async_listen(EVENT_LABS_UPDATED, _async_feature_updated)
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"""Helper functions for the Home Assistant Labs integration."""
2+
3+
from __future__ import annotations
4+
5+
from collections.abc import Callable
6+
7+
from homeassistant.const import EVENT_LABS_UPDATED
8+
from homeassistant.core import Event, HomeAssistant, callback
9+
10+
from .const import LABS_DATA
11+
from .models import EventLabsUpdatedData
12+
13+
14+
@callback
15+
def async_is_preview_feature_enabled(
16+
hass: HomeAssistant, domain: str, preview_feature: str
17+
) -> bool:
18+
"""Check if a lab preview feature is enabled.
19+
20+
Args:
21+
hass: HomeAssistant instance
22+
domain: Integration domain
23+
preview_feature: Preview feature name
24+
25+
Returns:
26+
True if the preview feature is enabled, False otherwise
27+
"""
28+
if LABS_DATA not in hass.data:
29+
return False
30+
31+
labs_data = hass.data[LABS_DATA]
32+
return (domain, preview_feature) in labs_data.data.preview_feature_status
33+
34+
35+
@callback
36+
def async_listen(
37+
hass: HomeAssistant,
38+
domain: str,
39+
preview_feature: str,
40+
listener: Callable[[], None],
41+
) -> Callable[[], None]:
42+
"""Listen for changes to a specific preview feature.
43+
44+
Args:
45+
hass: HomeAssistant instance
46+
domain: Integration domain
47+
preview_feature: Preview feature name
48+
listener: Callback to invoke when the preview feature is toggled
49+
50+
Returns:
51+
Callable to unsubscribe from the listener
52+
"""
53+
54+
@callback
55+
def _async_feature_updated(event: Event[EventLabsUpdatedData]) -> None:
56+
"""Handle labs feature update event."""
57+
if (
58+
event.data["domain"] == domain
59+
and event.data["preview_feature"] == preview_feature
60+
):
61+
listener()
62+
63+
return hass.bus.async_listen(EVENT_LABS_UPDATED, _async_feature_updated)

homeassistant/components/labs/websocket_api.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from homeassistant.core import HomeAssistant, callback
1313

1414
from .const import LABS_DATA
15+
from .helpers import async_is_preview_feature_enabled, async_listen
1516
from .models import EventLabsUpdatedData
1617

1718

@@ -20,6 +21,7 @@ def async_setup(hass: HomeAssistant) -> None:
2021
"""Set up the number websocket API."""
2122
websocket_api.async_register_command(hass, websocket_list_preview_features)
2223
websocket_api.async_register_command(hass, websocket_update_preview_feature)
24+
websocket_api.async_register_command(hass, websocket_subscribe_feature)
2325

2426

2527
@callback
@@ -108,3 +110,52 @@ async def websocket_update_preview_feature(
108110
hass.bus.async_fire(EVENT_LABS_UPDATED, event_data)
109111

110112
connection.send_result(msg["id"])
113+
114+
115+
@callback
116+
@websocket_api.websocket_command(
117+
{
118+
vol.Required("type"): "labs/subscribe",
119+
vol.Required("domain"): str,
120+
vol.Required("preview_feature"): str,
121+
}
122+
)
123+
def websocket_subscribe_feature(
124+
hass: HomeAssistant,
125+
connection: websocket_api.ActiveConnection,
126+
msg: dict[str, Any],
127+
) -> None:
128+
"""Subscribe to a specific lab preview feature updates."""
129+
domain = msg["domain"]
130+
preview_feature_key = msg["preview_feature"]
131+
labs_data = hass.data[LABS_DATA]
132+
133+
preview_feature_id = f"{domain}.{preview_feature_key}"
134+
135+
if preview_feature_id not in labs_data.preview_features:
136+
connection.send_error(
137+
msg["id"],
138+
websocket_api.ERR_NOT_FOUND,
139+
f"Preview feature {preview_feature_id} not found",
140+
)
141+
return
142+
143+
preview_feature = labs_data.preview_features[preview_feature_id]
144+
145+
@callback
146+
def send_event() -> None:
147+
"""Send feature state to client."""
148+
enabled = async_is_preview_feature_enabled(hass, domain, preview_feature_key)
149+
connection.send_message(
150+
websocket_api.event_message(
151+
msg["id"],
152+
preview_feature.to_dict(enabled=enabled),
153+
)
154+
)
155+
156+
connection.subscriptions[msg["id"]] = async_listen(
157+
hass, domain, preview_feature_key, send_event
158+
)
159+
160+
connection.send_result(msg["id"])
161+
send_event()

0 commit comments

Comments
 (0)