jetpytools 1.7.3__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 +407 -271
- jetpytools/utils/funcs.py +13 -10
- jetpytools/utils/math.py +3 -5
- jetpytools/utils/ranges.py +9 -50
- {jetpytools-1.7.3.dist-info → jetpytools-2.0.0.dist-info}/METADATA +3 -3
- jetpytools-2.0.0.dist-info/RECORD +33 -0
- jetpytools-1.7.3.dist-info/RECORD +0 -33
- {jetpytools-1.7.3.dist-info → jetpytools-2.0.0.dist-info}/WHEEL +0 -0
- {jetpytools-1.7.3.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
|
-
def __init__(self, function: Callable[[T1_co], R1_co]) -> None:
|
|
213
|
-
self.function = inject_self(function)
|
|
315
|
+
__slots__ = ()
|
|
214
316
|
|
|
215
|
-
|
|
216
|
-
|
|
317
|
+
class property(Generic[_T1_co, _P1, _R1_co], metaclass=_InjectSelfMeta):
|
|
318
|
+
"""Property variant of `inject_self.cached` that auto-calls the wrapped method."""
|
|
217
319
|
|
|
218
|
-
|
|
320
|
+
__slots__ = ("__func__",)
|
|
321
|
+
|
|
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]
|
|
466
|
+
|
|
467
|
+
return _wrapper
|
|
329
468
|
|
|
330
|
-
if TYPE_CHECKING: # love you mypy...
|
|
331
469
|
|
|
332
|
-
|
|
333
|
-
|
|
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.
|
|
334
473
|
|
|
335
|
-
|
|
336
|
-
|
|
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
|
+
"""
|
|
337
478
|
|
|
338
|
-
|
|
479
|
+
__slots__ = ()
|
|
339
480
|
|
|
340
|
-
|
|
341
|
-
|
|
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.
|
|
342
485
|
|
|
343
|
-
|
|
344
|
-
|
|
486
|
+
This allows additional context or configuration values to be forwarded without requiring explicit parameters.
|
|
487
|
+
"""
|
|
345
488
|
|
|
489
|
+
__slots__ = ()
|
|
346
490
|
|
|
347
|
-
class complex_hash(Generic[T]):
|
|
348
|
-
"""
|
|
349
|
-
Decorator for classes to add a ``__hash__`` method to them.
|
|
350
491
|
|
|
351
|
-
|
|
352
|
-
""
|
|
492
|
+
class _ComplexHash[**P, R]:
|
|
493
|
+
__slots__ = "func"
|
|
353
494
|
|
|
354
|
-
def
|
|
355
|
-
|
|
356
|
-
def __hash__(self) -> int:
|
|
357
|
-
return complex_hash.hash(self.__class__.__name__, *(getattr(self, key) for key in self.__annotations__))
|
|
495
|
+
def __init__(self, func: Callable[P, R]) -> None:
|
|
496
|
+
self.func = func
|
|
358
497
|
|
|
359
|
-
|
|
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,35 +558,33 @@ def get_subclasses(family: type[T], exclude: Sequence[type[T]] = []) -> list[typ
|
|
|
401
558
|
return list(set(_subclasses(family)))
|
|
402
559
|
|
|
403
560
|
|
|
404
|
-
class classproperty_base(Generic[
|
|
561
|
+
class classproperty_base(Generic[_T, _R_co, _T_Any]):
|
|
405
562
|
__isabstractmethod__: bool = False
|
|
406
563
|
|
|
564
|
+
fget: Callable[[type[_T]], _R_co]
|
|
565
|
+
fset: Callable[Concatenate[type[_T], _T_Any, ...], None] | None
|
|
566
|
+
fdel: Callable[[type[_T]], None] | None
|
|
567
|
+
|
|
407
568
|
def __init__(
|
|
408
569
|
self,
|
|
409
|
-
fget: Callable[[type[
|
|
410
|
-
fset: Callable[Concatenate[type[
|
|
411
|
-
| 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]
|
|
412
573
|
| None = None,
|
|
413
|
-
fdel: Callable[[type[
|
|
574
|
+
fdel: Callable[[type[_T]], None] | classmethod[_T, ..., None] | None = None,
|
|
414
575
|
doc: str | None = None,
|
|
415
576
|
) -> None:
|
|
416
|
-
self.fget =
|
|
417
|
-
self.fset =
|
|
418
|
-
self.fdel =
|
|
577
|
+
self.fget = fget.__func__ if isinstance(fget, classmethod) else fget
|
|
578
|
+
self.fset = fset.__func__ if isinstance(fset, classmethod) else fset
|
|
579
|
+
self.fdel = fdel.__func__ if isinstance(fdel, classmethod) else fdel
|
|
419
580
|
|
|
420
581
|
self.__doc__ = doc
|
|
421
582
|
self.__name__ = self.fget.__name__
|
|
422
583
|
|
|
423
|
-
def _wrap(self, func: Callable[..., R1] | classmethod[T, P1, R1]) -> classmethod[T, P1, R1]:
|
|
424
|
-
if not isinstance(func, classmethod):
|
|
425
|
-
func = classmethod(func)
|
|
426
|
-
|
|
427
|
-
return func
|
|
428
|
-
|
|
429
584
|
def __set_name__(self, owner: object, name: str) -> None:
|
|
430
585
|
self.__name__ = name
|
|
431
586
|
|
|
432
|
-
def _get_cache(self, type_: type) -> dict[str, Any]:
|
|
587
|
+
def _get_cache(self, type_: type[_T]) -> dict[str, Any]:
|
|
433
588
|
cache_key = getattr(self, "cache_key")
|
|
434
589
|
|
|
435
590
|
if not hasattr(type_, cache_key):
|
|
@@ -437,21 +592,23 @@ class classproperty_base(Generic[T, R_co]):
|
|
|
437
592
|
|
|
438
593
|
return getattr(type_, cache_key)
|
|
439
594
|
|
|
440
|
-
def __get__(self, obj:
|
|
441
|
-
if type_ is None:
|
|
595
|
+
def __get__(self, obj: _T | None, type_: type[_T] | None = None) -> _R_co:
|
|
596
|
+
if type_ is None and obj is not None:
|
|
442
597
|
type_ = type(obj)
|
|
598
|
+
elif type_ is None:
|
|
599
|
+
raise NotImplementedError("Both obj and type_ are None")
|
|
443
600
|
|
|
444
601
|
if not isinstance(self, classproperty.cached):
|
|
445
|
-
return self.fget
|
|
602
|
+
return self.fget(type_)
|
|
446
603
|
|
|
447
604
|
if self.__name__ in (cache := self._get_cache(type_)):
|
|
448
605
|
return cache[self.__name__]
|
|
449
606
|
|
|
450
|
-
value = self.fget
|
|
607
|
+
value = self.fget(type_)
|
|
451
608
|
cache[self.__name__] = value
|
|
452
609
|
return value
|
|
453
610
|
|
|
454
|
-
def __set__(self, obj:
|
|
611
|
+
def __set__(self, obj: _T, value: _T_Any) -> None:
|
|
455
612
|
if not self.fset:
|
|
456
613
|
raise AttributeError(
|
|
457
614
|
f'classproperty with getter "{self.__name__}" of "{obj.__class__.__name__}" object has no setter.'
|
|
@@ -459,12 +616,15 @@ class classproperty_base(Generic[T, R_co]):
|
|
|
459
616
|
|
|
460
617
|
type_ = type(obj)
|
|
461
618
|
|
|
619
|
+
if not isinstance(self, classproperty.cached):
|
|
620
|
+
return self.fset(type_, value)
|
|
621
|
+
|
|
462
622
|
if self.__name__ in (cache := self._get_cache(type_)):
|
|
463
623
|
del cache[self.__name__]
|
|
464
624
|
|
|
465
|
-
self.fset
|
|
625
|
+
self.fset(type_, value)
|
|
466
626
|
|
|
467
|
-
def __delete__(self, obj:
|
|
627
|
+
def __delete__(self, obj: _T) -> None:
|
|
468
628
|
if not self.fdel:
|
|
469
629
|
raise AttributeError(
|
|
470
630
|
f'classproperty with getter "{self.__name__}" of "{obj.__class__.__name__}" object has no deleter.'
|
|
@@ -472,18 +632,21 @@ class classproperty_base(Generic[T, R_co]):
|
|
|
472
632
|
|
|
473
633
|
type_ = type(obj)
|
|
474
634
|
|
|
635
|
+
if not isinstance(self, classproperty.cached):
|
|
636
|
+
return self.fdel(type_)
|
|
637
|
+
|
|
475
638
|
if self.__name__ in (cache := self._get_cache(type_)):
|
|
476
639
|
del cache[self.__name__]
|
|
477
640
|
|
|
478
|
-
self.fdel
|
|
641
|
+
self.fdel(type_)
|
|
479
642
|
|
|
480
643
|
|
|
481
|
-
class classproperty(classproperty_base[
|
|
644
|
+
class classproperty(classproperty_base[_T, _R_co, _T_Any]):
|
|
482
645
|
"""
|
|
483
646
|
A combination of `classmethod` and `property`.
|
|
484
647
|
"""
|
|
485
648
|
|
|
486
|
-
class cached(classproperty_base[
|
|
649
|
+
class cached(classproperty_base[_T0, _R0_co, _T0_Any]):
|
|
487
650
|
"""
|
|
488
651
|
A combination of `classmethod` and `property`.
|
|
489
652
|
|
|
@@ -516,10 +679,7 @@ class classproperty(classproperty_base[T, R_co]):
|
|
|
516
679
|
del cache[name]
|
|
517
680
|
|
|
518
681
|
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
class cachedproperty(property, Generic[R_co, _T_cc]):
|
|
682
|
+
class cachedproperty(property, Generic[_R_co, _T_Any]):
|
|
523
683
|
"""
|
|
524
684
|
Wrapper for a one-time get property, that will be cached.
|
|
525
685
|
|
|
@@ -540,17 +700,17 @@ class cachedproperty(property, Generic[R_co, _T_cc]):
|
|
|
540
700
|
|
|
541
701
|
def __init__(
|
|
542
702
|
self,
|
|
543
|
-
fget: Callable[[Any],
|
|
544
|
-
fset: Callable[[Any,
|
|
703
|
+
fget: Callable[[Any], _R_co],
|
|
704
|
+
fset: Callable[[Any, _T_Any], None] | None = None,
|
|
545
705
|
fdel: Callable[[Any], None] | None = None,
|
|
546
706
|
doc: str | None = None,
|
|
547
707
|
) -> None: ...
|
|
548
708
|
|
|
549
|
-
def getter(self, fget: Callable[...,
|
|
709
|
+
def getter(self, fget: Callable[..., _R_co]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
550
710
|
|
|
551
|
-
def setter(self, fset: Callable[[Any,
|
|
711
|
+
def setter(self, fset: Callable[[Any, _T_Any], None]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
552
712
|
|
|
553
|
-
def deleter(self, fdel: Callable[..., None]) -> cachedproperty[
|
|
713
|
+
def deleter(self, fdel: Callable[..., None]) -> cachedproperty[_R_co, _T_Any]: ...
|
|
554
714
|
|
|
555
715
|
if sys.version_info < (3, 13):
|
|
556
716
|
|
|
@@ -562,7 +722,7 @@ class cachedproperty(property, Generic[R_co, _T_cc]):
|
|
|
562
722
|
def __get__(self, instance: None, owner: type | None = None) -> Self: ...
|
|
563
723
|
|
|
564
724
|
@overload
|
|
565
|
-
def __get__(self, instance: Any, owner: type | None = None) ->
|
|
725
|
+
def __get__(self, instance: Any, owner: type | None = None) -> _R_co: ...
|
|
566
726
|
|
|
567
727
|
def __get__(self, instance: Any, owner: type | None = None) -> Any:
|
|
568
728
|
if instance is None:
|
|
@@ -575,7 +735,7 @@ class cachedproperty(property, Generic[R_co, _T_cc]):
|
|
|
575
735
|
cache[self.__name__] = value
|
|
576
736
|
return value
|
|
577
737
|
|
|
578
|
-
def __set__(self, instance: Any, value:
|
|
738
|
+
def __set__(self, instance: Any, value: _T_Any) -> None:
|
|
579
739
|
if self.__name__ in (cache := instance.__dict__.setdefault(self.cache_key, {})):
|
|
580
740
|
del cache[self.__name__]
|
|
581
741
|
|
|
@@ -622,18 +782,20 @@ class cachedproperty(property, Generic[R_co, _T_cc]):
|
|
|
622
782
|
class KwargsNotNone(KwargsT):
|
|
623
783
|
"""Remove all None objects from this kwargs dict."""
|
|
624
784
|
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
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})
|
|
629
788
|
|
|
630
789
|
|
|
631
790
|
class SingletonMeta(type):
|
|
632
791
|
_instances: ClassVar[dict[type[Any], Any]] = {}
|
|
633
792
|
_singleton_init: bool
|
|
634
793
|
|
|
635
|
-
def __new__
|
|
636
|
-
|
|
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)
|
|
637
799
|
|
|
638
800
|
def __call__(cls, *args: Any, **kwargs: Any) -> SingletonMeta:
|
|
639
801
|
if cls not in cls._instances:
|
|
@@ -647,34 +809,7 @@ class SingletonMeta(type):
|
|
|
647
809
|
class Singleton(metaclass=SingletonMeta):
|
|
648
810
|
"""Handy class to inherit to have the SingletonMeta metaclass."""
|
|
649
811
|
|
|
650
|
-
|
|
651
|
-
class to_singleton_impl:
|
|
652
|
-
_ts_args = tuple[str, ...]()
|
|
653
|
-
_ts_kwargs: Mapping[str, Any] = {}
|
|
654
|
-
_add_classes = tuple[type, ...]()
|
|
655
|
-
|
|
656
|
-
def __new__(_cls, cls: type[T]) -> T: # type: ignore
|
|
657
|
-
if _cls._add_classes:
|
|
658
|
-
|
|
659
|
-
class rcls(cls, *_cls._add_classes): # type: ignore
|
|
660
|
-
...
|
|
661
|
-
else:
|
|
662
|
-
rcls = cls # type: ignore
|
|
663
|
-
|
|
664
|
-
return rcls(*_cls._ts_args, **_cls._ts_kwargs)
|
|
665
|
-
|
|
666
|
-
@classmethod
|
|
667
|
-
def with_args(cls, *args: Any, **kwargs: Any) -> type[to_singleton]:
|
|
668
|
-
class _inner_singl(cls): # type: ignore
|
|
669
|
-
_ts_args = args
|
|
670
|
-
_ts_kwargs = kwargs
|
|
671
|
-
|
|
672
|
-
return _inner_singl
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
class to_singleton(to_singleton_impl):
|
|
676
|
-
class as_property(to_singleton_impl):
|
|
677
|
-
_add_classes = (property,)
|
|
812
|
+
__slots__ = ()
|
|
678
813
|
|
|
679
814
|
|
|
680
815
|
class LinearRangeLut(Mapping[int, int]):
|
|
@@ -699,13 +834,14 @@ class LinearRangeLut(Mapping[int, int]):
|
|
|
699
834
|
if self._misses_n > 2:
|
|
700
835
|
self._ranges_idx_lut = self._ranges_idx_lut[missed_hit:] + self._ranges_idx_lut[:missed_hit]
|
|
701
836
|
|
|
702
|
-
return idx
|
|
837
|
+
return idx # pyright: ignore[reportPossiblyUnboundVariable]
|
|
703
838
|
|
|
704
839
|
def __len__(self) -> int:
|
|
705
840
|
return len(self.ranges)
|
|
706
841
|
|
|
707
842
|
def __iter__(self) -> Iterator[int]:
|
|
708
|
-
|
|
843
|
+
for i in range(len(self)):
|
|
844
|
+
yield i
|
|
709
845
|
|
|
710
846
|
def __setitem__(self, n: int, _range: range) -> NoReturn:
|
|
711
847
|
raise NotImplementedError
|