Add support for JSON fragments (#107213)
This commit is contained in:
parent
50edc334de
commit
d04e2d56da
11 changed files with 289 additions and 103 deletions
|
@ -87,7 +87,7 @@ from .helpers.deprecation import (
|
|||
check_if_deprecated_constant,
|
||||
dir_with_deprecated_constants,
|
||||
)
|
||||
from .helpers.json import json_dumps
|
||||
from .helpers.json import json_dumps, json_fragment
|
||||
from .util import dt as dt_util, location
|
||||
from .util.async_ import (
|
||||
cancelling,
|
||||
|
@ -996,8 +996,6 @@ class HomeAssistant:
|
|||
class Context:
|
||||
"""The context that triggered something."""
|
||||
|
||||
__slots__ = ("user_id", "parent_id", "id", "origin_event", "_as_dict")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
|
@ -1009,23 +1007,37 @@ class Context:
|
|||
self.user_id = user_id
|
||||
self.parent_id = parent_id
|
||||
self.origin_event: Event | None = None
|
||||
self._as_dict: ReadOnlyDict[str, str | None] | None = None
|
||||
|
||||
def __eq__(self, other: Any) -> bool:
|
||||
"""Compare contexts."""
|
||||
return bool(self.__class__ == other.__class__ and self.id == other.id)
|
||||
|
||||
@cached_property
|
||||
def _as_dict(self) -> dict[str, str | None]:
|
||||
"""Return a dictionary representation of the context.
|
||||
|
||||
Callers should be careful to not mutate the returned dictionary
|
||||
as it will mutate the cached version.
|
||||
"""
|
||||
return {
|
||||
"id": self.id,
|
||||
"parent_id": self.parent_id,
|
||||
"user_id": self.user_id,
|
||||
}
|
||||
|
||||
def as_dict(self) -> ReadOnlyDict[str, str | None]:
|
||||
"""Return a dictionary representation of the context."""
|
||||
if not self._as_dict:
|
||||
self._as_dict = ReadOnlyDict(
|
||||
{
|
||||
"id": self.id,
|
||||
"parent_id": self.parent_id,
|
||||
"user_id": self.user_id,
|
||||
}
|
||||
)
|
||||
return self._as_dict
|
||||
"""Return a ReadOnlyDict representation of the context."""
|
||||
return self._as_read_only_dict
|
||||
|
||||
@cached_property
|
||||
def _as_read_only_dict(self) -> ReadOnlyDict[str, str | None]:
|
||||
"""Return a ReadOnlyDict representation of the context."""
|
||||
return ReadOnlyDict(self._as_dict)
|
||||
|
||||
@cached_property
|
||||
def json_fragment(self) -> json_fragment:
|
||||
"""Return a JSON fragment of the context."""
|
||||
return json_fragment(json_dumps(self._as_dict))
|
||||
|
||||
|
||||
class EventOrigin(enum.Enum):
|
||||
|
@ -1042,8 +1054,6 @@ class EventOrigin(enum.Enum):
|
|||
class Event:
|
||||
"""Representation of an event within the bus."""
|
||||
|
||||
__slots__ = ("event_type", "data", "origin", "time_fired", "context", "_as_dict")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
event_type: str,
|
||||
|
@ -1062,26 +1072,54 @@ class Event:
|
|||
id=ulid_at_time(dt_util.utc_to_timestamp(self.time_fired))
|
||||
)
|
||||
self.context = context
|
||||
self._as_dict: ReadOnlyDict[str, Any] | None = None
|
||||
if not context.origin_event:
|
||||
context.origin_event = self
|
||||
|
||||
def as_dict(self) -> ReadOnlyDict[str, Any]:
|
||||
@cached_property
|
||||
def _as_dict(self) -> dict[str, Any]:
|
||||
"""Create a dict representation of this Event.
|
||||
|
||||
Callers should be careful to not mutate the returned dictionary
|
||||
as it will mutate the cached version.
|
||||
"""
|
||||
return {
|
||||
"event_type": self.event_type,
|
||||
"data": self.data,
|
||||
"origin": self.origin.value,
|
||||
"time_fired": self.time_fired.isoformat(),
|
||||
# _as_dict is marked as protected
|
||||
# to avoid callers outside of this module
|
||||
# from misusing it by mistake.
|
||||
"context": self.context._as_dict, # pylint: disable=protected-access
|
||||
}
|
||||
|
||||
def as_dict(self) -> ReadOnlyDict[str, Any]:
|
||||
"""Create a ReadOnlyDict representation of this Event.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
if not self._as_dict:
|
||||
self._as_dict = ReadOnlyDict(
|
||||
{
|
||||
"event_type": self.event_type,
|
||||
"data": ReadOnlyDict(self.data),
|
||||
"origin": self.origin.value,
|
||||
"time_fired": self.time_fired.isoformat(),
|
||||
"context": self.context.as_dict(),
|
||||
}
|
||||
)
|
||||
return self._as_dict
|
||||
return self._as_read_only_dict
|
||||
|
||||
@cached_property
|
||||
def _as_read_only_dict(self) -> ReadOnlyDict[str, Any]:
|
||||
"""Create a ReadOnlyDict representation of this Event."""
|
||||
as_dict = self._as_dict
|
||||
data = as_dict["data"]
|
||||
context = as_dict["context"]
|
||||
# json_fragment will serialize data from a ReadOnlyDict
|
||||
# or a normal dict so its ok to have either. We only
|
||||
# mutate the cache if someone asks for the as_dict version
|
||||
# to avoid storing multiple copies of the data in memory.
|
||||
if type(data) is not ReadOnlyDict:
|
||||
as_dict["data"] = ReadOnlyDict(data)
|
||||
if type(context) is not ReadOnlyDict:
|
||||
as_dict["context"] = ReadOnlyDict(context)
|
||||
return ReadOnlyDict(as_dict)
|
||||
|
||||
@cached_property
|
||||
def json_fragment(self) -> json_fragment:
|
||||
"""Return an event as a JSON fragment."""
|
||||
return json_fragment(json_dumps(self._as_dict))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return the representation."""
|
||||
|
@ -1397,7 +1435,6 @@ class State:
|
|||
self.context = context or Context()
|
||||
self.state_info = state_info
|
||||
self.domain, self.object_id = split_entity_id(self.entity_id)
|
||||
self._as_dict: ReadOnlyDict[str, Collection[Any]] | None = None
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
|
@ -1406,36 +1443,66 @@ class State:
|
|||
"_", " "
|
||||
)
|
||||
|
||||
def as_dict(self) -> ReadOnlyDict[str, Collection[Any]]:
|
||||
@cached_property
|
||||
def _as_dict(self) -> dict[str, Any]:
|
||||
"""Return a dict representation of the State.
|
||||
|
||||
Callers should be careful to not mutate the returned dictionary
|
||||
as it will mutate the cached version.
|
||||
"""
|
||||
last_changed_isoformat = self.last_changed.isoformat()
|
||||
if self.last_changed == self.last_updated:
|
||||
last_updated_isoformat = last_changed_isoformat
|
||||
else:
|
||||
last_updated_isoformat = self.last_updated.isoformat()
|
||||
return {
|
||||
"entity_id": self.entity_id,
|
||||
"state": self.state,
|
||||
"attributes": self.attributes,
|
||||
"last_changed": last_changed_isoformat,
|
||||
"last_updated": last_updated_isoformat,
|
||||
# _as_dict is marked as protected
|
||||
# to avoid callers outside of this module
|
||||
# from misusing it by mistake.
|
||||
"context": self.context._as_dict, # pylint: disable=protected-access
|
||||
}
|
||||
|
||||
def as_dict(
|
||||
self,
|
||||
) -> ReadOnlyDict[str, datetime.datetime | Collection[Any]]:
|
||||
"""Return a ReadOnlyDict representation of the State.
|
||||
|
||||
Async friendly.
|
||||
|
||||
To be used for JSON serialization.
|
||||
Can be used for JSON serialization.
|
||||
Ensures: state == State.from_dict(state.as_dict())
|
||||
"""
|
||||
if not self._as_dict:
|
||||
last_changed_isoformat = self.last_changed.isoformat()
|
||||
if self.last_changed == self.last_updated:
|
||||
last_updated_isoformat = last_changed_isoformat
|
||||
else:
|
||||
last_updated_isoformat = self.last_updated.isoformat()
|
||||
self._as_dict = ReadOnlyDict(
|
||||
{
|
||||
"entity_id": self.entity_id,
|
||||
"state": self.state,
|
||||
"attributes": self.attributes,
|
||||
"last_changed": last_changed_isoformat,
|
||||
"last_updated": last_updated_isoformat,
|
||||
"context": self.context.as_dict(),
|
||||
}
|
||||
)
|
||||
return self._as_dict
|
||||
return self._as_read_only_dict
|
||||
|
||||
@cached_property
|
||||
def _as_read_only_dict(
|
||||
self,
|
||||
) -> ReadOnlyDict[str, datetime.datetime | Collection[Any]]:
|
||||
"""Return a ReadOnlyDict representation of the State."""
|
||||
as_dict = self._as_dict
|
||||
context = as_dict["context"]
|
||||
# json_fragment will serialize data from a ReadOnlyDict
|
||||
# or a normal dict so its ok to have either. We only
|
||||
# mutate the cache if someone asks for the as_dict version
|
||||
# to avoid storing multiple copies of the data in memory.
|
||||
if type(context) is not ReadOnlyDict:
|
||||
as_dict["context"] = ReadOnlyDict(context)
|
||||
return ReadOnlyDict(as_dict)
|
||||
|
||||
@cached_property
|
||||
def as_dict_json(self) -> str:
|
||||
"""Return a JSON string of the State."""
|
||||
return json_dumps(self.as_dict())
|
||||
return json_dumps(self._as_dict)
|
||||
|
||||
@cached_property
|
||||
def json_fragment(self) -> json_fragment:
|
||||
"""Return a JSON fragment of the State."""
|
||||
return json_fragment(self.as_dict_json)
|
||||
|
||||
@cached_property
|
||||
def as_compressed_state(self) -> dict[str, Any]:
|
||||
|
@ -1449,7 +1516,10 @@ class State:
|
|||
if state_context.parent_id is None and state_context.user_id is None:
|
||||
context: dict[str, Any] | str = state_context.id
|
||||
else:
|
||||
context = state_context.as_dict()
|
||||
# _as_dict is marked as protected
|
||||
# to avoid callers outside of this module
|
||||
# from misusing it by mistake.
|
||||
context = state_context._as_dict # pylint: disable=protected-access
|
||||
compressed_state = {
|
||||
COMPRESSED_STATE_STATE: self.state,
|
||||
COMPRESSED_STATE_ATTRIBUTES: self.attributes,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue