From 07d1a16efd4930c23f565ff0b714d38ed9e36bd7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 28 Apr 2023 21:15:39 +0200 Subject: [PATCH] Add as_dict cache to Context and Event (#92162) --- .../homeassistant/triggers/event.py | 2 +- homeassistant/core.py | 44 ++++++++++++------- tests/components/recorder/test_models.py | 4 +- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/homeassistant/components/homeassistant/triggers/event.py b/homeassistant/components/homeassistant/triggers/event.py index d84b04c3652..d0e74d5b04e 100644 --- a/homeassistant/components/homeassistant/triggers/event.py +++ b/homeassistant/components/homeassistant/triggers/event.py @@ -91,7 +91,7 @@ async def async_attach_trigger( if event_data_schema: event_data_schema(event.data) if event_context_schema: - event_context_schema(event.context.as_dict()) + event_context_schema(dict(event.context.as_dict())) except vol.Invalid: # If event doesn't match, skip event return False diff --git a/homeassistant/core.py b/homeassistant/core.py index f7cfcf44205..48c69f35956 100644 --- a/homeassistant/core.py +++ b/homeassistant/core.py @@ -850,7 +850,7 @@ class HomeAssistant: class Context: """The context that triggered something.""" - __slots__ = ("user_id", "parent_id", "id", "origin_event") + __slots__ = ("user_id", "parent_id", "id", "origin_event", "_as_dict") def __init__( self, @@ -863,14 +863,23 @@ 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) - def as_dict(self) -> dict[str, str | None]: + def as_dict(self) -> ReadOnlyDict[str, str | None]: """Return a dictionary representation of the context.""" - return {"id": self.id, "parent_id": self.parent_id, "user_id": self.user_id} + 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 class EventOrigin(enum.Enum): @@ -887,7 +896,7 @@ class EventOrigin(enum.Enum): class Event: """Representation of an event within the bus.""" - __slots__ = ["event_type", "data", "origin", "time_fired", "context"] + __slots__ = ("event_type", "data", "origin", "time_fired", "context", "_as_dict") def __init__( self, @@ -905,19 +914,24 @@ class Event: self.context: Context = context or Context( id=ulid_util.ulid_at_time(dt_util.utc_to_timestamp(self.time_fired)) ) + self._as_dict: ReadOnlyDict[str, Any] | None = None - def as_dict(self) -> dict[str, Any]: + def as_dict(self) -> ReadOnlyDict[str, Any]: """Create a dict representation of this Event. Async friendly. """ - return { - "event_type": self.event_type, - "data": dict(self.data), - "origin": str(self.origin.value), - "time_fired": self.time_fired.isoformat(), - "context": self.context.as_dict(), - } + if not self._as_dict: + self._as_dict = ReadOnlyDict( + { + "event_type": self.event_type, + "data": ReadOnlyDict(self.data), + "origin": str(self.origin.value), + "time_fired": self.time_fired.isoformat(), + "context": self.context.as_dict(), + } + ) + return self._as_dict def __repr__(self) -> str: """Return the representation.""" @@ -1189,7 +1203,7 @@ class State: object_id: Object id of this state. """ - __slots__ = [ + __slots__ = ( "entity_id", "state", "attributes", @@ -1200,7 +1214,7 @@ class State: "object_id", "_as_dict", "_as_compressed_state", - ] + ) def __init__( self, @@ -1265,7 +1279,7 @@ class State: "attributes": self.attributes, "last_changed": last_changed_isoformat, "last_updated": last_updated_isoformat, - "context": ReadOnlyDict(self.context.as_dict()), + "context": self.context.as_dict(), } ) return self._as_dict diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index f47f1d3e78b..8797c153a7b 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -256,7 +256,9 @@ async def test_event_to_db_model() -> None: assert native.as_dict() == event.as_dict() native = Events.from_event(event).to_native() - event.data = {} + native.data = ( + event.data + ) # data is not set by from_event as its in the event_data table native.event_type = event.event_type assert native.as_dict() == event.as_dict()