diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py index 993deae280a..ff66e42f466 100644 --- a/homeassistant/components/sensor/__init__.py +++ b/homeassistant/components/sensor/__init__.py @@ -10,7 +10,7 @@ from decimal import Decimal, InvalidOperation as DecimalInvalidOperation from functools import partial import logging from math import ceil, floor, isfinite, log10 -from typing import Any, Final, Self, cast, final +from typing import TYPE_CHECKING, Any, Final, Self, cast, final from typing_extensions import override @@ -92,6 +92,11 @@ from .const import ( # noqa: F401 ) from .websocket_api import async_setup as async_setup_ws_api +if TYPE_CHECKING: + from functools import cached_property +else: + from homeassistant.backports.functools import cached_property + _LOGGER: Final = logging.getLogger(__name__) ENTITY_ID_FORMAT: Final = DOMAIN + ".{}" @@ -182,7 +187,19 @@ def _numeric_state_expected( return device_class is not None -class SensorEntity(Entity): +CACHED_PROPERTIES_WITH_ATTR_ = { + "device_class", + "last_reset", + "native_unit_of_measurement", + "native_value", + "options", + "state_class", + "suggested_display_precision", + "suggested_unit_of_measurement", +} + + +class SensorEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_): """Base class for sensor entities.""" _entity_component_unrecorded_attributes = frozenset({ATTR_OPTIONS}) @@ -302,7 +319,7 @@ class SensorEntity(Entity): """ return self.device_class not in (None, SensorDeviceClass.ENUM) - @property + @cached_property @override def device_class(self) -> SensorDeviceClass | None: """Return the class of this entity.""" @@ -323,7 +340,7 @@ class SensorEntity(Entity): self.suggested_display_precision, ) - @property + @cached_property def options(self) -> list[str] | None: """Return a set of possible options.""" if hasattr(self, "_attr_options"): @@ -332,7 +349,7 @@ class SensorEntity(Entity): return self.entity_description.options return None - @property + @cached_property def state_class(self) -> SensorStateClass | str | None: """Return the state class of this entity, if any.""" if hasattr(self, "_attr_state_class"): @@ -341,7 +358,7 @@ class SensorEntity(Entity): return self.entity_description.state_class return None - @property + @cached_property def last_reset(self) -> datetime | None: """Return the time when the sensor was last reset, if any.""" if hasattr(self, "_attr_last_reset"): @@ -424,12 +441,12 @@ class SensorEntity(Entity): return None - @property + @cached_property def native_value(self) -> StateType | date | datetime | Decimal: """Return the value reported by the sensor.""" return self._attr_native_value - @property + @cached_property def suggested_display_precision(self) -> int | None: """Return the suggested number of decimal digits for display.""" if hasattr(self, "_attr_suggested_display_precision"): @@ -438,7 +455,7 @@ class SensorEntity(Entity): return self.entity_description.suggested_display_precision return None - @property + @cached_property def native_unit_of_measurement(self) -> str | None: """Return the unit of measurement of the sensor, if any.""" if hasattr(self, "_attr_native_unit_of_measurement"): @@ -447,7 +464,7 @@ class SensorEntity(Entity): return self.entity_description.native_unit_of_measurement return None - @property + @cached_property def suggested_unit_of_measurement(self) -> str | None: """Return the unit which should be used for the sensor's state.