mirror of
https://github.com/home-assistant/core.git
synced 2026-09-27 09:58:07 -04:00
sandbox: wire the service-less query ops onto EntityQuery
Replace the remaining raise_not_proxied stubs with EntityQuery forwards + typed rebuilds, so every query-shaped entity API now answers with real data: - media_player.async_search_media -> async_internal_search_media (which rebuilds the SearchMediaQuery from flat kwargs on the sandbox side, so the query crosses as plain JSON); rebuilds SearchMedia, reusing the BrowseMedia helper for its result list. - update.async_release_notes -> async_release_notes (plain str/None). - vacuum.async_get_segments -> async_get_segments; rebuilds list[Segment]. - calendar.async_update_event / async_delete_event -> the matching WS-only entity methods (None result). The sandbox-side serialisation is the as_dict-aware JSON encoder already added with the handler, so SearchMedia/BrowseMedia/Segment cross verbatim. raise_not_proxied is now callerless but kept exported for the still-deferred subscription/todo-push primitive. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
50a2d16d9d
commit
07a7b918ca
@@ -5,7 +5,7 @@ from typing import Any
|
||||
|
||||
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||
|
||||
from . import SandboxProxyEntity, raise_not_proxied
|
||||
from . import SandboxProxyEntity
|
||||
|
||||
|
||||
def _parse_calendar_date(value: Any) -> datetime.date | datetime.datetime | Any:
|
||||
@@ -52,11 +52,11 @@ class SandboxCalendarEntity(SandboxProxyEntity, CalendarEntity):
|
||||
|
||||
``create_event`` forwards through the standard ``calendar.create_event``
|
||||
service. The listing query (``async_get_events``) rides the
|
||||
``calendar.get_events`` ``SupportsResponse`` service. The WS-only event
|
||||
edits (``calendar/event/update`` / ``delete``) need the request/response
|
||||
``EntityQuery`` RPC; until that lands they raise. The recurrence-timer
|
||||
subscription (``calendar/event/subscribe``) is deferred — the
|
||||
next/current event is not pushed, so ``event`` returns ``None``. See
|
||||
``calendar.get_events`` ``SupportsResponse`` service; the WS-only event
|
||||
edits (``calendar/event/update`` / ``delete``) cross via the generic
|
||||
``EntityQuery`` RPC. The recurrence-timer subscription
|
||||
(``calendar/event/subscribe``) is deferred — the next/current event is not
|
||||
pushed, so ``event`` returns ``None``. See
|
||||
``sandbox/docs/query-shaped-rpcs.md``.
|
||||
"""
|
||||
|
||||
@@ -92,8 +92,14 @@ class SandboxCalendarEntity(SandboxProxyEntity, CalendarEntity):
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise — ``calendar/event/update`` needs the EntityQuery RPC."""
|
||||
raise_not_proxied("Updating a calendar event")
|
||||
"""Forward the WS-only event update through ``EntityQuery``."""
|
||||
await self._entity_query(
|
||||
"async_update_event",
|
||||
uid=uid,
|
||||
event=event,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=recurrence_range,
|
||||
)
|
||||
|
||||
async def async_delete_event(
|
||||
self,
|
||||
@@ -101,5 +107,10 @@ class SandboxCalendarEntity(SandboxProxyEntity, CalendarEntity):
|
||||
recurrence_id: str | None = None,
|
||||
recurrence_range: str | None = None,
|
||||
) -> None:
|
||||
"""Raise — ``calendar/event/delete`` needs the EntityQuery RPC."""
|
||||
raise_not_proxied("Deleting a calendar event")
|
||||
"""Forward the WS-only event delete through ``EntityQuery``."""
|
||||
await self._entity_query(
|
||||
"async_delete_event",
|
||||
uid=uid,
|
||||
recurrence_id=recurrence_id,
|
||||
recurrence_range=recurrence_range,
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ from homeassistant.components.media_player import (
|
||||
)
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
|
||||
from . import SandboxProxyEntity, raise_not_proxied
|
||||
from . import SandboxProxyEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bridge import SandboxBridge, SandboxEntityDescription
|
||||
@@ -64,6 +64,18 @@ def _browse_media_from_dict(data: dict[str, Any]) -> BrowseMedia:
|
||||
)
|
||||
|
||||
|
||||
def _search_media_from_dict(data: dict[str, Any]) -> SearchMedia:
|
||||
"""Rebuild a :class:`SearchMedia` from its ``as_dict`` shape.
|
||||
|
||||
``SearchMedia.as_dict`` holds its results under ``result`` as a list of
|
||||
``BrowseMedia`` dicts, so the rebuild reuses :func:`_browse_media_from_dict`
|
||||
per item. ``version`` is constructor-defaulted.
|
||||
"""
|
||||
return SearchMedia(
|
||||
result=[_browse_media_from_dict(item) for item in data.get("result", [])]
|
||||
)
|
||||
|
||||
|
||||
# pylint: disable-next=home-assistant-enforce-class-module
|
||||
class SandboxMediaPlayerEntity(SandboxProxyEntity, MediaPlayerEntity):
|
||||
"""Proxy for a ``media_player`` entity in a sandbox."""
|
||||
@@ -276,8 +288,24 @@ class SandboxMediaPlayerEntity(SandboxProxyEntity, MediaPlayerEntity):
|
||||
return _browse_media_from_dict(entity_response)
|
||||
|
||||
async def async_search_media(self, query: SearchMediaQuery) -> SearchMedia:
|
||||
"""Raise — media search is a server-side query, not yet proxied."""
|
||||
raise_not_proxied("Searching media")
|
||||
"""Search via ``EntityQuery`` against the real entity.
|
||||
|
||||
Forwarded to ``async_internal_search_media`` (which rebuilds the
|
||||
``SearchMediaQuery`` from flat kwargs on the sandbox side) rather than
|
||||
``async_search_media``, so the query crosses as plain JSON kwargs.
|
||||
``media_filter_classes`` cross as their ``MediaClass`` string values.
|
||||
"""
|
||||
args: dict[str, Any] = {"search_query": query.search_query}
|
||||
if query.media_content_type is not None:
|
||||
args["media_content_type"] = query.media_content_type
|
||||
if query.media_content_id is not None:
|
||||
args["media_content_id"] = query.media_content_id
|
||||
if query.media_filter_classes is not None:
|
||||
args["media_filter_classes"] = [
|
||||
getattr(item, "value", item) for item in query.media_filter_classes
|
||||
]
|
||||
response = await self._entity_query("async_internal_search_media", **args)
|
||||
return _search_media_from_dict(response or {})
|
||||
|
||||
async def async_clear_playlist(self) -> None:
|
||||
"""Forward clear_playlist."""
|
||||
|
||||
@@ -9,7 +9,7 @@ from homeassistant.components.update import (
|
||||
UpdateEntityFeature,
|
||||
)
|
||||
|
||||
from . import SandboxProxyEntity, raise_not_proxied
|
||||
from . import SandboxProxyEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bridge import SandboxBridge, SandboxEntityDescription
|
||||
@@ -99,5 +99,5 @@ class SandboxUpdateEntity(SandboxProxyEntity, UpdateEntity):
|
||||
await self._call_service("install", **payload)
|
||||
|
||||
async def async_release_notes(self) -> str | None:
|
||||
"""Raise — ``update/release_notes`` is a WS query, not yet proxied."""
|
||||
raise_not_proxied("Fetching update release notes")
|
||||
"""Return the release notes via ``EntityQuery`` (a plain str/None)."""
|
||||
return await self._entity_query("async_release_notes")
|
||||
|
||||
@@ -11,12 +11,17 @@ from homeassistant.components.vacuum import (
|
||||
VacuumEntityFeature,
|
||||
)
|
||||
|
||||
from . import SandboxProxyEntity, raise_not_proxied
|
||||
from . import SandboxProxyEntity
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bridge import SandboxBridge, SandboxEntityDescription
|
||||
|
||||
|
||||
def _segment_from_dict(data: dict[str, Any]) -> Segment:
|
||||
"""Rebuild a :class:`Segment` dataclass from its serialised dict."""
|
||||
return Segment(id=data["id"], name=data["name"], group=data.get("group"))
|
||||
|
||||
|
||||
# pylint: disable-next=home-assistant-enforce-class-module
|
||||
class SandboxVacuumEntity(SandboxProxyEntity, StateVacuumEntity):
|
||||
"""Proxy for a ``vacuum`` entity in a sandbox."""
|
||||
@@ -94,5 +99,6 @@ class SandboxVacuumEntity(SandboxProxyEntity, StateVacuumEntity):
|
||||
await self._call_service("send_command", **payload)
|
||||
|
||||
async def async_get_segments(self) -> list[Segment]:
|
||||
"""Raise — ``vacuum/get_segments`` is a WS query, not yet proxied."""
|
||||
raise_not_proxied("Listing vacuum segments")
|
||||
"""Return the cleanable segments via ``EntityQuery``."""
|
||||
response = await self._entity_query("async_get_segments")
|
||||
return [_segment_from_dict(segment) for segment in response or []]
|
||||
|
||||
Reference in New Issue
Block a user