From 84446aa8a3957d8a0d186f2ec4cf44544f4fc221 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 15 Mar 2022 22:39:10 -1000 Subject: [PATCH 1/3] Cache parsing attr in LazyState --- homeassistant/components/recorder/history.py | 11 +++++++---- homeassistant/components/recorder/models.py | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index 8be60a60ac6e30..4e61d74804d11a 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -302,7 +302,8 @@ def _get_states_with_session( if filters: query = filters.apply(query) - return [LazyState(row) for row in execute(query)] + attr_cache = {} + return [LazyState(row, attr_cache) for row in execute(query)] def _get_single_entity_states_with_session(hass, session, utc_point_in_time, entity_id): @@ -375,15 +376,17 @@ def _sorted_states_to_dict( for ent_id, group in groupby(states, lambda state: state.entity_id): domain = split_entity_id(ent_id)[0] ent_results = result[ent_id] + attr_cache = {} + if not minimal_response or domain in NEED_ATTRIBUTE_DOMAINS: - ent_results.extend(LazyState(db_state) for db_state in group) + ent_results.extend(LazyState(db_state, attr_cache) for db_state in group) # With minimal response we only provide a native # State for the first and last response. All the states # in-between only provide the "state" and the # "last_changed". if not ent_results: - ent_results.append(LazyState(next(group))) + ent_results.append(LazyState(next(group), attr_cache)) prev_state = ent_results[-1] initial_state_count = len(ent_results) @@ -408,7 +411,7 @@ def _sorted_states_to_dict( # There was at least one state change # replace the last minimal state with # a full state - ent_results[-1] = LazyState(prev_state) + ent_results[-1] = LazyState(prev_state, attr_cache) # Filter out the empty lists if some states had 0 results. return {key: val for key, val in result.items() if val} diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index b49189a9c3af54..dca03964aa26da 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -477,9 +477,10 @@ class LazyState(State): "_last_changed", "_last_updated", "_context", + "_attr_cache", ] - def __init__(self, row): # pylint: disable=super-init-not-called + def __init__(self, row, attr_cache=None): # pylint: disable=super-init-not-called """Init the lazy state.""" self._row = row self.entity_id = self._row.entity_id @@ -488,17 +489,26 @@ def __init__(self, row): # pylint: disable=super-init-not-called self._last_changed = None self._last_updated = None self._context = None + self._attr_cache = attr_cache @property # type: ignore[override] def attributes(self): """State attributes.""" - if not self._attributes: + if self._attributes is None: + attributes_json = self._row.attributes + if self._attr_cache is not None and ( + attributes := self._attr_cache.get(attributes_json) + ): + self._attributes = attributes + return attributes try: - self._attributes = json.loads(self._row.attributes) + self._attributes = json.loads(attributes_json) except ValueError: # When json.loads fails _LOGGER.exception("Error converting row to state: %s", self._row) self._attributes = {} + if self._attr_cache is not None: + self._attr_cache[attributes_json] = self._attributes return self._attributes @attributes.setter From 7581825565f73487d4460aba962ab577e8b15985 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Mar 2022 00:33:26 -1000 Subject: [PATCH 2/3] reverts --- homeassistant/components/recorder/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/homeassistant/components/recorder/models.py b/homeassistant/components/recorder/models.py index 9be74f57780003..ab87408105561d 100644 --- a/homeassistant/components/recorder/models.py +++ b/homeassistant/components/recorder/models.py @@ -551,17 +551,17 @@ def __init__(self, row, attr_cache=None): # pylint: disable=super-init-not-call def attributes(self): """State attributes.""" if self._attributes is None: - attributes_json = self._row.shared_attrs or self._row.attributes + source = self._row.shared_attrs or self._row.attributes if self._attr_cache is not None and ( - attributes := self._attr_cache.get(attributes_json) + attributes := self._attr_cache.get(source) ): self._attributes = attributes return attributes - if attributes_json == EMPTY_JSON_OBJECT or attributes_json is None: + if source == EMPTY_JSON_OBJECT or source is None: self._attributes = {} return self._attributes try: - self._attributes = json.loads(attributes_json) + self._attributes = json.loads(source) except ValueError: # When json.loads fails _LOGGER.exception( @@ -569,7 +569,7 @@ def attributes(self): ) self._attributes = {} if self._attr_cache is not None: - self._attr_cache[attributes_json] = self._attributes + self._attr_cache[source] = self._attributes return self._attributes @attributes.setter From 45f7c059db38bdabcbc3e15d810a35bab7f079f7 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Fri, 18 Mar 2022 00:34:05 -1000 Subject: [PATCH 3/3] lint --- homeassistant/components/recorder/history.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/homeassistant/components/recorder/history.py b/homeassistant/components/recorder/history.py index de1f9bfec075d9..c4d15863a5bb71 100644 --- a/homeassistant/components/recorder/history.py +++ b/homeassistant/components/recorder/history.py @@ -320,13 +320,12 @@ def _get_states_with_session( query = filters.apply(query) query = query.outerjoin( StateAttributes, (States.attributes_id == StateAttributes.attributes_id) - ) + ) attr_cache = {} return [LazyState(row, attr_cache) for row in execute(query)] - def _get_single_entity_states_with_session(hass, session, utc_point_in_time, entity_id): # Use an entirely different (and extremely fast) query if we only # have a single entity id