Make core States use cached_property (#100312)

Need to validate this is worth removing __slots__
This commit is contained in:
J. Nick Koston 2023-09-13 19:33:25 -05:00 committed by GitHub
parent 3cc9410a62
commit 547f32818c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 24 additions and 44 deletions

View File

@ -202,11 +202,11 @@ class APIStatesView(HomeAssistantView):
user: User = request["hass_user"] user: User = request["hass_user"]
hass: HomeAssistant = request.app["hass"] hass: HomeAssistant = request.app["hass"]
if user.is_admin: if user.is_admin:
states = (state.as_dict_json() for state in hass.states.async_all()) states = (state.as_dict_json for state in hass.states.async_all())
else: else:
entity_perm = user.permissions.check_entity entity_perm = user.permissions.check_entity
states = ( states = (
state.as_dict_json() state.as_dict_json
for state in hass.states.async_all() for state in hass.states.async_all()
if entity_perm(state.entity_id, "read") if entity_perm(state.entity_id, "read")
) )
@ -233,7 +233,7 @@ class APIEntityStateView(HomeAssistantView):
if state := hass.states.get(entity_id): if state := hass.states.get(entity_id):
return web.Response( return web.Response(
body=state.as_dict_json(), body=state.as_dict_json,
content_type=CONTENT_TYPE_JSON, content_type=CONTENT_TYPE_JSON,
) )
return self.json_message("Entity not found.", HTTPStatus.NOT_FOUND) return self.json_message("Entity not found.", HTTPStatus.NOT_FOUND)

View File

@ -270,7 +270,7 @@ def handle_get_states(
states = _async_get_allowed_states(hass, connection) states = _async_get_allowed_states(hass, connection)
try: try:
serialized_states = [state.as_dict_json() for state in states] serialized_states = [state.as_dict_json for state in states]
except (ValueError, TypeError): except (ValueError, TypeError):
pass pass
else: else:
@ -281,7 +281,7 @@ def handle_get_states(
serialized_states = [] serialized_states = []
for state in states: for state in states:
try: try:
serialized_states.append(state.as_dict_json()) serialized_states.append(state.as_dict_json)
except (ValueError, TypeError): except (ValueError, TypeError):
connection.logger.error( connection.logger.error(
"Unable to serialize to JSON. Bad data found at %s", "Unable to serialize to JSON. Bad data found at %s",
@ -358,7 +358,7 @@ def handle_subscribe_entities(
# to succeed for the UI to show. # to succeed for the UI to show.
try: try:
serialized_states = [ serialized_states = [
state.as_compressed_state_json() state.as_compressed_state_json
for state in states for state in states
if not entity_ids or state.entity_id in entity_ids if not entity_ids or state.entity_id in entity_ids
] ]
@ -371,7 +371,7 @@ def handle_subscribe_entities(
serialized_states = [] serialized_states = []
for state in states: for state in states:
try: try:
serialized_states.append(state.as_compressed_state_json()) serialized_states.append(state.as_compressed_state_json)
except (ValueError, TypeError): except (ValueError, TypeError):
connection.logger.error( connection.logger.error(
"Unable to serialize to JSON. Bad data found at %s", "Unable to serialize to JSON. Bad data found at %s",

View File

@ -141,7 +141,7 @@ def _state_diff_event(event: Event) -> dict:
if (event_old_state := event.data["old_state"]) is None: if (event_old_state := event.data["old_state"]) is None:
return { return {
ENTITY_EVENT_ADD: { ENTITY_EVENT_ADD: {
event_new_state.entity_id: event_new_state.as_compressed_state() event_new_state.entity_id: event_new_state.as_compressed_state
} }
} }
if TYPE_CHECKING: if TYPE_CHECKING:

View File

@ -35,6 +35,7 @@ import voluptuous as vol
import yarl import yarl
from . import block_async_io, util from . import block_async_io, util
from .backports.functools import cached_property
from .const import ( from .const import (
ATTR_DOMAIN, ATTR_DOMAIN,
ATTR_FRIENDLY_NAME, ATTR_FRIENDLY_NAME,
@ -1239,20 +1240,6 @@ class State:
object_id: Object id of this state. object_id: Object id of this state.
""" """
__slots__ = (
"entity_id",
"state",
"attributes",
"last_changed",
"last_updated",
"context",
"domain",
"object_id",
"_as_dict",
"_as_dict_json",
"_as_compressed_state_json",
)
def __init__( def __init__(
self, self,
entity_id: str, entity_id: str,
@ -1282,8 +1269,6 @@ class State:
self.context = context or Context() self.context = context or Context()
self.domain, self.object_id = split_entity_id(self.entity_id) self.domain, self.object_id = split_entity_id(self.entity_id)
self._as_dict: ReadOnlyDict[str, Collection[Any]] | None = None self._as_dict: ReadOnlyDict[str, Collection[Any]] | None = None
self._as_dict_json: str | None = None
self._as_compressed_state_json: str | None = None
@property @property
def name(self) -> str: def name(self) -> str:
@ -1318,12 +1303,12 @@ class State:
) )
return self._as_dict return self._as_dict
@cached_property
def as_dict_json(self) -> str: def as_dict_json(self) -> str:
"""Return a JSON string of the State.""" """Return a JSON string of the State."""
if not self._as_dict_json: return json_dumps(self.as_dict())
self._as_dict_json = json_dumps(self.as_dict())
return self._as_dict_json
@cached_property
def as_compressed_state(self) -> dict[str, Any]: def as_compressed_state(self) -> dict[str, Any]:
"""Build a compressed dict of a state for adds. """Build a compressed dict of a state for adds.
@ -1348,6 +1333,7 @@ class State:
) )
return compressed_state return compressed_state
@cached_property
def as_compressed_state_json(self) -> str: def as_compressed_state_json(self) -> str:
"""Build a compressed JSON key value pair of a state for adds. """Build a compressed JSON key value pair of a state for adds.
@ -1355,11 +1341,7 @@ class State:
It is used for sending multiple states in a single message. It is used for sending multiple states in a single message.
""" """
if not self._as_compressed_state_json: return json_dumps({self.entity_id: self.as_compressed_state})[1:-1]
self._as_compressed_state_json = json_dumps(
{self.entity_id: self.as_compressed_state()}
)[1:-1]
return self._as_compressed_state_json
@classmethod @classmethod
def from_dict(cls, json_dict: dict[str, Any]) -> Self | None: def from_dict(cls, json_dict: dict[str, Any]) -> Self | None:

View File

@ -929,8 +929,6 @@ class DomainStates:
class TemplateStateBase(State): class TemplateStateBase(State):
"""Class to represent a state object in a template.""" """Class to represent a state object in a template."""
__slots__ = ("_hass", "_collect", "_entity_id", "__dict__")
_state: State _state: State
__setitem__ = _readonly __setitem__ = _readonly

View File

@ -671,11 +671,11 @@ def test_state_as_dict_json() -> None:
'"last_changed":"1984-12-08T12:00:00","last_updated":"1984-12-08T12:00:00",' '"last_changed":"1984-12-08T12:00:00","last_updated":"1984-12-08T12:00:00",'
'"context":{"id":"01H0D6K3RFJAYAV2093ZW30PCW","parent_id":null,"user_id":null}}' '"context":{"id":"01H0D6K3RFJAYAV2093ZW30PCW","parent_id":null,"user_id":null}}'
) )
as_dict_json_1 = state.as_dict_json() as_dict_json_1 = state.as_dict_json
assert as_dict_json_1 == expected assert as_dict_json_1 == expected
# 2nd time to verify cache # 2nd time to verify cache
assert state.as_dict_json() == expected assert state.as_dict_json == expected
assert state.as_dict_json() is as_dict_json_1 assert state.as_dict_json is as_dict_json_1
def test_state_as_compressed_state() -> None: def test_state_as_compressed_state() -> None:
@ -694,12 +694,12 @@ def test_state_as_compressed_state() -> None:
"lc": last_time.timestamp(), "lc": last_time.timestamp(),
"s": "on", "s": "on",
} }
as_compressed_state = state.as_compressed_state() as_compressed_state = state.as_compressed_state
# We are not too concerned about these being ReadOnlyDict # We are not too concerned about these being ReadOnlyDict
# since we don't expect them to be called by external callers # since we don't expect them to be called by external callers
assert as_compressed_state == expected assert as_compressed_state == expected
# 2nd time to verify cache # 2nd time to verify cache
assert state.as_compressed_state() == expected assert state.as_compressed_state == expected
def test_state_as_compressed_state_unique_last_updated() -> None: def test_state_as_compressed_state_unique_last_updated() -> None:
@ -720,12 +720,12 @@ def test_state_as_compressed_state_unique_last_updated() -> None:
"lu": last_updated.timestamp(), "lu": last_updated.timestamp(),
"s": "on", "s": "on",
} }
as_compressed_state = state.as_compressed_state() as_compressed_state = state.as_compressed_state
# We are not too concerned about these being ReadOnlyDict # We are not too concerned about these being ReadOnlyDict
# since we don't expect them to be called by external callers # since we don't expect them to be called by external callers
assert as_compressed_state == expected assert as_compressed_state == expected
# 2nd time to verify cache # 2nd time to verify cache
assert state.as_compressed_state() == expected assert state.as_compressed_state == expected
def test_state_as_compressed_state_json() -> None: def test_state_as_compressed_state_json() -> None:
@ -740,13 +740,13 @@ def test_state_as_compressed_state_json() -> None:
context=ha.Context(id="01H0D6H5K3SZJ3XGDHED1TJ79N"), context=ha.Context(id="01H0D6H5K3SZJ3XGDHED1TJ79N"),
) )
expected = '"happy.happy":{"s":"on","a":{"pig":"dog"},"c":"01H0D6H5K3SZJ3XGDHED1TJ79N","lc":471355200.0}' expected = '"happy.happy":{"s":"on","a":{"pig":"dog"},"c":"01H0D6H5K3SZJ3XGDHED1TJ79N","lc":471355200.0}'
as_compressed_state = state.as_compressed_state_json() as_compressed_state = state.as_compressed_state_json
# We are not too concerned about these being ReadOnlyDict # We are not too concerned about these being ReadOnlyDict
# since we don't expect them to be called by external callers # since we don't expect them to be called by external callers
assert as_compressed_state == expected assert as_compressed_state == expected
# 2nd time to verify cache # 2nd time to verify cache
assert state.as_compressed_state_json() == expected assert state.as_compressed_state_json == expected
assert state.as_compressed_state_json() is as_compressed_state assert state.as_compressed_state_json is as_compressed_state
async def test_eventbus_add_remove_listener(hass: HomeAssistant) -> None: async def test_eventbus_add_remove_listener(hass: HomeAssistant) -> None: