From a568885ad21fa636f4710c7095599f40a88e3346 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Mon, 26 Jun 2023 23:23:07 +0200 Subject: [PATCH] Add backport of cached_property from CPython 3.12 (#95292) --- homeassistant/backports/functools.py | 62 +++++++++++++++++++ homeassistant/components/dlna_dms/dms.py | 3 +- homeassistant/components/fints/sensor.py | 2 +- .../components/nibe_heatpump/__init__.py | 2 +- .../components/thread/dataset_store.py | 2 +- .../zha/core/cluster_handlers/lighting.py | 4 +- homeassistant/components/zha/core/device.py | 2 +- 7 files changed, 70 insertions(+), 7 deletions(-) create mode 100644 homeassistant/backports/functools.py diff --git a/homeassistant/backports/functools.py b/homeassistant/backports/functools.py new file mode 100644 index 00000000000..b05277173c4 --- /dev/null +++ b/homeassistant/backports/functools.py @@ -0,0 +1,62 @@ +"""Functools backports from standard lib.""" +from __future__ import annotations + +from collections.abc import Callable +from types import GenericAlias +from typing import Any, Generic, TypeVar, cast + +_T = TypeVar("_T") +_R = TypeVar("_R") + + +class cached_property(Generic[_T, _R]): # pylint: disable=invalid-name + """Backport of Python 3.12's cached_property. + + Includes https://github.com/python/cpython/pull/101890/files + """ + + def __init__(self, func: Callable[[_T], _R]) -> None: + """Initialize.""" + self.func = func + self.attrname: Any = None + self.__doc__ = func.__doc__ + + def __set_name__(self, owner: type[_T], name: str) -> None: + """Set name.""" + if self.attrname is None: + self.attrname = name + elif name != self.attrname: + raise TypeError( + "Cannot assign the same cached_property to two different names " + f"({self.attrname!r} and {name!r})." + ) + + def __get__(self, instance: _T | None, owner: type[_T] | None = None) -> _R: + """Get.""" + if instance is None: + return cast(_R, self) + if self.attrname is None: + raise TypeError( + "Cannot use cached_property instance without calling __set_name__ on it." + ) + try: + cache = instance.__dict__ + # not all objects have __dict__ (e.g. class defines slots) + except AttributeError: + msg = ( + f"No '__dict__' attribute on {type(instance).__name__!r} " + f"instance to cache {self.attrname!r} property." + ) + raise TypeError(msg) from None + val = self.func(instance) + try: + cache[self.attrname] = val + except TypeError: + msg = ( + f"The '__dict__' attribute on {type(instance).__name__!r} instance " + f"does not support item assignment for caching {self.attrname!r} property." + ) + raise TypeError(msg) from None + return val + + __class_getitem__ = classmethod(GenericAlias) diff --git a/homeassistant/components/dlna_dms/dms.py b/homeassistant/components/dlna_dms/dms.py index 2fd1a85ebae..8fc55830c63 100644 --- a/homeassistant/components/dlna_dms/dms.py +++ b/homeassistant/components/dlna_dms/dms.py @@ -16,6 +16,7 @@ from async_upnp_client.profiles.dlna import ContentDirectoryErrorCode, DmsDevice from didl_lite import didl_lite from homeassistant.backports.enum import StrEnum +from homeassistant.backports.functools import cached_property from homeassistant.components import ssdp from homeassistant.components.media_player import BrowseError, MediaClass from homeassistant.components.media_source.error import Unresolvable @@ -619,7 +620,7 @@ class DmsDeviceSource: """Make an identifier for BrowseMediaSource.""" return f"{self.source_id}/{action}{object_id}" - @functools.cached_property + @cached_property def _sort_criteria(self) -> list[str]: """Return criteria to be used for sorting results. diff --git a/homeassistant/components/fints/sensor.py b/homeassistant/components/fints/sensor.py index 479e59d9cdf..3b961054544 100644 --- a/homeassistant/components/fints/sensor.py +++ b/homeassistant/components/fints/sensor.py @@ -3,7 +3,6 @@ from __future__ import annotations from collections import namedtuple from datetime import timedelta -from functools import cached_property import logging from typing import Any @@ -11,6 +10,7 @@ from fints.client import FinTS3PinTanClient from fints.models import SEPAAccount import voluptuous as vol +from homeassistant.backports.functools import cached_property from homeassistant.components.sensor import PLATFORM_SCHEMA, SensorEntity from homeassistant.const import CONF_NAME, CONF_PIN, CONF_URL, CONF_USERNAME from homeassistant.core import HomeAssistant diff --git a/homeassistant/components/nibe_heatpump/__init__.py b/homeassistant/components/nibe_heatpump/__init__.py index b46102879c4..a38e2182ad7 100644 --- a/homeassistant/components/nibe_heatpump/__init__.py +++ b/homeassistant/components/nibe_heatpump/__init__.py @@ -5,7 +5,6 @@ import asyncio from collections import defaultdict from collections.abc import Callable, Iterable from datetime import timedelta -from functools import cached_property from typing import Any, Generic, TypeVar from nibe.coil import Coil, CoilData @@ -15,6 +14,7 @@ from nibe.connection.nibegw import NibeGW, ProductInfo from nibe.exceptions import CoilNotFoundException, ReadException from nibe.heatpump import HeatPump, Model, Series +from homeassistant.backports.functools import cached_property from homeassistant.config_entries import ConfigEntry from homeassistant.const import ( CONF_IP_ADDRESS, diff --git a/homeassistant/components/thread/dataset_store.py b/homeassistant/components/thread/dataset_store.py index 643981e763b..55623f7e3a4 100644 --- a/homeassistant/components/thread/dataset_store.py +++ b/homeassistant/components/thread/dataset_store.py @@ -3,13 +3,13 @@ from __future__ import annotations import dataclasses from datetime import datetime -from functools import cached_property import logging from typing import Any, cast from python_otbr_api import tlv_parser from python_otbr_api.tlv_parser import MeshcopTLVType +from homeassistant.backports.functools import cached_property from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.singleton import singleton diff --git a/homeassistant/components/zha/core/cluster_handlers/lighting.py b/homeassistant/components/zha/core/cluster_handlers/lighting.py index 993ecca29cd..5f54ce381cc 100644 --- a/homeassistant/components/zha/core/cluster_handlers/lighting.py +++ b/homeassistant/components/zha/core/cluster_handlers/lighting.py @@ -1,10 +1,10 @@ """Lighting cluster handlers module for Zigbee Home Automation.""" from __future__ import annotations -from functools import cached_property - from zigpy.zcl.clusters import lighting +from homeassistant.backports.functools import cached_property + from .. import registries from ..const import REPORT_CONFIG_DEFAULT from . import AttrReportConfig, ClientClusterHandler, ClusterHandler diff --git a/homeassistant/components/zha/core/device.py b/homeassistant/components/zha/core/device.py index 311e876bbc0..51ab65e3318 100644 --- a/homeassistant/components/zha/core/device.py +++ b/homeassistant/components/zha/core/device.py @@ -5,7 +5,6 @@ import asyncio from collections.abc import Callable from datetime import timedelta from enum import Enum -from functools import cached_property import logging import random import time @@ -23,6 +22,7 @@ from zigpy.zcl.clusters.general import Groups, Identify from zigpy.zcl.foundation import Status as ZclStatus, ZCLCommandDef import zigpy.zdo.types as zdo_types +from homeassistant.backports.functools import cached_property from homeassistant.const import ATTR_COMMAND, ATTR_DEVICE_ID, ATTR_NAME from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import HomeAssistantError