1
0
mirror of https://github.com/home-assistant/core.git synced 2026-02-14 23:28:42 +00:00

Add subscribe preview feature helper to labs (#161778)

This commit is contained in:
Artur Pragacz
2026-02-09 14:03:02 +01:00
committed by GitHub
parent 45babbca92
commit 68cc2dff53
9 changed files with 178 additions and 51 deletions

View File

@@ -73,31 +73,21 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
started = False
async def _async_handle_labs_update(
event: Event[labs.EventLabsUpdatedData],
event_data: labs.EventLabsUpdatedData,
) -> None:
"""Handle labs feature toggle."""
await analytics.save_preferences({ATTR_SNAPSHOTS: event.data["enabled"]})
await analytics.save_preferences({ATTR_SNAPSHOTS: event_data["enabled"]})
if started:
await analytics.async_schedule()
@callback
def _async_labs_event_filter(event_data: labs.EventLabsUpdatedData) -> bool:
"""Filter labs events for this integration's snapshot feature."""
return (
event_data["domain"] == DOMAIN
and event_data["preview_feature"] == LABS_SNAPSHOT_FEATURE
)
async def start_schedule(_event: Event) -> None:
"""Start the send schedule after the started event."""
nonlocal started
started = True
await analytics.async_schedule()
hass.bus.async_listen(
labs.EVENT_LABS_UPDATED,
_async_handle_labs_update,
event_filter=_async_labs_event_filter,
labs.async_subscribe_preview_feature(
hass, DOMAIN, LABS_SNAPSHOT_FEATURE, _async_handle_labs_update
)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_schedule)

View File

@@ -14,7 +14,7 @@ import voluptuous as vol
from homeassistant.components import labs, websocket_api
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
from homeassistant.components.labs import async_listen as async_labs_listen
from homeassistant.components.labs import async_subscribe_preview_feature
from homeassistant.const import (
ATTR_AREA_ID,
ATTR_ENTITY_ID,
@@ -386,14 +386,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
schema=vol.Schema({vol.Optional(CONF_ID): str}),
)
@callback
def new_triggers_conditions_listener() -> None:
async def new_triggers_conditions_listener(
_event_data: labs.EventLabsUpdatedData,
) -> None:
"""Handle new_triggers_conditions flag change."""
hass.async_create_task(
reload_helper.execute_service(ServiceCall(hass, DOMAIN, SERVICE_RELOAD))
)
await reload_helper.execute_service(ServiceCall(hass, DOMAIN, SERVICE_RELOAD))
async_labs_listen(
async_subscribe_preview_feature(
hass,
DOMAIN,
NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG,

View File

@@ -21,6 +21,7 @@ from .const import DOMAIN, LABS_DATA, STORAGE_KEY, STORAGE_VERSION
from .helpers import (
async_is_preview_feature_enabled,
async_listen,
async_subscribe_preview_feature,
async_update_preview_feature,
)
from .models import (
@@ -41,6 +42,7 @@ __all__ = [
"EventLabsUpdatedData",
"async_is_preview_feature_enabled",
"async_listen",
"async_subscribe_preview_feature",
"async_update_preview_feature",
]

View File

@@ -2,7 +2,8 @@
from __future__ import annotations
from collections.abc import Callable
from collections.abc import Callable, Coroutine
from typing import Any
from homeassistant.const import EVENT_LABS_UPDATED
from homeassistant.core import Event, HomeAssistant, callback
@@ -32,6 +33,43 @@ def async_is_preview_feature_enabled(
return (domain, preview_feature) in labs_data.data.preview_feature_status
@callback
def async_subscribe_preview_feature(
hass: HomeAssistant,
domain: str,
preview_feature: str,
listener: Callable[[EventLabsUpdatedData], Coroutine[Any, Any, None]],
) -> Callable[[], None]:
"""Listen for changes to a specific preview feature.
Args:
hass: HomeAssistant instance
domain: Integration domain
preview_feature: Preview feature name
listener: Coroutine function to invoke when the preview feature
is toggled. Receives the event data as argument. Runs eagerly.
Returns:
Callable to unsubscribe from the listener
"""
@callback
def _async_event_filter(event_data: EventLabsUpdatedData) -> bool:
"""Filter labs events for this integration's preview feature."""
return (
event_data["domain"] == domain
and event_data["preview_feature"] == preview_feature
)
async def _handler(event: Event[EventLabsUpdatedData]) -> None:
"""Handle labs feature update event."""
await listener(event.data)
return hass.bus.async_listen(
EVENT_LABS_UPDATED, _handler, event_filter=_async_event_filter
)
@callback
def async_listen(
hass: HomeAssistant,
@@ -51,16 +89,10 @@ def async_listen(
Callable to unsubscribe from the listener
"""
@callback
def _async_feature_updated(event: Event[EventLabsUpdatedData]) -> None:
"""Handle labs feature update event."""
if (
event.data["domain"] == domain
and event.data["preview_feature"] == preview_feature
):
listener()
async def _listener(_event_data: EventLabsUpdatedData) -> None:
listener()
return hass.bus.async_listen(EVENT_LABS_UPDATED, _async_feature_updated)
return async_subscribe_preview_feature(hass, domain, preview_feature, _listener)
async def async_update_preview_feature(

View File

@@ -13,9 +13,10 @@ from homeassistant.core import HomeAssistant, callback
from .const import LABS_DATA
from .helpers import (
async_is_preview_feature_enabled,
async_listen,
async_subscribe_preview_feature,
async_update_preview_feature,
)
from .models import EventLabsUpdatedData
@callback
@@ -132,20 +133,27 @@ def websocket_subscribe_feature(
preview_feature = labs_data.preview_features[preview_feature_id]
@callback
def send_event() -> None:
async def send_event(event_data: EventLabsUpdatedData) -> None:
"""Send feature state to client."""
enabled = async_is_preview_feature_enabled(hass, domain, preview_feature_key)
connection.send_message(
websocket_api.event_message(
msg["id"],
preview_feature.to_dict(enabled=enabled),
preview_feature.to_dict(enabled=event_data["enabled"]),
)
)
connection.subscriptions[msg["id"]] = async_listen(
connection.subscriptions[msg["id"]] = async_subscribe_preview_feature(
hass, domain, preview_feature_key, send_event
)
connection.send_result(msg["id"])
send_event()
connection.send_message(
websocket_api.event_message(
msg["id"],
preview_feature.to_dict(
enabled=async_is_preview_feature_enabled(
hass, domain, preview_feature_key
)
),
)
)

View File

@@ -13,7 +13,10 @@ import voluptuous as vol
from homeassistant.components import automation, websocket_api
from homeassistant.components.blueprint import CONF_USE_BLUEPRINT
from homeassistant.components.labs import async_listen as async_labs_listen
from homeassistant.components.labs import (
EventLabsUpdatedData,
async_subscribe_preview_feature,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
@@ -282,14 +285,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
DOMAIN, SERVICE_TOGGLE, toggle_service, schema=SCRIPT_TURN_ONOFF_SCHEMA
)
@callback
def new_triggers_conditions_listener() -> None:
async def new_triggers_conditions_listener(
_event_data: EventLabsUpdatedData,
) -> None:
"""Handle new_triggers_conditions flag change."""
hass.async_create_task(
reload_service(ServiceCall(hass, DOMAIN, SERVICE_RELOAD))
)
await reload_service(ServiceCall(hass, DOMAIN, SERVICE_RELOAD))
async_labs_listen(
async_subscribe_preview_feature(
hass,
automation.DOMAIN,
automation.NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG,

View File

@@ -173,14 +173,15 @@ async def async_setup(hass: HomeAssistant) -> None:
hass.data[CONDITION_PLATFORM_SUBSCRIPTIONS] = []
hass.data[CONDITIONS] = {}
@callback
def new_triggers_conditions_listener() -> None:
async def new_triggers_conditions_listener(
_event_data: labs.EventLabsUpdatedData,
) -> None:
"""Handle new_triggers_conditions flag change."""
# Invalidate the cache
hass.data[CONDITION_DESCRIPTION_CACHE] = {}
hass.data[CONDITION_DISABLED_CONDITIONS] = set()
labs.async_listen(
labs.async_subscribe_preview_feature(
hass,
automation.DOMAIN,
automation.NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG,

View File

@@ -149,14 +149,15 @@ async def async_setup(hass: HomeAssistant) -> None:
hass.data[TRIGGER_PLATFORM_SUBSCRIPTIONS] = []
hass.data[TRIGGERS] = {}
@callback
def new_triggers_conditions_listener() -> None:
async def new_triggers_conditions_listener(
_event_data: labs.EventLabsUpdatedData,
) -> None:
"""Handle new_triggers_conditions flag change."""
# Invalidate the cache
hass.data[TRIGGER_DESCRIPTION_CACHE] = {}
hass.data[TRIGGER_DISABLED_TRIGGERS] = set()
labs.async_listen(
labs.async_subscribe_preview_feature(
hass,
automation.DOMAIN,
automation.NEW_TRIGGERS_CONDITIONS_FEATURE_FLAG,

View File

@@ -9,8 +9,10 @@ import pytest
from homeassistant.components.labs import (
EVENT_LABS_UPDATED,
EventLabsUpdatedData,
async_is_preview_feature_enabled,
async_listen,
async_subscribe_preview_feature,
async_update_preview_feature,
)
from homeassistant.components.labs.const import DOMAIN, LABS_DATA
@@ -441,6 +443,96 @@ async def test_async_listen_helper(hass: HomeAssistant) -> None:
assert len(listener_calls) == 1
async def test_async_subscribe_preview_feature_helper(hass: HomeAssistant) -> None:
"""Test async_subscribe_preview_feature helper."""
hass.config.components.add("kitchen_sink")
assert await async_setup_component(hass, DOMAIN, {})
await hass.async_block_till_done()
calls: list[EventLabsUpdatedData] = []
async def listener(event_data: EventLabsUpdatedData) -> None:
"""Test listener callback."""
calls.append(event_data)
unsub = async_subscribe_preview_feature(
hass,
domain="kitchen_sink",
preview_feature="special_repair",
listener=listener,
)
# Fire event for the subscribed feature
hass.bus.async_fire(
EVENT_LABS_UPDATED,
{
"domain": "kitchen_sink",
"preview_feature": "special_repair",
"enabled": True,
},
)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0]["enabled"] is True
# Fire event for a different feature - should not trigger listener
hass.bus.async_fire(
EVENT_LABS_UPDATED,
{
"domain": "kitchen_sink",
"preview_feature": "other_feature",
"enabled": True,
},
)
await hass.async_block_till_done()
assert len(calls) == 1
# Fire event for a different domain - should not trigger listener
hass.bus.async_fire(
EVENT_LABS_UPDATED,
{
"domain": "other_domain",
"preview_feature": "special_repair",
"enabled": True,
},
)
await hass.async_block_till_done()
assert len(calls) == 1
# Fire event with enabled=False
hass.bus.async_fire(
EVENT_LABS_UPDATED,
{
"domain": "kitchen_sink",
"preview_feature": "special_repair",
"enabled": False,
},
)
await hass.async_block_till_done()
assert len(calls) == 2
assert calls[1]["enabled"] is False
# Test unsubscribe
unsub()
hass.bus.async_fire(
EVENT_LABS_UPDATED,
{
"domain": "kitchen_sink",
"preview_feature": "special_repair",
"enabled": True,
},
)
await hass.async_block_till_done()
assert len(calls) == 2
async def test_async_update_preview_feature(
hass: HomeAssistant, hass_storage: dict[str, Any]
) -> None: