From acf78664e23d503fce4ecf3bbec507f9ca573d60 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Sun, 7 Jan 2024 17:36:49 -1000 Subject: [PATCH] Reduce overhead to compile statistics (#106927) * Reduce overhead to compile statistics statistics uses LazyState for compatibility with State when pulling data from the database. After the previous round of refactoring to modern history, the setters are never called and can be removed. * reduce --- .../components/recorder/models/state.py | 81 ++++++------------- tests/components/recorder/test_models.py | 16 ---- 2 files changed, 26 insertions(+), 71 deletions(-) diff --git a/homeassistant/components/recorder/models/state.py b/homeassistant/components/recorder/models/state.py index 73e7798b9f5..5f469638ec0 100644 --- a/homeassistant/components/recorder/models/state.py +++ b/homeassistant/components/recorder/models/state.py @@ -3,7 +3,7 @@ from __future__ import annotations from datetime import datetime import logging -from typing import Any +from typing import TYPE_CHECKING, Any from sqlalchemy.engine.row import Row @@ -17,10 +17,16 @@ from homeassistant.core import Context, State import homeassistant.util.dt as dt_util from .state_attributes import decode_attributes_from_source -from .time import process_timestamp + +if TYPE_CHECKING: + from functools import cached_property +else: + from homeassistant.backports.functools import cached_property _LOGGER = logging.getLogger(__name__) +EMPTY_CONTEXT = Context(id=None) + def extract_metadata_ids( entity_id_to_metadata_id: dict[str, int | None], @@ -36,15 +42,6 @@ def extract_metadata_ids( class LazyState(State): """A lazy version of core State after schema 31.""" - __slots__ = [ - "_row", - "_attributes", - "_last_changed_ts", - "_last_updated_ts", - "_context", - "attr_cache", - ] - def __init__( # pylint: disable=super-init-not-called self, row: Row, @@ -61,61 +58,35 @@ class LazyState(State): self.state = state or "" self._attributes: dict[str, Any] | None = None self._last_updated_ts: float | None = last_updated_ts or start_time_ts - self._last_changed_ts: float | None = None - self._context: Context | None = None self.attr_cache = attr_cache + self.context = EMPTY_CONTEXT - @property # type: ignore[override] + @cached_property # type: ignore[override] def attributes(self) -> dict[str, Any]: """State attributes.""" - if self._attributes is None: - self._attributes = decode_attributes_from_source( - getattr(self._row, "attributes", None), self.attr_cache - ) - return self._attributes + return decode_attributes_from_source( + getattr(self._row, "attributes", None), self.attr_cache + ) - @attributes.setter - def attributes(self, value: dict[str, Any]) -> None: - """Set attributes.""" - self._attributes = value + @cached_property + def _last_changed_ts(self) -> float | None: + """Last changed timestamp.""" + return getattr(self._row, "last_changed_ts", None) - @property - def context(self) -> Context: - """State context.""" - if self._context is None: - self._context = Context(id=None) - return self._context - - @context.setter - def context(self, value: Context) -> None: - """Set context.""" - self._context = value - - @property - def last_changed(self) -> datetime: + @cached_property + def last_changed(self) -> datetime: # type: ignore[override] """Last changed datetime.""" - if self._last_changed_ts is None: - self._last_changed_ts = ( - getattr(self._row, "last_changed_ts", None) or self._last_updated_ts - ) - return dt_util.utc_from_timestamp(self._last_changed_ts) + return dt_util.utc_from_timestamp( + self._last_changed_ts or self._last_updated_ts + ) - @last_changed.setter - def last_changed(self, value: datetime) -> None: - """Set last changed datetime.""" - self._last_changed_ts = process_timestamp(value).timestamp() - - @property - def last_updated(self) -> datetime: + @cached_property + def last_updated(self) -> datetime: # type: ignore[override] """Last updated datetime.""" - assert self._last_updated_ts is not None + if TYPE_CHECKING: + assert self._last_updated_ts is not None return dt_util.utc_from_timestamp(self._last_updated_ts) - @last_updated.setter - def last_updated(self, value: datetime) -> None: - """Set last updated datetime.""" - self._last_updated_ts = process_timestamp(value).timestamp() - def as_dict(self) -> dict[str, Any]: # type: ignore[override] """Return a dict representation of the LazyState. diff --git a/tests/components/recorder/test_models.py b/tests/components/recorder/test_models.py index f5ea8ff1656..8536481dd1f 100644 --- a/tests/components/recorder/test_models.py +++ b/tests/components/recorder/test_models.py @@ -352,22 +352,6 @@ async def test_lazy_state_handles_same_last_updated_and_last_changed( "last_updated": "2021-06-12T03:04:01.000323+00:00", "state": "off", } - lstate.last_updated = datetime(2020, 6, 12, 3, 4, 1, 323, tzinfo=dt_util.UTC) - assert lstate.as_dict() == { - "attributes": {"shared": True}, - "entity_id": "sensor.valid", - "last_changed": "2021-06-12T03:04:01.000323+00:00", - "last_updated": "2020-06-12T03:04:01.000323+00:00", - "state": "off", - } - lstate.last_changed = datetime(2020, 6, 12, 3, 4, 1, 323, tzinfo=dt_util.UTC) - assert lstate.as_dict() == { - "attributes": {"shared": True}, - "entity_id": "sensor.valid", - "last_changed": "2020-06-12T03:04:01.000323+00:00", - "last_updated": "2020-06-12T03:04:01.000323+00:00", - "state": "off", - } @pytest.mark.parametrize(