jetpytools 1.2.3__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/__init__.py +5 -0
- jetpytools/_metadata.py +12 -0
- jetpytools/enums/__init__.py +2 -0
- jetpytools/enums/base.py +78 -0
- jetpytools/enums/other.py +59 -0
- jetpytools/exceptions/__init__.py +5 -0
- jetpytools/exceptions/base.py +213 -0
- jetpytools/exceptions/enum.py +11 -0
- jetpytools/exceptions/file.py +38 -0
- jetpytools/exceptions/generic.py +45 -0
- jetpytools/exceptions/module.py +39 -0
- jetpytools/functions/__init__.py +3 -0
- jetpytools/functions/funcs.py +152 -0
- jetpytools/functions/normalize.py +254 -0
- jetpytools/functions/other.py +18 -0
- jetpytools/py.typed +0 -0
- jetpytools/types/__init__.py +6 -0
- jetpytools/types/builtins.py +77 -0
- jetpytools/types/file.py +193 -0
- jetpytools/types/funcs.py +109 -0
- jetpytools/types/generic.py +52 -0
- jetpytools/types/supports.py +127 -0
- jetpytools/types/utils.py +669 -0
- jetpytools/utils/__init__.py +4 -0
- jetpytools/utils/file.py +256 -0
- jetpytools/utils/funcs.py +35 -0
- jetpytools/utils/math.py +158 -0
- jetpytools/utils/ranges.py +89 -0
- jetpytools-1.2.3.dist-info/LICENSE +21 -0
- jetpytools-1.2.3.dist-info/METADATA +48 -0
- jetpytools-1.2.3.dist-info/RECORD +33 -0
- jetpytools-1.2.3.dist-info/WHEEL +5 -0
- jetpytools-1.2.3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from functools import partial, wraps
|
|
4
|
+
from inspect import Signature
|
|
5
|
+
from inspect import _empty as empty_param
|
|
6
|
+
from inspect import isclass
|
|
7
|
+
from typing import (
|
|
8
|
+
TYPE_CHECKING, Any, Callable, Concatenate, Generator, Generic, Iterable, Iterator, Mapping,
|
|
9
|
+
NoReturn, Protocol, Sequence, TypeVar, cast, overload
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
from .builtins import F0, F1, P0, P1, R0, R1, T0, T1, T2, KwargsT, P, R, T
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
'copy_signature',
|
|
16
|
+
|
|
17
|
+
'inject_self',
|
|
18
|
+
|
|
19
|
+
'inject_kwargs_params',
|
|
20
|
+
|
|
21
|
+
'complex_hash',
|
|
22
|
+
|
|
23
|
+
'get_subclasses',
|
|
24
|
+
|
|
25
|
+
'classproperty', 'cachedproperty',
|
|
26
|
+
|
|
27
|
+
'KwargsNotNone',
|
|
28
|
+
|
|
29
|
+
'Singleton', 'to_singleton',
|
|
30
|
+
|
|
31
|
+
'LinearRangeLut'
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class copy_signature(Generic[F0]):
|
|
36
|
+
"""
|
|
37
|
+
Type util to copy the signature of one function to another function.\n
|
|
38
|
+
Especially useful for passthrough functions.
|
|
39
|
+
|
|
40
|
+
.. code-block::
|
|
41
|
+
|
|
42
|
+
class SomeClass:
|
|
43
|
+
def __init__(
|
|
44
|
+
self, some: Any, complex: Any, /, *args: Any,
|
|
45
|
+
long: Any, signature: Any, **kwargs: Any
|
|
46
|
+
) -> None:
|
|
47
|
+
...
|
|
48
|
+
|
|
49
|
+
class SomeClassChild(SomeClass):
|
|
50
|
+
@copy_signature(SomeClass.__init__)
|
|
51
|
+
def __init__(*args: Any, **kwargs: Any) -> None:
|
|
52
|
+
super().__init__(*args, **kwargs)
|
|
53
|
+
# do some other thing
|
|
54
|
+
|
|
55
|
+
class Example(SomeClass):
|
|
56
|
+
@copy_signature(SomeClass.__init__)
|
|
57
|
+
def __init__(*args: Any, **kwargs: Any) -> None:
|
|
58
|
+
super().__init__(*args, **kwargs)
|
|
59
|
+
# another thing
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
def __init__(self, target: F0) -> None:
|
|
63
|
+
"""Copy the signature of ``target``."""
|
|
64
|
+
|
|
65
|
+
def __call__(self, wrapped: Callable[..., Any]) -> F0:
|
|
66
|
+
return cast(F0, wrapped)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class injected_self_func(Generic[T, P, R], Protocol): # type: ignore[misc]
|
|
70
|
+
@overload
|
|
71
|
+
@staticmethod
|
|
72
|
+
def __call__(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
73
|
+
...
|
|
74
|
+
|
|
75
|
+
@overload
|
|
76
|
+
@staticmethod
|
|
77
|
+
def __call__(self: T, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
78
|
+
...
|
|
79
|
+
|
|
80
|
+
@overload
|
|
81
|
+
@staticmethod
|
|
82
|
+
def __call__(self: T, _self: T, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
83
|
+
...
|
|
84
|
+
|
|
85
|
+
@overload
|
|
86
|
+
@staticmethod
|
|
87
|
+
def __call__(cls: type[T], *args: P.args, **kwargs: P.kwargs) -> R:
|
|
88
|
+
...
|
|
89
|
+
|
|
90
|
+
@overload
|
|
91
|
+
@staticmethod
|
|
92
|
+
def __call__(cls: type[T], _cls: type[T], *args: P.args, **kwargs: P.kwargs) -> R:
|
|
93
|
+
...
|
|
94
|
+
|
|
95
|
+
@staticmethod
|
|
96
|
+
def __call__(*args: Any, **kwds: Any) -> Any:
|
|
97
|
+
...
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
self_objects_cache = dict[type[T], T]() # type: ignore
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class inject_self_base(Generic[T, P, R]):
|
|
104
|
+
def __init__(self, function: Callable[Concatenate[T, P], R], /, *, cache: bool = False) -> None:
|
|
105
|
+
"""
|
|
106
|
+
Wrap ``function`` to always have a self provided to it.
|
|
107
|
+
|
|
108
|
+
:param function: Method to wrap.
|
|
109
|
+
:param cache: Whether to cache the self object.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
self.cache = self.init_kwargs = None
|
|
113
|
+
|
|
114
|
+
if isinstance(self, inject_self.cached):
|
|
115
|
+
self.cache = True
|
|
116
|
+
|
|
117
|
+
self.function = function
|
|
118
|
+
|
|
119
|
+
self.signature = self.first_key = self.init_kwargs = None
|
|
120
|
+
|
|
121
|
+
self.args = tuple[Any]()
|
|
122
|
+
self.kwargs = dict[str, Any]()
|
|
123
|
+
|
|
124
|
+
self.clean_kwargs = False
|
|
125
|
+
|
|
126
|
+
def __get__(
|
|
127
|
+
self, class_obj: type[T] | T | None, class_type: type[T] | type[type[T]] # type: ignore
|
|
128
|
+
) -> injected_self_func[T, P, R]:
|
|
129
|
+
if not self.signature or not self.first_key: # type: ignore
|
|
130
|
+
self.signature = Signature.from_callable(self.function, eval_str=True) # type: ignore
|
|
131
|
+
self.first_key = next(iter(list(self.signature.parameters.keys())), None) # type: ignore
|
|
132
|
+
|
|
133
|
+
if isinstance(self, inject_self.init_kwargs):
|
|
134
|
+
from ..exceptions import CustomValueError
|
|
135
|
+
|
|
136
|
+
if 4 not in {x.kind for x in self.signature.parameters.values()}: # type: ignore
|
|
137
|
+
raise CustomValueError(
|
|
138
|
+
'This function hasn\'t got any kwargs!', 'inject_self.init_kwargs', self.function
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
self.init_kwargs = list[str]( # type: ignore
|
|
142
|
+
k for k, x in self.signature.parameters.items() if x.kind != 4 # type: ignore
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
@wraps(self.function)
|
|
146
|
+
def _wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
147
|
+
first_arg = (args[0] if args else None) or (
|
|
148
|
+
kwargs.get(self.first_key, None) if self.first_key else None # type: ignore
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if (
|
|
152
|
+
first_arg and (
|
|
153
|
+
(is_obj := isinstance(first_arg, class_type))
|
|
154
|
+
or isinstance(first_arg, type(class_type)) # noqa
|
|
155
|
+
or first_arg is class_type # noqa
|
|
156
|
+
)
|
|
157
|
+
):
|
|
158
|
+
obj = first_arg if is_obj else first_arg()
|
|
159
|
+
if args:
|
|
160
|
+
args = args[1:]
|
|
161
|
+
elif kwargs and self.first_key:
|
|
162
|
+
kwargs.pop(self.first_key) # type: ignore
|
|
163
|
+
elif class_obj is None:
|
|
164
|
+
if self.cache:
|
|
165
|
+
if class_type not in self_objects_cache:
|
|
166
|
+
obj = self_objects_cache[class_type] = class_type(*self.args, **self.kwargs)
|
|
167
|
+
else:
|
|
168
|
+
obj = self_objects_cache[class_type]
|
|
169
|
+
elif self.init_kwargs:
|
|
170
|
+
obj = class_type( # type: ignore
|
|
171
|
+
*self.args, **(self.kwargs | {k: v for k, v in kwargs.items() if k not in self.init_kwargs})
|
|
172
|
+
)
|
|
173
|
+
if self.clean_kwargs:
|
|
174
|
+
kwargs = {k: v for k, v in kwargs.items() if k in self.init_kwargs}
|
|
175
|
+
else:
|
|
176
|
+
obj = class_type(*self.args, **self.kwargs)
|
|
177
|
+
else:
|
|
178
|
+
obj = class_obj
|
|
179
|
+
|
|
180
|
+
return self.function(obj, *args, **kwargs)
|
|
181
|
+
|
|
182
|
+
return _wrapper
|
|
183
|
+
|
|
184
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
185
|
+
return self.__get__(None, self)(*args, **kwargs) # type: ignore
|
|
186
|
+
|
|
187
|
+
@property
|
|
188
|
+
def __signature__(self) -> Signature:
|
|
189
|
+
return Signature.from_callable(self.function)
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
def with_args(
|
|
193
|
+
cls, *args: Any, **kwargs: Any
|
|
194
|
+
) -> Callable[[Callable[Concatenate[T0, P0], R0]], inject_self[T0, P0, R0]]:
|
|
195
|
+
"""Provide custom args to instantiate the ``self`` object with."""
|
|
196
|
+
|
|
197
|
+
def _wrapper(function: Callable[Concatenate[T0, P0], R0]) -> inject_self[T0, P0, R0]:
|
|
198
|
+
inj = cls(function) # type: ignore
|
|
199
|
+
inj.args = args
|
|
200
|
+
inj.kwargs = kwargs
|
|
201
|
+
return inj # type: ignore
|
|
202
|
+
return _wrapper
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
class inject_self(Generic[T, P, R], inject_self_base[T, P, R]):
|
|
206
|
+
"""Wrap a method so it always has a constructed ``self`` provided to it."""
|
|
207
|
+
|
|
208
|
+
class cached(Generic[T0, P0, R0], inject_self_base[T0, P0, R0]):
|
|
209
|
+
"""
|
|
210
|
+
Wrap a method so it always has a constructed ``self`` provided to it.
|
|
211
|
+
Once ``self`` is constructed, it will be reused.
|
|
212
|
+
"""
|
|
213
|
+
|
|
214
|
+
class property(Generic[T1, R1]):
|
|
215
|
+
def __init__(self, function: Callable[[T1], R1]) -> None:
|
|
216
|
+
self.function = inject_self(function)
|
|
217
|
+
|
|
218
|
+
def __get__(
|
|
219
|
+
self, class_obj: type[T1] | T1 | None, class_type: type[T1] | type[type[T1]] # type: ignore
|
|
220
|
+
) -> R1:
|
|
221
|
+
return self.function.__get__(class_obj, class_type)()
|
|
222
|
+
|
|
223
|
+
class init_kwargs(Generic[T0, P0, R0], inject_self_base[T0, P0, R0]):
|
|
224
|
+
"""
|
|
225
|
+
Wrap a method so it always has a constructed ``self`` provided to it.
|
|
226
|
+
When constructed, kwargs to the function will be passed to the constructor.
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
@classmethod
|
|
230
|
+
def clean(cls, function: Callable[Concatenate[T1, P1], R1]) -> inject_self[T1, P1, R1]:
|
|
231
|
+
"""Wrap a method, pass kwargs to the constructor and remove them from actual **kwargs."""
|
|
232
|
+
inj = cls(function) # type: ignore
|
|
233
|
+
inj.clean_kwargs = True
|
|
234
|
+
return inj # type: ignore
|
|
235
|
+
|
|
236
|
+
class property(Generic[T0, R0]):
|
|
237
|
+
def __init__(self, function: Callable[[T0], R0]) -> None:
|
|
238
|
+
self.function = inject_self(function)
|
|
239
|
+
|
|
240
|
+
def __get__(
|
|
241
|
+
self, class_obj: type[T0] | T0 | None, class_type: type[T0] | type[type[T0]] # type: ignore
|
|
242
|
+
) -> R0:
|
|
243
|
+
return self.function.__get__(class_obj, class_type)()
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
class inject_kwargs_params_base_func(Generic[T, P, R], Callable[Concatenate[T, P], R]): # type: ignore[misc]
|
|
247
|
+
def __call__(self: T, *args: P.args, **kwargs: P.kwargs) -> R: # type: ignore
|
|
248
|
+
...
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class inject_kwargs_params_base(Generic[T, P, R]):
|
|
252
|
+
_kwargs_name = 'kwargs'
|
|
253
|
+
|
|
254
|
+
def __init__(self, function: Callable[Concatenate[T, P], R]) -> None:
|
|
255
|
+
self.function = function
|
|
256
|
+
|
|
257
|
+
self.signature = None
|
|
258
|
+
|
|
259
|
+
def __get__(
|
|
260
|
+
self, class_obj: T, class_type: type[T]
|
|
261
|
+
) -> inject_kwargs_params_base_func[T, P, R]:
|
|
262
|
+
if not self.signature:
|
|
263
|
+
self.signature = Signature.from_callable(self.function, eval_str=True) # type: ignore
|
|
264
|
+
|
|
265
|
+
if (
|
|
266
|
+
isinstance(self, inject_kwargs_params.add_to_kwargs) # type: ignore
|
|
267
|
+
and (4 not in {x.kind for x in self.signature.parameters.values()}) # type: ignore
|
|
268
|
+
):
|
|
269
|
+
from ..exceptions import CustomValueError
|
|
270
|
+
|
|
271
|
+
raise CustomValueError(
|
|
272
|
+
'This function hasn\'t got any kwargs!', 'inject_kwargs_params.add_to_kwargs', self.function
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
this = self
|
|
276
|
+
|
|
277
|
+
@wraps(self.function)
|
|
278
|
+
def _wrapper(self: T, *_args: P.args, **kwargs: P.kwargs) -> R:
|
|
279
|
+
assert this.signature
|
|
280
|
+
|
|
281
|
+
if class_obj and not isinstance(self, class_type): # type: ignore
|
|
282
|
+
_args = (self, *_args)
|
|
283
|
+
self = class_obj
|
|
284
|
+
|
|
285
|
+
if not hasattr(self, this._kwargs_name):
|
|
286
|
+
from ..exceptions import CustomRuntimeError
|
|
287
|
+
|
|
288
|
+
raise CustomRuntimeError(
|
|
289
|
+
f'This class doesn\'t have any "{this._kwargs_name}" attribute!', reason=self.__class__
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
this_kwargs = self.kwargs.copy()
|
|
293
|
+
args, n_args = list(_args), len(_args)
|
|
294
|
+
|
|
295
|
+
for i, (key, value) in enumerate(this.signature.parameters.items()):
|
|
296
|
+
if key not in this_kwargs:
|
|
297
|
+
continue
|
|
298
|
+
|
|
299
|
+
kw_value = this_kwargs.pop(key)
|
|
300
|
+
|
|
301
|
+
if value.default is empty_param:
|
|
302
|
+
continue
|
|
303
|
+
|
|
304
|
+
if i < n_args:
|
|
305
|
+
if args[i] != value.default:
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
args[i] = kw_value
|
|
309
|
+
else:
|
|
310
|
+
if key in kwargs and kwargs[key] != value.default:
|
|
311
|
+
continue
|
|
312
|
+
|
|
313
|
+
kwargs[key] = kw_value
|
|
314
|
+
|
|
315
|
+
if isinstance(this, inject_kwargs_params.add_to_kwargs):
|
|
316
|
+
kwargs |= this_kwargs
|
|
317
|
+
|
|
318
|
+
return this.function(self, *args, **kwargs)
|
|
319
|
+
|
|
320
|
+
return _wrapper # type: ignore
|
|
321
|
+
|
|
322
|
+
def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
|
323
|
+
return self.__get__(None, self)(*args, **kwargs) # type: ignore
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def __signature__(self) -> Signature:
|
|
327
|
+
return Signature.from_callable(self.function)
|
|
328
|
+
|
|
329
|
+
@classmethod
|
|
330
|
+
def with_name(cls, kwargs_name: str) -> type[inject_kwargs_params]: # type: ignore
|
|
331
|
+
class _inner(inject_kwargs_params): # type: ignore
|
|
332
|
+
_kwargs_name = kwargs_name
|
|
333
|
+
|
|
334
|
+
return _inner
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
if TYPE_CHECKING: # love you mypy...
|
|
338
|
+
class _add_to_kwargs:
|
|
339
|
+
def __call__(self, func: F1) -> F1:
|
|
340
|
+
...
|
|
341
|
+
|
|
342
|
+
class _inject_kwargs_params:
|
|
343
|
+
def __call__(self, func: F0) -> F0:
|
|
344
|
+
...
|
|
345
|
+
|
|
346
|
+
add_to_kwargs = _add_to_kwargs()
|
|
347
|
+
|
|
348
|
+
inject_kwargs_params = _inject_kwargs_params()
|
|
349
|
+
else:
|
|
350
|
+
class inject_kwargs_params(Generic[T, P, R], inject_kwargs_params_base[T, P, R]):
|
|
351
|
+
class add_to_kwargs(Generic[T0, P0, R0], inject_kwargs_params_base[T0, P0, R0]):
|
|
352
|
+
...
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class complex_hash(Generic[T]):
|
|
356
|
+
"""
|
|
357
|
+
Decorator for classes to add a ``__hash__`` method to them.
|
|
358
|
+
|
|
359
|
+
Especially useful for NamedTuples.
|
|
360
|
+
"""
|
|
361
|
+
|
|
362
|
+
def __new__(cls, class_type: T) -> T: # type: ignore
|
|
363
|
+
class inner_class_type(class_type): # type: ignore
|
|
364
|
+
def __hash__(self) -> int:
|
|
365
|
+
return complex_hash.hash(
|
|
366
|
+
self.__class__.__name__, *(
|
|
367
|
+
getattr(self, key) for key in self.__annotations__.keys()
|
|
368
|
+
)
|
|
369
|
+
)
|
|
370
|
+
|
|
371
|
+
return inner_class_type # type: ignore
|
|
372
|
+
|
|
373
|
+
@staticmethod
|
|
374
|
+
def hash(*args: Any) -> int:
|
|
375
|
+
"""
|
|
376
|
+
Recursively hash every unhashable object in ``*args``.
|
|
377
|
+
|
|
378
|
+
:param *args: Objects to be hashed.
|
|
379
|
+
|
|
380
|
+
:return: Hash of all the combined objects' hashes.
|
|
381
|
+
"""
|
|
382
|
+
|
|
383
|
+
values = list[str]()
|
|
384
|
+
for value in args:
|
|
385
|
+
try:
|
|
386
|
+
new_hash = hash(value)
|
|
387
|
+
except TypeError:
|
|
388
|
+
if isinstance(value, Iterable):
|
|
389
|
+
new_hash = complex_hash.hash(*value)
|
|
390
|
+
else:
|
|
391
|
+
new_hash = hash(str(value))
|
|
392
|
+
|
|
393
|
+
values.append(str(new_hash))
|
|
394
|
+
|
|
395
|
+
return hash('_'.join(values))
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def get_subclasses(family: type[T], exclude: Sequence[type[T]] = []) -> list[type[T]]:
|
|
399
|
+
"""
|
|
400
|
+
Get all subclasses of a given type.
|
|
401
|
+
|
|
402
|
+
:param family: "Main" type all other classes inherit from.
|
|
403
|
+
:param exclude: Excluded types from the yield. Note that they won't be excluded from search.
|
|
404
|
+
For examples, subclasses of these excluded classes will be yield.
|
|
405
|
+
|
|
406
|
+
:return: List of all subclasses of "family".
|
|
407
|
+
"""
|
|
408
|
+
|
|
409
|
+
def _subclasses(cls: type[T]) -> Generator[type[T], None, None]:
|
|
410
|
+
for subclass in cls.__subclasses__():
|
|
411
|
+
yield from _subclasses(subclass)
|
|
412
|
+
if subclass in exclude:
|
|
413
|
+
continue
|
|
414
|
+
yield subclass
|
|
415
|
+
|
|
416
|
+
return list(set(_subclasses(family)))
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class classproperty(Generic[P, R, T, T0, P0]):
|
|
420
|
+
"""
|
|
421
|
+
Make a class property. A combination between classmethod and property.
|
|
422
|
+
"""
|
|
423
|
+
|
|
424
|
+
__isabstractmethod__: bool = False
|
|
425
|
+
|
|
426
|
+
class metaclass(type):
|
|
427
|
+
"""This must be set for the decorator to work."""
|
|
428
|
+
|
|
429
|
+
def __setattr__(self, key: str, value: Any) -> None:
|
|
430
|
+
if key in self.__dict__:
|
|
431
|
+
obj = self.__dict__.get(key)
|
|
432
|
+
|
|
433
|
+
if obj and isinstance(obj, classproperty):
|
|
434
|
+
obj.__set__(self, value)
|
|
435
|
+
return
|
|
436
|
+
|
|
437
|
+
super(classproperty.metaclass, self).__setattr__(key, value)
|
|
438
|
+
|
|
439
|
+
def __init__(
|
|
440
|
+
self,
|
|
441
|
+
fget: classmethod[T, P, R] | Callable[P, R],
|
|
442
|
+
fset: classmethod[T, P, None] | Callable[[T, T0], None] | None = None,
|
|
443
|
+
fdel: classmethod[T, P1, None] | Callable[P1, None] | None = None,
|
|
444
|
+
doc: str | None = None,
|
|
445
|
+
) -> None:
|
|
446
|
+
self.fget = self._wrap(fget)
|
|
447
|
+
self.fset = self._wrap(fset) if fset is not None else fset
|
|
448
|
+
self.fdel = self._wrap(fdel) if fdel is not None else fdel
|
|
449
|
+
|
|
450
|
+
self.doc = doc
|
|
451
|
+
|
|
452
|
+
def _wrap(self, func: classmethod[T1, P1, R1] | Callable[P1, R1]) -> classmethod[T1, P1, R1]:
|
|
453
|
+
if not isinstance(func, (classmethod, staticmethod)):
|
|
454
|
+
func = classmethod(func) # type: ignore
|
|
455
|
+
|
|
456
|
+
return func # type: ignore
|
|
457
|
+
|
|
458
|
+
def getter(self, __fget: classmethod[T, P, R] | Callable[P1, R1]) -> classproperty[P1, R1, T, T0, P0]:
|
|
459
|
+
self.fget = self._wrap(__fget) # type: ignore
|
|
460
|
+
return self # type: ignore
|
|
461
|
+
|
|
462
|
+
def setter(self, __fset: classmethod[T1, P, None] | Callable[[T1, T2], None]) -> classproperty[P, R, T1, T2, P0]:
|
|
463
|
+
self.fset = self._wrap(__fset) # type: ignore
|
|
464
|
+
return self # type: ignore
|
|
465
|
+
|
|
466
|
+
def deleter(self, __fdel: classmethod[T1, P1, None] | Callable[P1, None]) -> classproperty[P, R, T, T0, P1]:
|
|
467
|
+
self.fdel = self._wrap(__fdel) # type: ignore
|
|
468
|
+
return self # type: ignore
|
|
469
|
+
|
|
470
|
+
def __get__(self, __obj: Any, __type: type | None = None) -> R:
|
|
471
|
+
if __type is None:
|
|
472
|
+
__type = type(__obj)
|
|
473
|
+
|
|
474
|
+
return self.fget.__get__(__obj, __type)() # type: ignore
|
|
475
|
+
|
|
476
|
+
def __set__(self, __obj: Any, __value: T1) -> None:
|
|
477
|
+
from ..exceptions import CustomError
|
|
478
|
+
|
|
479
|
+
if not self.fset:
|
|
480
|
+
raise CustomError[AttributeError]("Can't set attribute")
|
|
481
|
+
|
|
482
|
+
if isclass(__obj):
|
|
483
|
+
type_, __obj = __obj, None
|
|
484
|
+
else:
|
|
485
|
+
type_ = type(__obj)
|
|
486
|
+
|
|
487
|
+
return self.fset.__get__(__obj, type_)(__value)
|
|
488
|
+
|
|
489
|
+
def __delete__(self, __obj: Any) -> None:
|
|
490
|
+
from ..exceptions import CustomError
|
|
491
|
+
|
|
492
|
+
if not self.fdel:
|
|
493
|
+
raise CustomError[AttributeError]("Can't delete attribute")
|
|
494
|
+
|
|
495
|
+
if isclass(__obj):
|
|
496
|
+
type_, __obj = __obj, None
|
|
497
|
+
else:
|
|
498
|
+
type_ = type(__obj)
|
|
499
|
+
|
|
500
|
+
return self.fdel.__delete__(__obj, type_)(__obj) # type: ignore
|
|
501
|
+
|
|
502
|
+
def __name__(self) -> str:
|
|
503
|
+
return self.fget.__name__
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
class cachedproperty(property, Generic[P, R, T, T0, P0]):
|
|
507
|
+
"""
|
|
508
|
+
Wrapper for a one-time get property, that will be cached.
|
|
509
|
+
|
|
510
|
+
Keep in mind two things:
|
|
511
|
+
|
|
512
|
+
* The cache is per-object. Don't hold a reference to itself or it will never get garbage collected.
|
|
513
|
+
* Your class has to either manually set __dict__[cachedproperty.cache_key]
|
|
514
|
+
or inherit from cachedproperty.baseclass.
|
|
515
|
+
"""
|
|
516
|
+
|
|
517
|
+
__isabstractmethod__: bool = False
|
|
518
|
+
|
|
519
|
+
cache_key = '_jetpt_cachedproperty_cache'
|
|
520
|
+
|
|
521
|
+
class baseclass:
|
|
522
|
+
"""Inherit from this class to automatically set the cache dict."""
|
|
523
|
+
|
|
524
|
+
if not TYPE_CHECKING:
|
|
525
|
+
def __new__(cls, *args: Any, **kwargs: Any) -> None:
|
|
526
|
+
try:
|
|
527
|
+
self = super().__new__(cls, *args, **kwargs)
|
|
528
|
+
except TypeError:
|
|
529
|
+
self = super().__new__(cls)
|
|
530
|
+
self.__dict__.__setitem__(cachedproperty.cache_key, dict[str, Any]())
|
|
531
|
+
return self
|
|
532
|
+
|
|
533
|
+
if TYPE_CHECKING:
|
|
534
|
+
def __init__(
|
|
535
|
+
self, fget: Callable[P, R], fset: Callable[[T, T0], None] | None = None,
|
|
536
|
+
fdel: Callable[P0, None] | None = None, doc: str | None = None,
|
|
537
|
+
) -> None:
|
|
538
|
+
...
|
|
539
|
+
|
|
540
|
+
def getter(self, __fget: Callable[P1, R1]) -> cachedproperty[P1, R1, T, T0, P0]:
|
|
541
|
+
...
|
|
542
|
+
|
|
543
|
+
def setter(self, __fset: Callable[[T1, T2], None]) -> cachedproperty[P, R, T1, T2, P0]:
|
|
544
|
+
...
|
|
545
|
+
|
|
546
|
+
def deleter(self, __fdel: Callable[P1, None]) -> cachedproperty[P, R, T, T0, P1]:
|
|
547
|
+
...
|
|
548
|
+
|
|
549
|
+
def __get__(self, __obj: Any, __type: type | None = None) -> R:
|
|
550
|
+
if isinstance(self.fget, classproperty):
|
|
551
|
+
function = partial(self.fget.__get__, __obj, __type) # type: ignore
|
|
552
|
+
__obj = __type
|
|
553
|
+
|
|
554
|
+
if not hasattr(__obj, cachedproperty.cache_key):
|
|
555
|
+
setattr(__obj, cachedproperty.cache_key, dict[str, Any]())
|
|
556
|
+
|
|
557
|
+
cache = getattr(__obj, cachedproperty.cache_key)
|
|
558
|
+
name = self.fget.__name__
|
|
559
|
+
else:
|
|
560
|
+
function = self.fget.__get__(__obj, __type) # type: ignore
|
|
561
|
+
cache = __obj.__dict__.get(cachedproperty.cache_key)
|
|
562
|
+
name = function.__name__
|
|
563
|
+
|
|
564
|
+
if name not in cache:
|
|
565
|
+
cache[name] = function()
|
|
566
|
+
|
|
567
|
+
return cache[name] # type: ignore
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
class KwargsNotNone(KwargsT):
|
|
571
|
+
"""Remove all None objects from this kwargs dict."""
|
|
572
|
+
|
|
573
|
+
if not TYPE_CHECKING:
|
|
574
|
+
def __new__(cls, *args: Any, **kwargs: Any) -> KwargsNotNone:
|
|
575
|
+
return KwargsT(**{
|
|
576
|
+
key: value for key, value in KwargsT(*args, **kwargs).items()
|
|
577
|
+
if value is not None
|
|
578
|
+
})
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
SingleMeta = TypeVar('SingleMeta', bound=type)
|
|
582
|
+
|
|
583
|
+
|
|
584
|
+
class SingletonMeta(type):
|
|
585
|
+
_instances = dict[type[SingleMeta], SingleMeta]() # type: ignore
|
|
586
|
+
_singleton_init: bool
|
|
587
|
+
|
|
588
|
+
def __new__(
|
|
589
|
+
cls: type[SingletonSelf], name: str, bases: tuple[type, ...], namespace: dict[str, Any], **kwargs: Any
|
|
590
|
+
) -> SingletonSelf:
|
|
591
|
+
return type.__new__(cls, name, bases, namespace | {'_singleton_init': kwargs.pop('init', False)})
|
|
592
|
+
|
|
593
|
+
def __call__(cls: type[SingletonSelf], *args: Any, **kwargs: Any) -> SingletonSelf: # type: ignore
|
|
594
|
+
if cls not in cls._instances:
|
|
595
|
+
cls._instances[cls] = super(SingletonMeta, cls).__call__(*args, **kwargs)
|
|
596
|
+
elif cls._singleton_init:
|
|
597
|
+
cls._instances[cls].__init__(*args, **kwargs) # type: ignore
|
|
598
|
+
|
|
599
|
+
return cls._instances[cls]
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
SingletonSelf = TypeVar('SingletonSelf', bound=SingletonMeta)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
class Singleton(metaclass=SingletonMeta):
|
|
606
|
+
"""Handy class to inherit to have the SingletonMeta metaclass."""
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
class to_singleton_impl:
|
|
610
|
+
_ts_args = tuple[str, ...]()
|
|
611
|
+
_ts_kwargs = dict[str, Any]()
|
|
612
|
+
_add_classes = tuple[type, ...]()
|
|
613
|
+
|
|
614
|
+
def __new__(_cls, cls: type[T]) -> T: # type: ignore
|
|
615
|
+
if _cls._add_classes:
|
|
616
|
+
class rcls(cls, *_cls._add_classes): # type: ignore
|
|
617
|
+
...
|
|
618
|
+
else:
|
|
619
|
+
rcls = cls # type: ignore
|
|
620
|
+
|
|
621
|
+
return rcls(*_cls._ts_args, **_cls._ts_kwargs)
|
|
622
|
+
|
|
623
|
+
@classmethod
|
|
624
|
+
def with_args(cls, *args: Any, **kwargs: Any) -> type[to_singleton]:
|
|
625
|
+
class _inner_singl(cls): # type: ignore
|
|
626
|
+
_ts_args = args
|
|
627
|
+
_ts_kwargs = kwargs
|
|
628
|
+
|
|
629
|
+
return _inner_singl
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
class to_singleton(to_singleton_impl):
|
|
633
|
+
class as_property(to_singleton_impl):
|
|
634
|
+
_add_classes = (property, )
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
class LinearRangeLut(Mapping[int, int]):
|
|
638
|
+
__slots__ = ('ranges', '_ranges_idx_lut', '_misses_n')
|
|
639
|
+
|
|
640
|
+
def __init__(self, ranges: Mapping[int, range]) -> None:
|
|
641
|
+
self.ranges = ranges
|
|
642
|
+
|
|
643
|
+
self._ranges_idx_lut = list(self.ranges.items())
|
|
644
|
+
self._misses_n = 0
|
|
645
|
+
|
|
646
|
+
def __getitem__(self, n: int) -> int:
|
|
647
|
+
for missed_hit, (idx, k) in enumerate(self._ranges_idx_lut):
|
|
648
|
+
if n in k:
|
|
649
|
+
break
|
|
650
|
+
|
|
651
|
+
if missed_hit:
|
|
652
|
+
self._misses_n += 1
|
|
653
|
+
|
|
654
|
+
if self._misses_n > 2:
|
|
655
|
+
self._ranges_idx_lut = self._ranges_idx_lut[missed_hit:] + self._ranges_idx_lut[:missed_hit]
|
|
656
|
+
|
|
657
|
+
return idx
|
|
658
|
+
|
|
659
|
+
def __len__(self) -> int:
|
|
660
|
+
return len(self.ranges)
|
|
661
|
+
|
|
662
|
+
def __iter__(self) -> Iterator[int]:
|
|
663
|
+
return iter(range(len(self)))
|
|
664
|
+
|
|
665
|
+
def __setitem__(self, n: int, _range: range) -> NoReturn:
|
|
666
|
+
raise NotImplementedError
|
|
667
|
+
|
|
668
|
+
def __delitem__(self, n: int) -> NoReturn:
|
|
669
|
+
raise NotImplementedError
|