1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
"""Various helper functions."""
 
import sys
from collections.abc import Mapping
from functools import cached_property
from typing import Any, Callable, Generic, Optional, Protocol, TypeVar, Union, overload
 
__all__ = ("under_cached_property", "cached_property")
 
 
if sys.version_info >= (3, 11):
    from typing import Self
else:
    Self = Any
 
_T = TypeVar("_T")
# We use Mapping to make it possible to use TypedDict, but this isn't
# technically type safe as we need to assign into the dict.
_Cache = TypeVar("_Cache", bound=Mapping[str, Any])
 
 
class _CacheImpl(Protocol[_Cache]):
    _cache: _Cache
 
 
class under_cached_property(Generic[_T]):
    """Use as a class method decorator.
 
    It operates almost exactly like
    the Python `@property` decorator, but it puts the result of the
    method it decorates into the instance dict after the first call,
    effectively replacing the function it decorates with an instance
    variable.  It is, in Python parlance, a data descriptor.
    """
 
    def __init__(self, wrapped: Callable[[Any], _T]) -> None:
        self.wrapped = wrapped
        self.__doc__ = wrapped.__doc__
        self.name = wrapped.__name__
 
    @overload
    def __get__(self, inst: None, owner: Optional[type[object]] = None) -> Self: ...
 
    @overload
    def __get__(
        self, inst: _CacheImpl[Any], owner: Optional[type[object]] = None
    ) -> _T: ...
 
    def __get__(
        self, inst: Optional[_CacheImpl[Any]], owner: Optional[type[object]] = None
    ) -> Union[_T, Self]:
        if inst is None:
            return self
        try:
            return inst._cache[self.name]  # type: ignore[no-any-return]
        except KeyError:
            val = self.wrapped(inst)
            inst._cache[self.name] = val
            return val
 
    def __set__(self, inst: _CacheImpl[Any], value: _T) -> None:
        raise AttributeError("cached property is read-only")