kapro 0.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kapro/__init__.py +72 -0
- kapro/cached/__init__.py +29 -0
- kapro/cached/instance.py +108 -0
- kapro/cached/instance.pyi +53 -0
- kapro/cached/klass.py +240 -0
- kapro/cached/klass.pyi +72 -0
- kapro/cached/mixed.py +146 -0
- kapro/cached/mixed.pyi +59 -0
- kapro/cached/post.py +70 -0
- kapro/cached/post.pyi +29 -0
- kapro/cached/pre.py +70 -0
- kapro/cached/pre.pyi +29 -0
- kapro/class_property.py +204 -0
- kapro/class_property.pyi +96 -0
- kapro/primitives.py +331 -0
- kapro/primitives.pyi +79 -0
- kapro/py.typed +0 -0
- kapro-0.0.1.dist-info/METADATA +167 -0
- kapro-0.0.1.dist-info/RECORD +21 -0
- kapro-0.0.1.dist-info/WHEEL +5 -0
- kapro-0.0.1.dist-info/top_level.txt +1 -0
kapro/__init__.py
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any, TypeVar, final, overload, override
|
|
4
|
+
|
|
5
|
+
from kapro.cached import (
|
|
6
|
+
cached_property,
|
|
7
|
+
class_cached_property,
|
|
8
|
+
mixed_cached_property,
|
|
9
|
+
post_cached_property,
|
|
10
|
+
pre_cached_property,
|
|
11
|
+
)
|
|
12
|
+
from kapro.class_property import class_property, mixed_property
|
|
13
|
+
from kapro.primitives import (
|
|
14
|
+
ContextFaultError,
|
|
15
|
+
Nothing,
|
|
16
|
+
bound_property,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@final
|
|
23
|
+
class pin[T_co](bound_property[T_co]): # noqa: N801
|
|
24
|
+
native = cached_property
|
|
25
|
+
cls = class_cached_property
|
|
26
|
+
any = mixed_cached_property
|
|
27
|
+
pre = pre_cached_property
|
|
28
|
+
post = post_cached_property
|
|
29
|
+
|
|
30
|
+
@overload
|
|
31
|
+
def __get__(
|
|
32
|
+
self,
|
|
33
|
+
node: None,
|
|
34
|
+
klass: Any = ..., # noqa: ANN401
|
|
35
|
+
) -> pin[T_co]: ...
|
|
36
|
+
|
|
37
|
+
@overload
|
|
38
|
+
def __get__(
|
|
39
|
+
self,
|
|
40
|
+
node: object,
|
|
41
|
+
klass: Any = ..., # noqa: ANN401
|
|
42
|
+
) -> T_co: ...
|
|
43
|
+
|
|
44
|
+
@override
|
|
45
|
+
def __get__(
|
|
46
|
+
self,
|
|
47
|
+
node: object | None,
|
|
48
|
+
klass: Any = Nothing,
|
|
49
|
+
) -> pin[T_co] | T_co:
|
|
50
|
+
if node is None:
|
|
51
|
+
raise ContextFaultError(self.header_with_context(klass))
|
|
52
|
+
|
|
53
|
+
cache = getattr(node, "__dict__", None)
|
|
54
|
+
if cache is None:
|
|
55
|
+
raise TypeError(
|
|
56
|
+
f"{self.header_with_context(node)} has no __dict__",
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
return cache[self.name] # type: ignore[no-any-return, index]
|
|
61
|
+
|
|
62
|
+
except KeyError:
|
|
63
|
+
value = self.function(node)
|
|
64
|
+
cache[self.name] = value # type: ignore[index]
|
|
65
|
+
return value # type: ignore[no-any-return]
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
__all__ = (
|
|
69
|
+
"class_property",
|
|
70
|
+
"mixed_property",
|
|
71
|
+
"pin",
|
|
72
|
+
)
|
kapro/cached/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from kapro.cached.instance import cached_property
|
|
2
|
+
from kapro.cached.klass import (
|
|
3
|
+
class_cached_property,
|
|
4
|
+
class_parent_cached_property,
|
|
5
|
+
)
|
|
6
|
+
from kapro.cached.mixed import (
|
|
7
|
+
mixed_cached_property,
|
|
8
|
+
mixed_parent_cached_property,
|
|
9
|
+
)
|
|
10
|
+
from kapro.cached.post import (
|
|
11
|
+
post_cached_property,
|
|
12
|
+
post_parent_cached_property,
|
|
13
|
+
)
|
|
14
|
+
from kapro.cached.pre import (
|
|
15
|
+
pre_cached_property,
|
|
16
|
+
pre_parent_cached_property,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = (
|
|
20
|
+
"cached_property",
|
|
21
|
+
"class_cached_property",
|
|
22
|
+
"class_parent_cached_property",
|
|
23
|
+
"mixed_cached_property",
|
|
24
|
+
"mixed_parent_cached_property",
|
|
25
|
+
"post_cached_property",
|
|
26
|
+
"post_parent_cached_property",
|
|
27
|
+
"pre_cached_property",
|
|
28
|
+
"pre_parent_cached_property",
|
|
29
|
+
)
|
kapro/cached/instance.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""Instance-level cached property descriptor.
|
|
2
|
+
|
|
3
|
+
This module defines :class:`cached_property` — the caching equivalent of
|
|
4
|
+
:class:`kapro.primitives.bound_property`. It stores computed
|
|
5
|
+
values in a per-instance dictionary named ``__instance_memoized__`` and
|
|
6
|
+
supports parent-aware resolution (via inheritance from
|
|
7
|
+
:class:`class_parent_cached_property`).
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# ruff: noqa: ANN401, N801
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections.abc import Callable
|
|
15
|
+
from contextlib import suppress
|
|
16
|
+
from functools import cached_property as base_cached_property
|
|
17
|
+
from types import NoneType
|
|
18
|
+
from typing import (
|
|
19
|
+
TYPE_CHECKING,
|
|
20
|
+
Any,
|
|
21
|
+
ClassVar,
|
|
22
|
+
TypeVar,
|
|
23
|
+
cast,
|
|
24
|
+
overload,
|
|
25
|
+
override,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
from kain import Is, Who
|
|
29
|
+
|
|
30
|
+
from kapro.cached.klass import class_parent_cached_property
|
|
31
|
+
from kapro.primitives import ContextFaultError, parent_call
|
|
32
|
+
|
|
33
|
+
__all__ = ("cached_property",)
|
|
34
|
+
|
|
35
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
36
|
+
R = TypeVar("R")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class cached_property[T_co](class_parent_cached_property[T_co]):
|
|
40
|
+
klass: ClassVar[bool | NoneType] = False
|
|
41
|
+
|
|
42
|
+
@base_cached_property
|
|
43
|
+
@override
|
|
44
|
+
def title(self) -> str:
|
|
45
|
+
return f"instance data-descriptor {Who.Addr(self)}".strip()
|
|
46
|
+
|
|
47
|
+
@override
|
|
48
|
+
def get_node(self, node: Any) -> Any:
|
|
49
|
+
if node is None or Is.Class(node):
|
|
50
|
+
msg = f"{self.header_with_context(node)}, node={node!r}"
|
|
51
|
+
if node is None and (not self.klass):
|
|
52
|
+
msg = f"{msg}; looks like a non-instance invocation"
|
|
53
|
+
raise ContextFaultError(msg)
|
|
54
|
+
return node
|
|
55
|
+
|
|
56
|
+
@override
|
|
57
|
+
def get_cache(self, node: Any) -> dict[str, Any]:
|
|
58
|
+
self.get_node(node)
|
|
59
|
+
name = "__instance_memoized__"
|
|
60
|
+
cache: dict[str, Any] = {}
|
|
61
|
+
|
|
62
|
+
if hasattr(node, "__dict__"):
|
|
63
|
+
with suppress(KeyError):
|
|
64
|
+
return node.__dict__[name]
|
|
65
|
+
setattr(node, name, cache)
|
|
66
|
+
|
|
67
|
+
return cache
|
|
68
|
+
|
|
69
|
+
@overload
|
|
70
|
+
def __get__(
|
|
71
|
+
self,
|
|
72
|
+
instance: None,
|
|
73
|
+
klass: Any = ...,
|
|
74
|
+
) -> cached_property[T_co]: ...
|
|
75
|
+
|
|
76
|
+
@overload
|
|
77
|
+
def __get__(
|
|
78
|
+
self,
|
|
79
|
+
instance: object,
|
|
80
|
+
klass: Any = ...,
|
|
81
|
+
) -> T_co: ...
|
|
82
|
+
|
|
83
|
+
@override
|
|
84
|
+
def __get__(
|
|
85
|
+
self,
|
|
86
|
+
instance: object | None,
|
|
87
|
+
klass: Any = None,
|
|
88
|
+
) -> cached_property[T_co] | T_co:
|
|
89
|
+
if instance is None:
|
|
90
|
+
raise ContextFaultError(self.header_with_context(klass))
|
|
91
|
+
return self.call(instance)
|
|
92
|
+
|
|
93
|
+
if TYPE_CHECKING:
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
@override
|
|
97
|
+
def with_parent(function: Callable[..., R]) -> cached_property[R]: ...
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _cached_property_with_parent[R](
|
|
101
|
+
function: Callable[..., R],
|
|
102
|
+
) -> cached_property[R]:
|
|
103
|
+
return cached_property(cast("Callable[[Any], R]", parent_call(function)))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
cached_property.with_parent = staticmethod( # type: ignore[method-assign]
|
|
107
|
+
_cached_property_with_parent,
|
|
108
|
+
)
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from functools import cached_property as base_cached_property
|
|
5
|
+
from typing import (
|
|
6
|
+
TYPE_CHECKING,
|
|
7
|
+
Any,
|
|
8
|
+
TypeVar,
|
|
9
|
+
overload,
|
|
10
|
+
override,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from kapro.cached.klass import class_parent_cached_property
|
|
14
|
+
|
|
15
|
+
__all__ = ("cached_property",)
|
|
16
|
+
T_co: Any
|
|
17
|
+
R = TypeVar("R")
|
|
18
|
+
|
|
19
|
+
class cached_property[T_co](class_parent_cached_property[T_co]):
|
|
20
|
+
@base_cached_property
|
|
21
|
+
@override
|
|
22
|
+
def title(self) -> str: ...
|
|
23
|
+
@override
|
|
24
|
+
def get_node(self, node: Any) -> Any: ...
|
|
25
|
+
@override
|
|
26
|
+
def get_cache(self, node: Any) -> dict[str, Any]: ...
|
|
27
|
+
@overload
|
|
28
|
+
def __get__(
|
|
29
|
+
self,
|
|
30
|
+
instance: None,
|
|
31
|
+
klass: Any = ...,
|
|
32
|
+
) -> cached_property[T_co]: ...
|
|
33
|
+
@overload
|
|
34
|
+
def __get__(
|
|
35
|
+
self,
|
|
36
|
+
instance: object,
|
|
37
|
+
klass: Any = ...,
|
|
38
|
+
) -> T_co: ...
|
|
39
|
+
@override # type: ignore[misc]
|
|
40
|
+
def __get__(
|
|
41
|
+
self,
|
|
42
|
+
instance: object | None,
|
|
43
|
+
klass: Any = None,
|
|
44
|
+
) -> cached_property[T_co] | T_co: ...
|
|
45
|
+
|
|
46
|
+
if TYPE_CHECKING:
|
|
47
|
+
@staticmethod
|
|
48
|
+
@override
|
|
49
|
+
def with_parent(function: Callable[..., R]) -> cached_property[R]: ...
|
|
50
|
+
|
|
51
|
+
def _cached_property_with_parent[R](
|
|
52
|
+
function: Callable[..., R],
|
|
53
|
+
) -> cached_property[R]: ...
|
kapro/cached/klass.py
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Class-level cached property descriptors.
|
|
2
|
+
|
|
3
|
+
This module implements the ``class_cached_property`` family. Both variants
|
|
4
|
+
store their cache in a dictionary named ``__class_memoized__`` attached to
|
|
5
|
+
the class object (either the concrete class or the owning parent class,
|
|
6
|
+
depending on the variant).
|
|
7
|
+
|
|
8
|
+
In addition, :class:`CustomCallbackMixin` provides a pluggable cache
|
|
9
|
+
invalidation mechanism via the ``is_actual`` callback.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
# ruff: noqa: ANN401, N801
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from collections.abc import Callable
|
|
17
|
+
from contextlib import suppress
|
|
18
|
+
from functools import cached_property, partial
|
|
19
|
+
from time import time
|
|
20
|
+
from typing import TYPE_CHECKING, Any, TypeVar, cast, override
|
|
21
|
+
|
|
22
|
+
from kain import Is, Who
|
|
23
|
+
from kain.internals import get_owner
|
|
24
|
+
|
|
25
|
+
from kapro.primitives import (
|
|
26
|
+
AttributeExceptionError,
|
|
27
|
+
BaseProperty,
|
|
28
|
+
ContextFaultError,
|
|
29
|
+
Nothing,
|
|
30
|
+
parent_call,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
__all__ = ("class_cached_property", "class_parent_cached_property")
|
|
34
|
+
|
|
35
|
+
T_co = TypeVar("T_co", covariant=True)
|
|
36
|
+
R = TypeVar("R")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class CustomCallbackMixin:
|
|
40
|
+
"""Mixin that adds TTL and custom callback support to cached descriptors.
|
|
41
|
+
|
|
42
|
+
The mixin is designed to be combined with a descriptor class (e.g.
|
|
43
|
+
``class_parent_cached_property``). It exposes two factory classmethods:
|
|
44
|
+
|
|
45
|
+
* :meth:`by` / :meth:`expired_by` - supply your own ``is_actual``
|
|
46
|
+
predicate.
|
|
47
|
+
* :meth:`ttl` - supply a numeric lifetime in seconds.
|
|
48
|
+
|
|
49
|
+
``is_actual`` signature
|
|
50
|
+
-----------------------
|
|
51
|
+
``is_actual(self, node, stamp) -> bool | float``
|
|
52
|
+
|
|
53
|
+
* When called **without** ``stamp`` (during ``__set__``), it should
|
|
54
|
+
return the *new* stamp to store alongside the cached value.
|
|
55
|
+
* When called **with** ``stamp`` (during ``call`` / cache lookup), it
|
|
56
|
+
should return ``True`` if the cached value is still valid.
|
|
57
|
+
|
|
58
|
+
If ``is_actual`` is ``Nothing`` (the default), the cache never expires.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@classmethod
|
|
62
|
+
def by(cls, callback: Callable[..., Any]) -> Callable[..., Any]:
|
|
63
|
+
return partial(cls, is_actual=callback) # type: ignore[call-arg]
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def ttl(cls, expire: float) -> Callable[..., Any]:
|
|
67
|
+
if not isinstance(expire, float | int): # type: ignore[redundant-expr]
|
|
68
|
+
msg = f"expire must be float or int, not {Who.Cast(expire)}"
|
|
69
|
+
raise TypeError(msg)
|
|
70
|
+
if expire <= 0:
|
|
71
|
+
msg = f"expire must be a positive number, not {Who.Cast(expire)}"
|
|
72
|
+
raise ValueError(msg)
|
|
73
|
+
|
|
74
|
+
def is_actual(_self: Any, _node: Any, value: Any = Nothing) -> Any:
|
|
75
|
+
if isinstance(value, float):
|
|
76
|
+
return value + expire > time()
|
|
77
|
+
return time()
|
|
78
|
+
|
|
79
|
+
return cls.by(is_actual)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class class_parent_cached_property[T_co](
|
|
83
|
+
BaseProperty[T_co],
|
|
84
|
+
CustomCallbackMixin,
|
|
85
|
+
):
|
|
86
|
+
"""Class-level cached descriptor that stores cache on the *owning* class.
|
|
87
|
+
|
|
88
|
+
The *owning* class is determined by :func:`kapro.internals.get_owner`,
|
|
89
|
+
which walks the MRO to find the class that actually defines this
|
|
90
|
+
descriptor. This means that if ``Base`` defines the property and
|
|
91
|
+
``Child(Base)`` is accessed, the cache lives on ``Base`` — so all
|
|
92
|
+
subclasses share the same cached value.
|
|
93
|
+
|
|
94
|
+
Cache structure
|
|
95
|
+
---------------
|
|
96
|
+
The cache dict is stored in the class ``__dict__`` under the key
|
|
97
|
+
``__class_memoized__``. Each entry is keyed by the property ``name``.
|
|
98
|
+
|
|
99
|
+
* Without ``is_actual`` → ``cache[name] = value``
|
|
100
|
+
* With ``is_actual`` → ``cache[name] = (value, stamp)``
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
function: Callable[[Any], T_co],
|
|
106
|
+
is_actual: Any = Nothing,
|
|
107
|
+
) -> None:
|
|
108
|
+
super().__init__(function)
|
|
109
|
+
if method := getattr(Is.classOf(self), "is_actual", None):
|
|
110
|
+
if is_actual:
|
|
111
|
+
msg = (
|
|
112
|
+
f"{Who.Is(self)}.is_actual method ({Who.Cast(method)}) "
|
|
113
|
+
"can't be overridden by the is_actual keyword argument"
|
|
114
|
+
)
|
|
115
|
+
raise TypeError(msg)
|
|
116
|
+
is_actual = method
|
|
117
|
+
self.is_actual: Any = is_actual
|
|
118
|
+
|
|
119
|
+
@cached_property
|
|
120
|
+
@override
|
|
121
|
+
def title(self) -> str:
|
|
122
|
+
return f"class data-descriptor {Who.Addr(self)}".strip()
|
|
123
|
+
|
|
124
|
+
@override
|
|
125
|
+
def header_with_context(self, node: Any) -> str:
|
|
126
|
+
return self.footer(node)
|
|
127
|
+
|
|
128
|
+
def get_node(self, node: Any) -> Any:
|
|
129
|
+
if node is None or not Is.Class(node):
|
|
130
|
+
msg = f"{self.header_with_context(node)}, node={node!r}"
|
|
131
|
+
raise ContextFaultError(msg)
|
|
132
|
+
result = get_owner(node, self.name)
|
|
133
|
+
return result if result is not None else node
|
|
134
|
+
|
|
135
|
+
def get_cache(self, node: Any) -> dict[str, Any]:
|
|
136
|
+
self.get_node(node)
|
|
137
|
+
name = "__class_memoized__"
|
|
138
|
+
if hasattr(node, "__dict__"):
|
|
139
|
+
with suppress(KeyError):
|
|
140
|
+
return cast("dict[str, Any]", node.__dict__[name])
|
|
141
|
+
cache: dict[str, Any] = {}
|
|
142
|
+
setattr(node, name, cache)
|
|
143
|
+
return cache
|
|
144
|
+
|
|
145
|
+
@override
|
|
146
|
+
def call(self, node: Any, *args: Any, **kw: Any) -> T_co:
|
|
147
|
+
pivot = self.get_node(node)
|
|
148
|
+
with suppress(KeyError):
|
|
149
|
+
stored = self.get_cache(pivot)[self.name]
|
|
150
|
+
if not self.is_actual:
|
|
151
|
+
return stored
|
|
152
|
+
value, stamp = stored
|
|
153
|
+
if self.is_actual(self, pivot, stamp) is True:
|
|
154
|
+
return value
|
|
155
|
+
try:
|
|
156
|
+
value = self.function(node)
|
|
157
|
+
except AttributeError as e:
|
|
158
|
+
raise AttributeExceptionError(e) from e
|
|
159
|
+
return self.__set__(pivot, value)
|
|
160
|
+
|
|
161
|
+
def __get__(self, instance: Any, klass: Any = None) -> T_co:
|
|
162
|
+
return self.call(klass)
|
|
163
|
+
|
|
164
|
+
def __set__(self, node: Any, value: Any) -> Any:
|
|
165
|
+
cache = self.get_cache(node)
|
|
166
|
+
if not self.is_actual:
|
|
167
|
+
cache[self.name] = value
|
|
168
|
+
else:
|
|
169
|
+
cache[self.name] = (value, self.is_actual(self, node))
|
|
170
|
+
return value
|
|
171
|
+
|
|
172
|
+
def __delete__(self, node: Any) -> None:
|
|
173
|
+
cache = self.get_cache(node)
|
|
174
|
+
with suppress(KeyError):
|
|
175
|
+
del cache[self.name]
|
|
176
|
+
|
|
177
|
+
if TYPE_CHECKING:
|
|
178
|
+
|
|
179
|
+
@staticmethod
|
|
180
|
+
@override
|
|
181
|
+
def with_parent( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
182
|
+
function: Callable[..., R],
|
|
183
|
+
) -> class_parent_cached_property[R]: ...
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _class_parent_cached_property_with_parent[R](
|
|
187
|
+
function: Callable[..., R],
|
|
188
|
+
) -> class_parent_cached_property[R]:
|
|
189
|
+
return class_parent_cached_property(
|
|
190
|
+
cast("Callable[[Any], R]", parent_call(function)),
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class_parent_cached_property.with_parent = staticmethod( # type: ignore[method-assign]
|
|
195
|
+
_class_parent_cached_property_with_parent,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
class class_cached_property[T_co](
|
|
200
|
+
class_parent_cached_property[T_co],
|
|
201
|
+
):
|
|
202
|
+
"""Class-level cached descriptor that stores cache on the accessed class.
|
|
203
|
+
|
|
204
|
+
This is the "plain" variant of ``class_parent_cached_property``. Rather
|
|
205
|
+
than resolving the *owning* parent via ``get_owner``, it caches directly
|
|
206
|
+
on ``node`` (the class that was passed to ``__get__``). Consequently,
|
|
207
|
+
each subclass maintains its own independent cache.
|
|
208
|
+
"""
|
|
209
|
+
|
|
210
|
+
@override
|
|
211
|
+
def get_node(self, node: Any) -> Any:
|
|
212
|
+
if node is None or not Is.Class(node):
|
|
213
|
+
msg = f"{self.header_with_context(node)}, node={node!r}"
|
|
214
|
+
raise ContextFaultError(msg)
|
|
215
|
+
return node
|
|
216
|
+
|
|
217
|
+
@class_parent_cached_property
|
|
218
|
+
def here(cls) -> type[class_parent_cached_property[Any]]: # noqa: N805
|
|
219
|
+
return class_parent_cached_property
|
|
220
|
+
|
|
221
|
+
if TYPE_CHECKING:
|
|
222
|
+
|
|
223
|
+
@staticmethod
|
|
224
|
+
@override
|
|
225
|
+
def with_parent(
|
|
226
|
+
function: Callable[..., R],
|
|
227
|
+
) -> class_cached_property[R]: ...
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def _class_cached_property_with_parent[R](
|
|
231
|
+
function: Callable[..., R],
|
|
232
|
+
) -> class_cached_property[R]:
|
|
233
|
+
return class_cached_property(
|
|
234
|
+
cast("Callable[[Any], R]", parent_call(function)),
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class_cached_property.with_parent = staticmethod( # type: ignore[method-assign]
|
|
239
|
+
_class_cached_property_with_parent,
|
|
240
|
+
)
|
kapro/cached/klass.pyi
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
from functools import cached_property
|
|
5
|
+
from typing import TYPE_CHECKING, Any, TypeVar, override
|
|
6
|
+
|
|
7
|
+
from kapro.primitives import (
|
|
8
|
+
BaseProperty,
|
|
9
|
+
Nothing,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = ("class_cached_property", "class_parent_cached_property")
|
|
13
|
+
T_co: Any
|
|
14
|
+
R = TypeVar("R")
|
|
15
|
+
|
|
16
|
+
class CustomCallbackMixin:
|
|
17
|
+
@classmethod
|
|
18
|
+
def by(cls, callback: Callable[..., Any]) -> Callable[..., Any]: ...
|
|
19
|
+
@classmethod
|
|
20
|
+
def ttl(cls, expire: float) -> Callable[..., Any]: ...
|
|
21
|
+
|
|
22
|
+
class class_parent_cached_property[T_co](
|
|
23
|
+
BaseProperty[T_co],
|
|
24
|
+
CustomCallbackMixin,
|
|
25
|
+
):
|
|
26
|
+
def __init__(
|
|
27
|
+
self: Any,
|
|
28
|
+
function: Callable[[Any], T_co],
|
|
29
|
+
is_actual: Any = Nothing,
|
|
30
|
+
) -> None: ...
|
|
31
|
+
@cached_property
|
|
32
|
+
@override
|
|
33
|
+
def title(self) -> str: ...
|
|
34
|
+
@override
|
|
35
|
+
def header_with_context(self, node: Any) -> str: ...
|
|
36
|
+
def get_node(self, node: Any) -> Any: ...
|
|
37
|
+
def get_cache(self, node: Any) -> dict[str, Any]: ...
|
|
38
|
+
@override
|
|
39
|
+
def call(self, node: Any, *args: Any, **kw: Any) -> T_co: ...
|
|
40
|
+
def __get__(self, instance: Any, klass: Any = None) -> T_co: ...
|
|
41
|
+
def __set__(self, node: Any, value: Any) -> Any: ...
|
|
42
|
+
def __delete__(self, node: Any) -> None: ...
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
@staticmethod
|
|
46
|
+
@override
|
|
47
|
+
def with_parent( # pyright: ignore[reportIncompatibleMethodOverride]
|
|
48
|
+
function: Callable[..., R],
|
|
49
|
+
) -> class_parent_cached_property[R]: ...
|
|
50
|
+
|
|
51
|
+
def _class_parent_cached_property_with_parent[R](
|
|
52
|
+
function: Callable[..., R],
|
|
53
|
+
) -> class_parent_cached_property[R]: ...
|
|
54
|
+
|
|
55
|
+
class class_cached_property[T_co](
|
|
56
|
+
class_parent_cached_property[T_co],
|
|
57
|
+
):
|
|
58
|
+
@override
|
|
59
|
+
def get_node(self, node: Any) -> Any: ...
|
|
60
|
+
@class_parent_cached_property
|
|
61
|
+
def here(cls) -> type[class_parent_cached_property[Any]]: ...
|
|
62
|
+
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
@staticmethod
|
|
65
|
+
@override
|
|
66
|
+
def with_parent(
|
|
67
|
+
function: Callable[..., R],
|
|
68
|
+
) -> class_cached_property[R]: ...
|
|
69
|
+
|
|
70
|
+
def _class_cached_property_with_parent[R](
|
|
71
|
+
function: Callable[..., R],
|
|
72
|
+
) -> class_cached_property[R]: ...
|