jetpytools 1.7.4__py3-none-any.whl → 2.0.0__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.
Potentially problematic release.
This version of jetpytools might be problematic. Click here for more details.
- jetpytools/_metadata.py +1 -1
- jetpytools/enums/base.py +1 -7
- jetpytools/enums/other.py +5 -5
- jetpytools/exceptions/base.py +8 -31
- jetpytools/exceptions/generic.py +2 -2
- jetpytools/functions/funcs.py +18 -19
- jetpytools/functions/normalize.py +16 -18
- jetpytools/types/builtins.py +12 -70
- jetpytools/types/file.py +2 -6
- jetpytools/types/funcs.py +5 -6
- jetpytools/types/generic.py +5 -5
- jetpytools/types/supports.py +22 -39
- jetpytools/types/utils.py +388 -259
- jetpytools/utils/funcs.py +13 -10
- jetpytools/utils/math.py +3 -5
- jetpytools/utils/ranges.py +9 -50
- {jetpytools-1.7.4.dist-info → jetpytools-2.0.0.dist-info}/METADATA +3 -3
- jetpytools-2.0.0.dist-info/RECORD +33 -0
- jetpytools-1.7.4.dist-info/RECORD +0 -33
- {jetpytools-1.7.4.dist-info → jetpytools-2.0.0.dist-info}/WHEEL +0 -0
- {jetpytools-1.7.4.dist-info → jetpytools-2.0.0.dist-info}/licenses/LICENSE +0 -0
jetpytools/types/utils.py
CHANGED
|
@@ -4,28 +4,28 @@ import sys
|
|
|
4
4
|
from contextlib import suppress
|
|
5
5
|
from functools import wraps
|
|
6
6
|
from inspect import Signature
|
|
7
|
-
from inspect import _empty as empty_param
|
|
8
7
|
from typing import (
|
|
9
8
|
TYPE_CHECKING,
|
|
10
9
|
Any,
|
|
11
10
|
Callable,
|
|
12
11
|
ClassVar,
|
|
13
12
|
Concatenate,
|
|
14
|
-
Generator,
|
|
15
13
|
Generic,
|
|
16
14
|
Iterable,
|
|
17
15
|
Iterator,
|
|
18
16
|
Mapping,
|
|
19
17
|
NoReturn,
|
|
18
|
+
ParamSpec,
|
|
20
19
|
Protocol,
|
|
20
|
+
Self,
|
|
21
21
|
Sequence,
|
|
22
22
|
cast,
|
|
23
23
|
overload,
|
|
24
24
|
)
|
|
25
25
|
|
|
26
|
-
from typing_extensions import
|
|
26
|
+
from typing_extensions import TypeVar, deprecated
|
|
27
27
|
|
|
28
|
-
from .builtins import
|
|
28
|
+
from .builtins import KwargsT
|
|
29
29
|
|
|
30
30
|
__all__ = [
|
|
31
31
|
"KwargsNotNone",
|
|
@@ -38,12 +38,11 @@ __all__ = [
|
|
|
38
38
|
"get_subclasses",
|
|
39
39
|
"inject_kwargs_params",
|
|
40
40
|
"inject_self",
|
|
41
|
-
"to_singleton",
|
|
42
41
|
]
|
|
43
42
|
# ruff: noqa: N801
|
|
44
43
|
|
|
45
44
|
|
|
46
|
-
def copy_signature(target:
|
|
45
|
+
def copy_signature[F: Callable[..., Any]](target: F, /) -> Callable[[Callable[..., Any]], F]:
|
|
47
46
|
"""
|
|
48
47
|
Utility function to copy the signature of one function to another one.
|
|
49
48
|
|
|
@@ -71,292 +70,433 @@ def copy_signature(target: F0, /) -> Callable[[Callable[..., Any]], F0]:
|
|
|
71
70
|
# another thing
|
|
72
71
|
"""
|
|
73
72
|
|
|
74
|
-
def decorator(wrapped: Callable[..., Any]) ->
|
|
75
|
-
return cast(
|
|
73
|
+
def decorator(wrapped: Callable[..., Any]) -> F:
|
|
74
|
+
return cast(F, wrapped)
|
|
76
75
|
|
|
77
76
|
return decorator
|
|
78
77
|
|
|
79
78
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
@staticmethod
|
|
83
|
-
def __call__(*args: P.args, **kwargs: P.kwargs) -> R_co: ...
|
|
79
|
+
type _InnerInjectSelfType = dict[_InjectSelfMeta, dict[_InjectSelfMeta, _InnerInjectSelfType]]
|
|
80
|
+
_inject_self_cls: _InnerInjectSelfType = {}
|
|
84
81
|
|
|
85
|
-
@overload
|
|
86
|
-
@staticmethod
|
|
87
|
-
def __call__(self: T_co, *args: P.args, **kwargs: P.kwargs) -> R_co: # type: ignore[misc]
|
|
88
|
-
...
|
|
89
82
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
83
|
+
class _InjectSelfMeta(type):
|
|
84
|
+
"""
|
|
85
|
+
Metaclass used to manage subclass relationships and type flattening for the `inject_self` hierarchy.
|
|
86
|
+
"""
|
|
87
|
+
|
|
88
|
+
_subclasses: frozenset[_InjectSelfMeta]
|
|
89
|
+
"""All descendant metaclasses of a given `inject_self` subclass."""
|
|
93
90
|
|
|
91
|
+
def __new__[MetaSelf: _InjectSelfMeta](
|
|
92
|
+
mcls: type[MetaSelf], name: str, bases: tuple[type, ...], namespace: dict[str, Any], /, **kwargs: Any
|
|
93
|
+
) -> MetaSelf:
|
|
94
|
+
cls = super().__new__(mcls, name, bases, namespace, **kwargs)
|
|
94
95
|
|
|
95
|
-
|
|
96
|
+
clsd = _inject_self_cls.setdefault(cls, {})
|
|
96
97
|
|
|
98
|
+
for k, v in _inject_self_cls.items():
|
|
99
|
+
if k in namespace.values():
|
|
100
|
+
clsd[k] = v
|
|
97
101
|
|
|
98
|
-
|
|
99
|
-
cache: bool | None
|
|
100
|
-
signature: Signature | None
|
|
101
|
-
init_kwargs: list[str] | None
|
|
102
|
-
first_key: str | None
|
|
102
|
+
cls._subclasses = frozenset(cls._flatten_cls())
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
return cls
|
|
105
|
+
|
|
106
|
+
def _flatten_cls(cls, d: _InnerInjectSelfType | None = None) -> Iterator[_InjectSelfMeta]:
|
|
105
107
|
"""
|
|
106
|
-
|
|
108
|
+
Recursively flatten and yield all nested inject_self metaclass relationships.
|
|
107
109
|
|
|
108
|
-
:param
|
|
109
|
-
:
|
|
110
|
+
:param d: Optional inner dictionary representing the hierarchy level.
|
|
111
|
+
:yield: Each `_InjectSelfMeta` subclass found in the hierarchy.
|
|
110
112
|
"""
|
|
113
|
+
if d is None:
|
|
114
|
+
d = _inject_self_cls[cls]
|
|
111
115
|
|
|
112
|
-
|
|
116
|
+
for k, v in d.items():
|
|
117
|
+
yield k
|
|
118
|
+
yield from cls._flatten_cls(v)
|
|
113
119
|
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
def __instancecheck__(cls, instance: Any) -> bool:
|
|
121
|
+
"""Allow isinstance() checks to succeed for any flattened subclass."""
|
|
122
|
+
return any(type.__instancecheck__(t, instance) for t in cls._subclasses)
|
|
116
123
|
|
|
117
|
-
|
|
124
|
+
def __subclasscheck__(cls, subclass: type) -> bool:
|
|
125
|
+
"""Allow issubclass() checks to succeed for any flattened subclass."""
|
|
126
|
+
return any(type.__subclasscheck__(t, subclass) for t in cls._subclasses)
|
|
118
127
|
|
|
119
|
-
self.signature = self.first_key = self.init_kwargs = None
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
129
|
+
_T = TypeVar("_T")
|
|
130
|
+
_T0 = TypeVar("_T0")
|
|
123
131
|
|
|
124
|
-
|
|
132
|
+
_T_co = TypeVar("_T_co", covariant=True)
|
|
133
|
+
_T0_co = TypeVar("_T0_co", covariant=True)
|
|
134
|
+
_T1_co = TypeVar("_T1_co", covariant=True)
|
|
125
135
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
class_type: type[T | type[T]] | Any, # type: ignore[valid-type]
|
|
130
|
-
) -> injected_self_func[T_co, P, R_co]:
|
|
131
|
-
if not self.signature or not self.first_key:
|
|
132
|
-
self.signature = Signature.from_callable(self.function, eval_str=True)
|
|
133
|
-
self.first_key = next(iter(list(self.signature.parameters.keys())), None)
|
|
134
|
-
|
|
135
|
-
if isinstance(self, inject_self.init_kwargs):
|
|
136
|
-
from ..exceptions import CustomValueError
|
|
136
|
+
_R_co = TypeVar("_R_co", covariant=True)
|
|
137
|
+
_R0_co = TypeVar("_R0_co", covariant=True)
|
|
138
|
+
_R1_co = TypeVar("_R1_co", covariant=True)
|
|
137
139
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
140
|
+
_T_Any = TypeVar("_T_Any", default=Any)
|
|
141
|
+
_T0_Any = TypeVar("_T0_Any", default=Any)
|
|
142
|
+
|
|
143
|
+
_P = ParamSpec("_P")
|
|
144
|
+
_P0 = ParamSpec("_P0")
|
|
145
|
+
_P1 = ParamSpec("_P1")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class _InjectedSelfFunc(Protocol[_T_co, _P, _R_co]):
|
|
149
|
+
"""
|
|
150
|
+
Protocol defining the callable interface for wrapped functions under `inject_self`.
|
|
151
|
+
|
|
152
|
+
This allows the injected function to be called in any of the following forms:
|
|
153
|
+
- As a normal function: `f(*args, **kwargs)`
|
|
154
|
+
- As a bound method: `f(self, *args, **kwargs)`
|
|
155
|
+
- As a class method: `f(cls, *args, **kwargs)`
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
@overload
|
|
159
|
+
def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ...
|
|
160
|
+
@overload
|
|
161
|
+
def __call__(_self, self: _T_co, /, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... # type: ignore[misc] # noqa: N805
|
|
162
|
+
@overload
|
|
163
|
+
def __call__(self, cls: type[_T_co], /, *args: _P.args, **kwargs: _P.kwargs) -> _R_co: ... # pyright: ignore[reportGeneralTypeIssues]
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
_self_objects_cache = dict[type[Any], Any]()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class _InjectSelfBase(Generic[_T_co, _P, _R_co]):
|
|
170
|
+
"""
|
|
171
|
+
Base descriptor implementation for `inject_self`.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
__isabstractmethod__ = False
|
|
175
|
+
__slots__ = ("_function", "_init_signature", "_signature", "args", "kwargs")
|
|
176
|
+
|
|
177
|
+
_signature: Signature | None
|
|
178
|
+
_init_signature: Signature | None
|
|
179
|
+
|
|
180
|
+
def __init__(self, function: Callable[Concatenate[_T_co, _P], _R_co], /, *args: Any, **kwargs: Any) -> None:
|
|
181
|
+
"""
|
|
182
|
+
Initialize the inject_self descriptor.
|
|
183
|
+
|
|
184
|
+
:param function: The function or method to wrap.
|
|
185
|
+
:param *args: Positional arguments to pass when instantiating the target class.
|
|
186
|
+
:param **kwargs: Keyword arguments to pass when instantiating the target class.
|
|
187
|
+
"""
|
|
188
|
+
self._function = function
|
|
189
|
+
|
|
190
|
+
self._signature = self._init_signature = None
|
|
191
|
+
|
|
192
|
+
self.args = args
|
|
193
|
+
self.kwargs = kwargs
|
|
194
|
+
|
|
195
|
+
def __get__[T](self, instance: T | None, owner: type[T]) -> _InjectedSelfFunc[T, _P, _R_co]: # pyright: ignore[reportGeneralTypeIssues]
|
|
196
|
+
"""
|
|
197
|
+
Return a wrapped callable that automatically injects an instance as the first argument when called.
|
|
198
|
+
"""
|
|
199
|
+
|
|
200
|
+
@wraps(self._function)
|
|
201
|
+
def _wrapper(*args: Any, **kwargs: Any) -> _R_co:
|
|
202
|
+
"""
|
|
203
|
+
Call wrapper that performs the actual injection of `self`.
|
|
204
|
+
"""
|
|
205
|
+
if args and isinstance((first_arg := args[0]), (owner, type(owner))):
|
|
206
|
+
# Instance or class explicitly provided as first argument
|
|
207
|
+
obj = first_arg if isinstance(first_arg, owner) else first_arg() # type: ignore[operator]
|
|
208
|
+
args = args[1:]
|
|
209
|
+
elif instance is None:
|
|
210
|
+
# Accessed via class
|
|
211
|
+
obj, kwargs = self._handle_class_access(owner, kwargs)
|
|
173
212
|
else:
|
|
174
|
-
|
|
213
|
+
# Accessed via instance
|
|
214
|
+
obj = instance
|
|
175
215
|
|
|
176
|
-
return self.
|
|
216
|
+
return self._function(obj, *args, **kwargs) # type: ignore[arg-type]
|
|
177
217
|
|
|
178
218
|
return _wrapper
|
|
179
219
|
|
|
180
|
-
def
|
|
181
|
-
|
|
220
|
+
def _handle_class_access[T](self, owner: type[T], kwargs: dict[str, Any]) -> tuple[T, dict[str, Any]]:
|
|
221
|
+
"""
|
|
222
|
+
Handle logic when the descriptor is accessed from the class level.
|
|
223
|
+
|
|
224
|
+
:param owner: The class object owning the descriptor.
|
|
225
|
+
:param kwargs: Keyword arguments passed to the wrapped function.
|
|
226
|
+
:return: A tuple of `(self_object, updated_kwargs)`.
|
|
227
|
+
"""
|
|
228
|
+
if isinstance(self, inject_self.cached):
|
|
229
|
+
# Cached instance creation
|
|
230
|
+
try:
|
|
231
|
+
return _self_objects_cache[owner], kwargs
|
|
232
|
+
except KeyError:
|
|
233
|
+
return _self_objects_cache.setdefault(owner, owner()), kwargs
|
|
234
|
+
|
|
235
|
+
if isinstance(self, (inject_self.init_kwargs, inject_self.init_kwargs.clean)):
|
|
236
|
+
# Constructor accepts forwarded kwargs
|
|
237
|
+
has_kwargs = any(
|
|
238
|
+
param.kind in (param.VAR_KEYWORD, param.KEYWORD_ONLY)
|
|
239
|
+
for param in self.__signature__.parameters.values()
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
if not has_kwargs:
|
|
243
|
+
from ..exceptions import CustomValueError
|
|
244
|
+
|
|
245
|
+
raise CustomValueError(
|
|
246
|
+
f"Function {self._function.__name__} doesn't accept keyword arguments.",
|
|
247
|
+
"inject_self.init_kwargs",
|
|
248
|
+
self._function,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if not self._init_signature:
|
|
252
|
+
self._init_signature = Signature.from_callable(owner)
|
|
253
|
+
|
|
254
|
+
init_kwargs = self.kwargs | {k: kwargs[k] for k in kwargs.keys() & self._init_signature.parameters.keys()}
|
|
255
|
+
|
|
256
|
+
obj = owner(*self.args, **init_kwargs)
|
|
257
|
+
|
|
258
|
+
if isinstance(self, inject_self.init_kwargs.clean):
|
|
259
|
+
# Clean up forwarded kwargs
|
|
260
|
+
kwargs = {k: v for k, v in kwargs.items() if k not in self._init_signature.parameters}
|
|
261
|
+
|
|
262
|
+
return obj, kwargs
|
|
263
|
+
|
|
264
|
+
return owner(*self.args, **self.kwargs), kwargs
|
|
265
|
+
|
|
266
|
+
@property
|
|
267
|
+
def __func__(self) -> Callable[Concatenate[_T_co, _P], _R_co]:
|
|
268
|
+
"""Return the original wrapped function."""
|
|
269
|
+
return self._function
|
|
182
270
|
|
|
183
271
|
@property
|
|
184
272
|
def __signature__(self) -> Signature:
|
|
185
|
-
|
|
273
|
+
"""Return (and cache) the signature of the wrapped function."""
|
|
274
|
+
if not self._signature:
|
|
275
|
+
self._signature = Signature.from_callable(self._function)
|
|
276
|
+
return self._signature
|
|
186
277
|
|
|
187
278
|
@classmethod
|
|
188
|
-
def with_args(
|
|
279
|
+
def with_args[T0, **P0, R0](
|
|
189
280
|
cls, *args: Any, **kwargs: Any
|
|
190
|
-
) -> Callable[[Callable[Concatenate[
|
|
191
|
-
"""
|
|
281
|
+
) -> Callable[[Callable[Concatenate[T0, P0], R0]], inject_self[T0, P0, R0]]:
|
|
282
|
+
"""
|
|
283
|
+
Decorator factory to construct an `inject_self` or subclass (`cached`, `init_kwargs`, etc.)
|
|
284
|
+
with specific instantiation arguments.
|
|
285
|
+
"""
|
|
192
286
|
|
|
287
|
+
# TODO: The precise subclass type cannot be expressed yet when the class is itself generic.
|
|
193
288
|
def _wrapper(function: Callable[Concatenate[T0, P0], R0]) -> inject_self[T0, P0, R0]:
|
|
194
|
-
|
|
195
|
-
inj.args = args
|
|
196
|
-
inj.kwargs = kwargs
|
|
197
|
-
return inj # type: ignore
|
|
289
|
+
return cls(function, *args, **kwargs) # type: ignore[return-value, arg-type]
|
|
198
290
|
|
|
199
291
|
return _wrapper
|
|
200
292
|
|
|
201
293
|
|
|
202
|
-
class inject_self(
|
|
203
|
-
"""
|
|
294
|
+
class inject_self(_InjectSelfBase[_T_co, _P, _R_co], metaclass=_InjectSelfMeta):
|
|
295
|
+
"""
|
|
296
|
+
Descriptor that ensures the wrapped function always has a constructed `self`.
|
|
297
|
+
|
|
298
|
+
When accessed via a class, it will automatically instantiate an object before calling the function.
|
|
299
|
+
When accessed via an instance, it simply binds.
|
|
300
|
+
|
|
301
|
+
Subclasses such as `cached`, `init_kwargs`, and `init_kwargs.clean`
|
|
302
|
+
define variations in how the injected object is created or reused.
|
|
303
|
+
"""
|
|
304
|
+
|
|
305
|
+
__slots__ = ()
|
|
204
306
|
|
|
205
|
-
class cached(
|
|
307
|
+
class cached(_InjectSelfBase[_T0_co, _P0, _R0_co], metaclass=_InjectSelfMeta):
|
|
206
308
|
"""
|
|
207
|
-
|
|
208
|
-
|
|
309
|
+
Variant of `inject_self` that caches the constructed instance.
|
|
310
|
+
|
|
311
|
+
The first time the method is accessed via the class, a `self` object is created and stored.
|
|
312
|
+
Subsequent calls reuse it.
|
|
209
313
|
"""
|
|
210
314
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
315
|
+
__slots__ = ()
|
|
316
|
+
|
|
317
|
+
class property(Generic[_T1_co, _P1, _R1_co], metaclass=_InjectSelfMeta):
|
|
318
|
+
"""Property variant of `inject_self.cached` that auto-calls the wrapped method."""
|
|
214
319
|
|
|
215
|
-
|
|
216
|
-
return self.function.__get__(class_obj, class_type)()
|
|
320
|
+
__slots__ = ("__func__",)
|
|
217
321
|
|
|
218
|
-
|
|
322
|
+
def __init__(self, function: Callable[[_T1_co], _R1_co], /) -> None:
|
|
323
|
+
self.__func__ = inject_self.cached(function)
|
|
324
|
+
|
|
325
|
+
def __get__(self, instance: _T1_co | None, owner: type[_T1_co]) -> _R1_co: # pyright: ignore[reportGeneralTypeIssues]
|
|
326
|
+
"""Return the result of calling the cached method without arguments."""
|
|
327
|
+
return self.__func__.__get__(instance, owner)()
|
|
328
|
+
|
|
329
|
+
class init_kwargs(_InjectSelfBase[_T0_co, _P0, _R0_co], metaclass=_InjectSelfMeta):
|
|
219
330
|
"""
|
|
220
|
-
|
|
221
|
-
|
|
331
|
+
Variant of `inject_self` that forwards function keyword arguments to the class constructor
|
|
332
|
+
when instantiating `self`.
|
|
222
333
|
"""
|
|
223
334
|
|
|
224
|
-
|
|
225
|
-
def clean(cls, function: Callable[Concatenate[T1_co, P1], R1_co]) -> inject_self[T1_co, P1, R1_co]:
|
|
226
|
-
"""Wrap a method, pass kwargs to the constructor and remove them from actual **kwargs."""
|
|
227
|
-
inj = cls(function) # type: ignore
|
|
228
|
-
inj.clean_kwargs = True
|
|
229
|
-
return inj # type: ignore
|
|
335
|
+
__slots__ = ()
|
|
230
336
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
337
|
+
class clean(_InjectSelfBase[_T1_co, _P1, _R1_co], metaclass=_InjectSelfMeta):
|
|
338
|
+
"""
|
|
339
|
+
Variant of `inject_self.init_kwargs` that removes any forwarded kwargs from the final function call
|
|
340
|
+
after using them for construction.
|
|
341
|
+
"""
|
|
234
342
|
|
|
235
|
-
|
|
236
|
-
return self.function.__get__(class_obj, class_type)()
|
|
343
|
+
__slots__ = ()
|
|
237
344
|
|
|
345
|
+
class property(Generic[_T0_co, _R0_co], metaclass=_InjectSelfMeta):
|
|
346
|
+
"""Property variant of `inject_self` that auto-calls the wrapped method."""
|
|
238
347
|
|
|
239
|
-
|
|
240
|
-
def __call__(self: T_co, *args: P.args, **kwargs: P.kwargs) -> R_co:
|
|
241
|
-
raise NotImplementedError
|
|
348
|
+
__slots__ = ("__func__",)
|
|
242
349
|
|
|
350
|
+
def __init__(self, function: Callable[[_T0_co], _R0_co], /) -> None:
|
|
351
|
+
self.__func__ = inject_self(function)
|
|
243
352
|
|
|
244
|
-
|
|
245
|
-
|
|
353
|
+
def __get__(self, instance: _T0_co | None, owner: type[_T0_co]) -> _R0_co: # pyright: ignore[reportGeneralTypeIssues]
|
|
354
|
+
"""Return the result of calling the injected method without arguments."""
|
|
355
|
+
return self.__func__.__get__(instance, owner)()
|
|
246
356
|
|
|
247
|
-
_kwargs_name = "kwargs"
|
|
248
357
|
|
|
249
|
-
|
|
250
|
-
|
|
358
|
+
class _InjectKwargsParamsBase(Generic[_T_co, _P, _R_co]):
|
|
359
|
+
"""
|
|
360
|
+
Base descriptor implementation for `inject_kwargs_params`.
|
|
361
|
+
"""
|
|
251
362
|
|
|
252
|
-
|
|
363
|
+
__isabstractmethod__ = False
|
|
364
|
+
__slots__ = ("_function", "_signature")
|
|
253
365
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
self.signature = Signature.from_callable(self.function, eval_str=True)
|
|
366
|
+
_kwargs_name = "kwargs"
|
|
367
|
+
_signature: Signature | None
|
|
257
368
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
):
|
|
262
|
-
from ..exceptions import CustomValueError
|
|
369
|
+
def __init__(self, func: Callable[Concatenate[_T_co, _P], _R_co], /) -> None:
|
|
370
|
+
"""
|
|
371
|
+
Initialize the inject_kwargs_params descriptor.
|
|
263
372
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
373
|
+
:param function: The target function or method whose parameters will be injected
|
|
374
|
+
from the instance's `self.kwargs` mapping.
|
|
375
|
+
"""
|
|
376
|
+
self._function = func
|
|
377
|
+
self._signature = None
|
|
267
378
|
|
|
268
|
-
|
|
379
|
+
@overload
|
|
380
|
+
def __get__(self, instance: None, owner: type[Any]) -> Self: ...
|
|
381
|
+
@overload
|
|
382
|
+
def __get__(self, instance: Any, owner: type[Any]) -> Callable[_P, _R_co]: ...
|
|
383
|
+
def __get__(self, instance: Any | None, owner: type[Any]) -> Self | Callable[_P, _R_co]:
|
|
384
|
+
"""
|
|
385
|
+
Descriptor binding logic.
|
|
269
386
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
assert this.signature
|
|
387
|
+
When accessed via an instance, returns a wrapped function that injects values from `instance.<_kwargs_name>`
|
|
388
|
+
into matching function parameters.
|
|
273
389
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
390
|
+
When accessed via the class, returns the descriptor itself.
|
|
391
|
+
"""
|
|
392
|
+
if instance is None:
|
|
393
|
+
return self
|
|
277
394
|
|
|
278
|
-
|
|
279
|
-
|
|
395
|
+
if not hasattr(instance, self._kwargs_name):
|
|
396
|
+
from ..exceptions import CustomRuntimeError
|
|
280
397
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
398
|
+
raise CustomRuntimeError(
|
|
399
|
+
f'Missing attribute "{self._kwargs_name}" on {type(instance).__name__}.', owner, self.__class__
|
|
400
|
+
)
|
|
284
401
|
|
|
285
|
-
|
|
286
|
-
|
|
402
|
+
@wraps(self._function)
|
|
403
|
+
def wrapper(*args: Any, **kwargs: Any) -> _R_co:
|
|
404
|
+
"""
|
|
405
|
+
Wrapper that performs parameter injection before calling the wrapped function.
|
|
406
|
+
"""
|
|
407
|
+
injectable_kwargs = dict(getattr(instance, self._kwargs_name))
|
|
287
408
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
continue
|
|
409
|
+
if not injectable_kwargs:
|
|
410
|
+
return self._function(instance, *args, **kwargs)
|
|
291
411
|
|
|
292
|
-
|
|
412
|
+
args_list = [instance, *args]
|
|
413
|
+
kwargs = kwargs.copy()
|
|
293
414
|
|
|
294
|
-
|
|
415
|
+
for i, (name, param) in enumerate(self.__signature__.parameters.items()):
|
|
416
|
+
if name not in injectable_kwargs:
|
|
295
417
|
continue
|
|
296
418
|
|
|
297
|
-
|
|
298
|
-
if args[i] != value.default:
|
|
299
|
-
continue
|
|
419
|
+
value_from_kwargs = injectable_kwargs.pop(name)
|
|
300
420
|
|
|
301
|
-
|
|
421
|
+
if i < len(args_list):
|
|
422
|
+
# Positional arg case
|
|
423
|
+
if args_list[i] == param.default:
|
|
424
|
+
args_list[i] = value_from_kwargs
|
|
302
425
|
else:
|
|
303
|
-
|
|
304
|
-
|
|
426
|
+
# Keyword arg case
|
|
427
|
+
if kwargs.get(name, param.default) == param.default:
|
|
428
|
+
kwargs[name] = value_from_kwargs
|
|
305
429
|
|
|
306
|
-
|
|
430
|
+
# Merge leftover kwargs if subclass allows
|
|
431
|
+
if isinstance(self, inject_kwargs_params.add_to_kwargs):
|
|
432
|
+
kwargs |= injectable_kwargs
|
|
307
433
|
|
|
308
|
-
|
|
309
|
-
kwargs |= this_kwargs
|
|
434
|
+
return self._function(*tuple(args_list), **kwargs)
|
|
310
435
|
|
|
311
|
-
|
|
436
|
+
return wrapper
|
|
312
437
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
return self.
|
|
438
|
+
@property
|
|
439
|
+
def __func__(self) -> Callable[Concatenate[_T_co, _P], _R_co]:
|
|
440
|
+
"""Return the original wrapped function."""
|
|
441
|
+
return self._function
|
|
317
442
|
|
|
318
443
|
@property
|
|
319
444
|
def __signature__(self) -> Signature:
|
|
320
|
-
|
|
445
|
+
"""Return (and cache) the signature of the wrapped function."""
|
|
446
|
+
if not self._signature:
|
|
447
|
+
self._signature = Signature.from_callable(self._function)
|
|
448
|
+
return self._signature
|
|
321
449
|
|
|
322
450
|
@classmethod
|
|
323
|
-
def with_name
|
|
324
|
-
|
|
325
|
-
|
|
451
|
+
def with_name[T0, **P0, R0](
|
|
452
|
+
cls, kwargs_name: str = "kwargs"
|
|
453
|
+
) -> Callable[[Callable[Concatenate[T0, P0], R0]], inject_kwargs_params[T0, P0, R0]]:
|
|
454
|
+
"""
|
|
455
|
+
Decorator factory that creates a subclass of `inject_kwargs_params` with a custom name
|
|
456
|
+
for the keyword argument store.
|
|
457
|
+
"""
|
|
458
|
+
ns = cls.__dict__.copy()
|
|
459
|
+
ns["_kwargs_name"] = kwargs_name
|
|
326
460
|
|
|
327
|
-
|
|
461
|
+
custom_cls = type(cls.__name__, cls.__bases__, ns)
|
|
328
462
|
|
|
463
|
+
# TODO: The precise subclass type cannot be expressed yet when the class is itself generic.
|
|
464
|
+
def _wrapper(function: Callable[Concatenate[T0, P0], R0]) -> inject_kwargs_params[T0, P0, R0]:
|
|
465
|
+
return custom_cls(function) # pyright: ignore[reportReturnType, reportArgumentType]
|
|
329
466
|
|
|
330
|
-
|
|
467
|
+
return _wrapper
|
|
331
468
|
|
|
332
|
-
class _add_to_kwargs:
|
|
333
|
-
def __call__(self, func: F1) -> F1: ...
|
|
334
469
|
|
|
335
|
-
|
|
336
|
-
|
|
470
|
+
class inject_kwargs_params[T, **P, R](_InjectKwargsParamsBase[T, P, R]):
|
|
471
|
+
"""
|
|
472
|
+
Descriptor that injects parameters into functions based on an instance's keyword mapping.
|
|
337
473
|
|
|
338
|
-
|
|
474
|
+
When a method wrapped with `@inject_kwargs_params` is called, the descriptor inspects the function's signature
|
|
475
|
+
and replaces any arguments matching keys in `self.kwargs` (or another mapping defined by `_kwargs_name`)
|
|
476
|
+
if their values equal the parameter's default.
|
|
477
|
+
"""
|
|
339
478
|
|
|
340
|
-
|
|
341
|
-
else:
|
|
479
|
+
__slots__ = ()
|
|
342
480
|
|
|
343
|
-
class
|
|
344
|
-
|
|
481
|
+
class add_to_kwargs[T0, **P0, R0](_InjectKwargsParamsBase[T0, P0, R0]):
|
|
482
|
+
"""
|
|
483
|
+
Variant of `inject_kwargs_params` that merges unused entries from `self.kwargs` into the keyword arguments
|
|
484
|
+
passed to the target function.
|
|
345
485
|
|
|
486
|
+
This allows additional context or configuration values to be forwarded without requiring explicit parameters.
|
|
487
|
+
"""
|
|
346
488
|
|
|
347
|
-
|
|
348
|
-
"""
|
|
349
|
-
Decorator for classes to add a ``__hash__`` method to them.
|
|
489
|
+
__slots__ = ()
|
|
350
490
|
|
|
351
|
-
Especially useful for NamedTuples.
|
|
352
|
-
"""
|
|
353
491
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
def __hash__(self) -> int:
|
|
357
|
-
return complex_hash.hash(self.__class__.__name__, *(getattr(self, key) for key in self.__annotations__))
|
|
492
|
+
class _ComplexHash[**P, R]:
|
|
493
|
+
__slots__ = "func"
|
|
358
494
|
|
|
359
|
-
|
|
495
|
+
def __init__(self, func: Callable[P, R]) -> None:
|
|
496
|
+
self.func = func
|
|
497
|
+
|
|
498
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
499
|
+
return self.func(*args, **kwargs)
|
|
360
500
|
|
|
361
501
|
@staticmethod
|
|
362
502
|
def hash(*args: Any) -> int:
|
|
@@ -380,7 +520,24 @@ class complex_hash(Generic[T]):
|
|
|
380
520
|
return hash("_".join(values))
|
|
381
521
|
|
|
382
522
|
|
|
383
|
-
|
|
523
|
+
@_ComplexHash
|
|
524
|
+
def complex_hash[T](cls: type[T]) -> type[T]:
|
|
525
|
+
"""
|
|
526
|
+
Decorator for classes to add a ``__hash__`` method to them.
|
|
527
|
+
|
|
528
|
+
Especially useful for NamedTuples.
|
|
529
|
+
"""
|
|
530
|
+
|
|
531
|
+
def __hash__(self: T) -> int: # noqa: N807
|
|
532
|
+
return complex_hash.hash(self.__class__.__name__, *(getattr(self, key) for key in self.__annotations__))
|
|
533
|
+
|
|
534
|
+
ns = cls.__dict__.copy()
|
|
535
|
+
ns["__hash__"] = __hash__
|
|
536
|
+
|
|
537
|
+
return type(cls.__name__, (cls,), ns) # pyright: ignore[reportReturnType]
|
|
538
|
+
|
|
539
|
+
|
|
540
|
+
def get_subclasses[T](family: type[T], exclude: Sequence[type[T]] = []) -> list[type[T]]:
|
|
384
541
|
"""
|
|
385
542
|
Get all subclasses of a given type.
|
|
386
543
|
|
|
@@ -391,7 +548,7 @@ def get_subclasses(family: type[T], exclude: Sequence[type[T]] = []) -> list[typ
|
|
|
391
548
|
:return: List of all subclasses of "family".
|
|
392
549
|
"""
|
|
393
550
|
|
|
394
|
-
def _subclasses(cls: type[T]) ->
|
|
551
|
+
def _subclasses(cls: type[T]) -> Iterator[type[T]]:
|
|
395
552
|
for subclass in cls.__subclasses__():
|
|
396
553
|
yield from _subclasses(subclass)
|
|
397
554
|
if subclass in exclude:
|
|
@@ -401,24 +558,20 @@ def get_subclasses(family: type[T], exclude: Sequence[type[T]] = []) -> list[typ
|
|
|
401
558
|
return list(set(_subclasses(family)))
|
|
402
559
|
|
|
403
560
|
|
|
404
|
-
|
|
405
|
-
_T0_Any = TypeVar("_T0_Any", default=Any)
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
561
|
+
class classproperty_base(Generic[_T, _R_co, _T_Any]):
|
|
409
562
|
__isabstractmethod__: bool = False
|
|
410
563
|
|
|
411
|
-
fget: Callable[[type[
|
|
412
|
-
fset: Callable[Concatenate[type[
|
|
413
|
-
fdel: Callable[[type[
|
|
564
|
+
fget: Callable[[type[_T]], _R_co]
|
|
565
|
+
fset: Callable[Concatenate[type[_T], _T_Any, ...], None] | None
|
|
566
|
+
fdel: Callable[[type[_T]], None] | None
|
|
414
567
|
|
|
415
568
|
def __init__(
|
|
416
569
|
self,
|
|
417
|
-
fget: Callable[[type[
|
|
418
|
-
fset: Callable[Concatenate[type[
|
|
419
|
-
| classmethod[
|
|
570
|
+
fget: Callable[[type[_T]], _R_co] | classmethod[_T, ..., _R_co],
|
|
571
|
+
fset: Callable[Concatenate[type[_T], _T_Any, ...], None]
|
|
572
|
+
| classmethod[_T, Concatenate[_T_Any, ...], None]
|
|
420
573
|
| None = None,
|
|
421
|
-
fdel: Callable[[type[
|
|
574
|
+
fdel: Callable[[type[_T]], None] | classmethod[_T, ..., None] | None = None,
|
|
422
575
|
doc: str | None = None,
|
|
423
576
|
) -> None:
|
|
424
577
|
self.fget = fget.__func__ if isinstance(fget, classmethod) else fget
|
|
@@ -431,7 +584,7 @@ class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
|
431
584
|
def __set_name__(self, owner: object, name: str) -> None:
|
|
432
585
|
self.__name__ = name
|
|
433
586
|
|
|
434
|
-
def _get_cache(self, type_: type[
|
|
587
|
+
def _get_cache(self, type_: type[_T]) -> dict[str, Any]:
|
|
435
588
|
cache_key = getattr(self, "cache_key")
|
|
436
589
|
|
|
437
590
|
if not hasattr(type_, cache_key):
|
|
@@ -439,7 +592,7 @@ class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
|
439
592
|
|
|
440
593
|
return getattr(type_, cache_key)
|
|
441
594
|
|
|
442
|
-
def __get__(self, obj:
|
|
595
|
+
def __get__(self, obj: _T | None, type_: type[_T] | None = None) -> _R_co:
|
|
443
596
|
if type_ is None and obj is not None:
|
|
444
597
|
type_ = type(obj)
|
|
445
598
|
elif type_ is None:
|
|
@@ -455,7 +608,7 @@ class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
|
455
608
|
cache[self.__name__] = value
|
|
456
609
|
return value
|
|
457
610
|
|
|
458
|
-
def __set__(self, obj:
|
|
611
|
+
def __set__(self, obj: _T, value: _T_Any) -> None:
|
|
459
612
|
if not self.fset:
|
|
460
613
|
raise AttributeError(
|
|
461
614
|
f'classproperty with getter "{self.__name__}" of "{obj.__class__.__name__}" object has no setter.'
|
|
@@ -471,7 +624,7 @@ class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
|
471
624
|
|
|
472
625
|
self.fset(type_, value)
|
|
473
626
|
|
|
474
|
-
def __delete__(self, obj:
|
|
627
|
+
def __delete__(self, obj: _T) -> None:
|
|
475
628
|
if not self.fdel:
|
|
476
629
|
raise AttributeError(
|
|
477
630
|
f'classproperty with getter "{self.__name__}" of "{obj.__class__.__name__}" object has no deleter.'
|
|
@@ -488,12 +641,12 @@ class classproperty_base(Generic[T, R_co, _T_Any]):
|
|
|
488
641
|
self.fdel(type_)
|
|
489
642
|
|
|
490
643
|
|
|
491
|
-
class classproperty(classproperty_base[
|
|
644
|
+
class classproperty(classproperty_base[_T, _R_co, _T_Any]):
|
|
492
645
|
"""
|
|
493
646
|
A combination of `classmethod` and `property`.
|
|
494
647
|
"""
|
|
495
648
|
|
|
496
|
-
class cached(classproperty_base[
|
|
649
|
+
class cached(classproperty_base[_T0, _R0_co, _T0_Any]):
|
|
497
650
|
"""
|
|
498
651
|
A combination of `classmethod` and `property`.
|
|
499
652
|
|
|
@@ -526,7 +679,7 @@ class classproperty(classproperty_base[T, R_co, _T_Any]):
|
|
|
526
679
|
del cache[name]
|
|
527
680
|
|
|
528
681
|
|
|
529
|
-
class cachedproperty(property, Generic[
|
|
682
|
+
class cachedproperty(property, Generic[_R_co, _T_Any]):
|
|
530
683
|
"""
|
|
531
684
|
Wrapper for a one-time get property, that will be cached.
|
|
532
685
|
|
|
@@ -547,17 +700,17 @@ class cachedproperty(property, Generic[R_co, _T_Any]):
|
|
|
547
700
|
|
|
548
701
|
def __init__(
|
|
549
702
|
self,
|
|
550
|
-
fget: Callable[[Any],
|
|
703
|
+
fget: Callable[[Any], _R_co],
|
|
551
704
|
fset: Callable[[Any, _T_Any], None] | None = None,
|
|
552
705
|
fdel: Callable[[Any], None] | None = None,
|
|
553
706
|
doc: str | None = None,
|
|
554
707
|
) -> None: ...
|
|
555
708
|
|
|
556
|
-
def getter(self, fget: Callable[...,
|
|
709
|
+
def getter(self, fget: Callable[..., _R_co]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
557
710
|
|
|
558
|
-
def setter(self, fset: Callable[[Any, _T_Any], None]) -> cachedproperty[
|
|
711
|
+
def setter(self, fset: Callable[[Any, _T_Any], None]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
559
712
|
|
|
560
|
-
def deleter(self, fdel: Callable[..., None]) -> cachedproperty[
|
|
713
|
+
def deleter(self, fdel: Callable[..., None]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
561
714
|
|
|
562
715
|
if sys.version_info < (3, 13):
|
|
563
716
|
|
|
@@ -569,7 +722,7 @@ class cachedproperty(property, Generic[R_co, _T_Any]):
|
|
|
569
722
|
def __get__(self, instance: None, owner: type | None = None) -> Self: ...
|
|
570
723
|
|
|
571
724
|
@overload
|
|
572
|
-
def __get__(self, instance: Any, owner: type | None = None) ->
|
|
725
|
+
def __get__(self, instance: Any, owner: type | None = None) -> _R_co: ...
|
|
573
726
|
|
|
574
727
|
def __get__(self, instance: Any, owner: type | None = None) -> Any:
|
|
575
728
|
if instance is None:
|
|
@@ -629,18 +782,20 @@ class cachedproperty(property, Generic[R_co, _T_Any]):
|
|
|
629
782
|
class KwargsNotNone(KwargsT):
|
|
630
783
|
"""Remove all None objects from this kwargs dict."""
|
|
631
784
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
return KwargsT(**{key: value for key, value in KwargsT(*args, **kwargs).items() if value is not None})
|
|
785
|
+
@copy_signature(KwargsT.__init__)
|
|
786
|
+
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
787
|
+
super().__init__({key: value for key, value in dict(*args, **kwargs).items() if value is not None})
|
|
636
788
|
|
|
637
789
|
|
|
638
790
|
class SingletonMeta(type):
|
|
639
791
|
_instances: ClassVar[dict[type[Any], Any]] = {}
|
|
640
792
|
_singleton_init: bool
|
|
641
793
|
|
|
642
|
-
def __new__
|
|
643
|
-
|
|
794
|
+
def __new__[MetaSelf: SingletonMeta](
|
|
795
|
+
mcls: type[MetaSelf], name: str, bases: tuple[type, ...], namespace: dict[str, Any], **kwargs: Any
|
|
796
|
+
) -> MetaSelf:
|
|
797
|
+
namespace["_singleton_init"] = kwargs.pop("init", False)
|
|
798
|
+
return super().__new__(mcls, name, bases, namespace, **kwargs)
|
|
644
799
|
|
|
645
800
|
def __call__(cls, *args: Any, **kwargs: Any) -> SingletonMeta:
|
|
646
801
|
if cls not in cls._instances:
|
|
@@ -654,34 +809,7 @@ class SingletonMeta(type):
|
|
|
654
809
|
class Singleton(metaclass=SingletonMeta):
|
|
655
810
|
"""Handy class to inherit to have the SingletonMeta metaclass."""
|
|
656
811
|
|
|
657
|
-
|
|
658
|
-
class to_singleton_impl:
|
|
659
|
-
_ts_args = tuple[str, ...]()
|
|
660
|
-
_ts_kwargs: Mapping[str, Any] = {}
|
|
661
|
-
_add_classes = tuple[type, ...]()
|
|
662
|
-
|
|
663
|
-
def __new__(_cls, cls: type[T]) -> T: # type: ignore
|
|
664
|
-
if _cls._add_classes:
|
|
665
|
-
|
|
666
|
-
class rcls(cls, *_cls._add_classes): # type: ignore
|
|
667
|
-
...
|
|
668
|
-
else:
|
|
669
|
-
rcls = cls # type: ignore
|
|
670
|
-
|
|
671
|
-
return rcls(*_cls._ts_args, **_cls._ts_kwargs)
|
|
672
|
-
|
|
673
|
-
@classmethod
|
|
674
|
-
def with_args(cls, *args: Any, **kwargs: Any) -> type[to_singleton]:
|
|
675
|
-
class _inner_singl(cls): # type: ignore
|
|
676
|
-
_ts_args = args
|
|
677
|
-
_ts_kwargs = kwargs
|
|
678
|
-
|
|
679
|
-
return _inner_singl
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
class to_singleton(to_singleton_impl):
|
|
683
|
-
class as_property(to_singleton_impl):
|
|
684
|
-
_add_classes = (property,)
|
|
812
|
+
__slots__ = ()
|
|
685
813
|
|
|
686
814
|
|
|
687
815
|
class LinearRangeLut(Mapping[int, int]):
|
|
@@ -706,13 +834,14 @@ class LinearRangeLut(Mapping[int, int]):
|
|
|
706
834
|
if self._misses_n > 2:
|
|
707
835
|
self._ranges_idx_lut = self._ranges_idx_lut[missed_hit:] + self._ranges_idx_lut[:missed_hit]
|
|
708
836
|
|
|
709
|
-
return idx
|
|
837
|
+
return idx # pyright: ignore[reportPossiblyUnboundVariable]
|
|
710
838
|
|
|
711
839
|
def __len__(self) -> int:
|
|
712
840
|
return len(self.ranges)
|
|
713
841
|
|
|
714
842
|
def __iter__(self) -> Iterator[int]:
|
|
715
|
-
|
|
843
|
+
for i in range(len(self)):
|
|
844
|
+
yield i
|
|
716
845
|
|
|
717
846
|
def __setitem__(self, n: int, _range: range) -> NoReturn:
|
|
718
847
|
raise NotImplementedError
|