absfuyu 4.1.1__py3-none-any.whl → 5.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 absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +4 -4
- absfuyu/__main__.py +13 -1
- absfuyu/cli/__init__.py +4 -2
- absfuyu/cli/color.py +7 -0
- absfuyu/cli/do_group.py +9 -91
- absfuyu/cli/tool_group.py +136 -0
- absfuyu/config/__init__.py +17 -34
- absfuyu/core/__init__.py +49 -0
- absfuyu/core/baseclass.py +299 -0
- absfuyu/core/baseclass2.py +165 -0
- absfuyu/core/decorator.py +67 -0
- absfuyu/core/docstring.py +163 -0
- absfuyu/core/dummy_cli.py +67 -0
- absfuyu/core/dummy_func.py +47 -0
- absfuyu/dxt/__init__.py +42 -0
- absfuyu/dxt/dictext.py +201 -0
- absfuyu/dxt/dxt_support.py +79 -0
- absfuyu/dxt/intext.py +586 -0
- absfuyu/dxt/listext.py +508 -0
- absfuyu/dxt/strext.py +530 -0
- absfuyu/{extensions → extra}/__init__.py +3 -2
- absfuyu/extra/beautiful.py +251 -0
- absfuyu/{extensions/extra → extra}/data_analysis.py +51 -82
- absfuyu/fun/__init__.py +110 -135
- absfuyu/fun/tarot.py +9 -17
- absfuyu/game/__init__.py +6 -0
- absfuyu/game/game_stat.py +6 -0
- absfuyu/game/sudoku.py +7 -1
- absfuyu/game/tictactoe.py +12 -5
- absfuyu/game/wordle.py +14 -8
- absfuyu/general/__init__.py +6 -79
- absfuyu/general/content.py +22 -36
- absfuyu/general/generator.py +17 -42
- absfuyu/general/human.py +108 -228
- absfuyu/general/shape.py +1334 -0
- absfuyu/logger.py +8 -13
- absfuyu/pkg_data/__init__.py +137 -99
- absfuyu/pkg_data/deprecated.py +133 -0
- absfuyu/pkg_data/passwordlib_lzma.pkl +0 -0
- absfuyu/sort.py +6 -130
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +44 -22
- absfuyu/tools/converter.py +82 -50
- absfuyu/tools/keygen.py +25 -30
- absfuyu/tools/obfuscator.py +246 -112
- absfuyu/tools/passwordlib.py +330 -0
- absfuyu/tools/shutdownizer.py +287 -0
- absfuyu/tools/web.py +2 -9
- absfuyu/util/__init__.py +15 -15
- absfuyu/util/api.py +10 -15
- absfuyu/util/json_method.py +7 -24
- absfuyu/util/lunar.py +3 -9
- absfuyu/util/path.py +22 -27
- absfuyu/util/performance.py +43 -67
- absfuyu/util/shorten_number.py +65 -14
- absfuyu/util/zipped.py +9 -15
- absfuyu-5.0.0.dist-info/METADATA +143 -0
- absfuyu-5.0.0.dist-info/RECORD +68 -0
- absfuyu/core.py +0 -57
- absfuyu/everything.py +0 -32
- absfuyu/extensions/beautiful.py +0 -188
- absfuyu/extensions/dev/__init__.py +0 -244
- absfuyu/extensions/dev/password_hash.py +0 -80
- absfuyu/extensions/dev/passwordlib.py +0 -258
- absfuyu/extensions/dev/project_starter.py +0 -60
- absfuyu/extensions/dev/shutdownizer.py +0 -156
- absfuyu/extensions/extra/__init__.py +0 -24
- absfuyu/fun/WGS.py +0 -134
- absfuyu/general/data_extension.py +0 -1796
- absfuyu/tools/stats.py +0 -226
- absfuyu/util/pkl.py +0 -67
- absfuyu-4.1.1.dist-info/METADATA +0 -121
- absfuyu-4.1.1.dist-info/RECORD +0 -61
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-4.1.1.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Core
|
|
3
|
+
-------------
|
|
4
|
+
Bases for other features
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 12/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Color
|
|
14
|
+
"CLITextColor",
|
|
15
|
+
# Mixins
|
|
16
|
+
"ShowAllMethodsMixin",
|
|
17
|
+
"AutoREPRMixin",
|
|
18
|
+
# Class
|
|
19
|
+
"BaseClass",
|
|
20
|
+
# Metaclass
|
|
21
|
+
"PositiveInitArgsMeta",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Color
|
|
26
|
+
# ---------------------------------------------------------------------------
|
|
27
|
+
class CLITextColor:
|
|
28
|
+
"""Color code for text in terminal"""
|
|
29
|
+
|
|
30
|
+
WHITE = "\x1b[37m"
|
|
31
|
+
BLACK = "\x1b[30m"
|
|
32
|
+
BLUE = "\x1b[34m"
|
|
33
|
+
GRAY = "\x1b[90m"
|
|
34
|
+
GREEN = "\x1b[32m"
|
|
35
|
+
RED = "\x1b[91m"
|
|
36
|
+
DARK_RED = "\x1b[31m"
|
|
37
|
+
MAGENTA = "\x1b[35m"
|
|
38
|
+
YELLOW = "\x1b[33m"
|
|
39
|
+
RESET = "\x1b[39m"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Mixins
|
|
43
|
+
# ---------------------------------------------------------------------------
|
|
44
|
+
class ShowAllMethodsMixin:
|
|
45
|
+
"""
|
|
46
|
+
Show all methods of the class and its parent class minus ``object`` class
|
|
47
|
+
|
|
48
|
+
*This class is meant to be used with other class*
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def show_all_methods(
|
|
53
|
+
cls,
|
|
54
|
+
include_classmethod: bool = True,
|
|
55
|
+
classmethod_indicator: str = "<classmethod>",
|
|
56
|
+
include_staticmethod: bool = True,
|
|
57
|
+
staticmethod_indicator: str = "<staticmethod>",
|
|
58
|
+
include_private_method: bool = False,
|
|
59
|
+
print_result: bool = False,
|
|
60
|
+
) -> dict[str, list[str]]:
|
|
61
|
+
"""
|
|
62
|
+
Class method to display all methods of the class and its parent classes,
|
|
63
|
+
including the class in which they are defined in alphabetical order.
|
|
64
|
+
|
|
65
|
+
Parameters
|
|
66
|
+
----------
|
|
67
|
+
include_classmethod : bool, optional
|
|
68
|
+
Whether to include classmethod in the output, by default ``True``
|
|
69
|
+
|
|
70
|
+
classmethod_indicator : str, optional
|
|
71
|
+
A string used to mark classmethod in the output. This string is appended
|
|
72
|
+
to the name of each classmethod to visually differentiate it from regular
|
|
73
|
+
instance methods, by default ``"<classmethod>"``
|
|
74
|
+
|
|
75
|
+
include_staticmethod : bool, optional
|
|
76
|
+
Whether to include staticmethod in the output, by default ``True``
|
|
77
|
+
|
|
78
|
+
staticmethod_indicator : str, optional
|
|
79
|
+
A string used to mark staticmethod in the output. This string is appended
|
|
80
|
+
to the name of each staticmethod to visually differentiate it from regular
|
|
81
|
+
instance methods, by default ``"<staticmethod>"``
|
|
82
|
+
|
|
83
|
+
include_private_method : bool, optional
|
|
84
|
+
Whether to include private method in the output, by default ``False``
|
|
85
|
+
|
|
86
|
+
print_result : bool, optional
|
|
87
|
+
Beautifully print the output, by default ``False``
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
dict[str, list[str]]
|
|
92
|
+
A dictionary where keys are class names and values are lists of method names.
|
|
93
|
+
"""
|
|
94
|
+
classes = cls.__mro__[::-1][1:] # MRO in reverse order
|
|
95
|
+
result = {}
|
|
96
|
+
for base in classes:
|
|
97
|
+
methods = []
|
|
98
|
+
for name, attr in base.__dict__.items():
|
|
99
|
+
# Skip private attribute
|
|
100
|
+
if name.startswith("__"):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# Skip private Callable
|
|
104
|
+
if base.__name__ in name and not include_private_method:
|
|
105
|
+
continue
|
|
106
|
+
|
|
107
|
+
# Function
|
|
108
|
+
if callable(attr):
|
|
109
|
+
if isinstance(attr, staticmethod):
|
|
110
|
+
if include_staticmethod:
|
|
111
|
+
methods.append(f"{name} {staticmethod_indicator}")
|
|
112
|
+
else:
|
|
113
|
+
methods.append(name)
|
|
114
|
+
if isinstance(attr, classmethod) and include_classmethod:
|
|
115
|
+
methods.append(f"{name} {classmethod_indicator}")
|
|
116
|
+
|
|
117
|
+
if methods:
|
|
118
|
+
result[base.__name__] = sorted(methods)
|
|
119
|
+
|
|
120
|
+
if print_result:
|
|
121
|
+
cls.__print_show_all_result(result)
|
|
122
|
+
|
|
123
|
+
return result
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def show_all_properties(cls, print_result: bool = False) -> dict[str, list[str]]:
|
|
127
|
+
"""
|
|
128
|
+
Class method to display all properties of the class and its parent classes,
|
|
129
|
+
including the class in which they are defined in alphabetical order.
|
|
130
|
+
|
|
131
|
+
Parameters
|
|
132
|
+
----------
|
|
133
|
+
print_result : bool, optional
|
|
134
|
+
Beautifully print the output, by default ``False``
|
|
135
|
+
|
|
136
|
+
Returns
|
|
137
|
+
-------
|
|
138
|
+
dict[str, list[str]]
|
|
139
|
+
A dictionary where keys are class names and values are lists of property names.
|
|
140
|
+
"""
|
|
141
|
+
classes = cls.__mro__[::-1][1:] # MRO in reverse order
|
|
142
|
+
result = {}
|
|
143
|
+
for base in classes:
|
|
144
|
+
properties = []
|
|
145
|
+
for name, attr in base.__dict__.items():
|
|
146
|
+
# Skip private attribute
|
|
147
|
+
if name.startswith("__"):
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
if isinstance(attr, property):
|
|
151
|
+
properties.append(name)
|
|
152
|
+
|
|
153
|
+
if properties:
|
|
154
|
+
result[base.__name__] = sorted(properties)
|
|
155
|
+
|
|
156
|
+
if print_result:
|
|
157
|
+
cls.__print_show_all_result(result)
|
|
158
|
+
|
|
159
|
+
return result
|
|
160
|
+
|
|
161
|
+
@staticmethod
|
|
162
|
+
def __print_show_all_result(result: dict[str, list[str]]) -> None:
|
|
163
|
+
"""
|
|
164
|
+
Pretty print the result of ``ShowAllMethodsMixin.show_all_methods()``
|
|
165
|
+
|
|
166
|
+
Parameters
|
|
167
|
+
----------
|
|
168
|
+
result : dict[str, list[str]]
|
|
169
|
+
Result of ``ShowAllMethodsMixin.show_all_methods()``
|
|
170
|
+
"""
|
|
171
|
+
print_func = print # Can be extended with function parameter
|
|
172
|
+
|
|
173
|
+
# Loop through each class base
|
|
174
|
+
for order, (class_base, methods) in enumerate(result.items(), start=1):
|
|
175
|
+
mlen = len(methods) # How many methods in that class
|
|
176
|
+
print_func(f"{order:02}. <{class_base}> | len: {mlen:02}")
|
|
177
|
+
|
|
178
|
+
# Modify methods list
|
|
179
|
+
max_method_name_len = max([len(x) for x in methods])
|
|
180
|
+
if mlen % 2 == 0:
|
|
181
|
+
p1, p2 = methods[: int(mlen / 2)], methods[int(mlen / 2) :]
|
|
182
|
+
else:
|
|
183
|
+
p1, p2 = methods[: int(mlen / 2) + 1], methods[int(mlen / 2) + 1 :]
|
|
184
|
+
p2.append("")
|
|
185
|
+
new_methods = list(zip(p1, p2))
|
|
186
|
+
|
|
187
|
+
# This print 2 methods in 1 line
|
|
188
|
+
for x1, x2 in new_methods:
|
|
189
|
+
if x2 == "":
|
|
190
|
+
print_func(f" - {x1.ljust(max_method_name_len)}")
|
|
191
|
+
else:
|
|
192
|
+
print_func(
|
|
193
|
+
f" - {x1.ljust(max_method_name_len)} - {x2.ljust(max_method_name_len)}"
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
# This print 1 method in one line
|
|
197
|
+
# for name in methods:
|
|
198
|
+
# print(f" - {name.ljust(max_method_name_len)}")
|
|
199
|
+
|
|
200
|
+
print_func("".ljust(88, "-"))
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class AutoREPRMixin:
|
|
204
|
+
"""
|
|
205
|
+
Generate ``repr()`` output as ``<class(param1=any, param2=any, ...)>``
|
|
206
|
+
|
|
207
|
+
*This class is meant to be used with other class*
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
--------
|
|
212
|
+
>>> class Test(AutoREPRMixin):
|
|
213
|
+
... def __init__(self, param):
|
|
214
|
+
... self.param = param
|
|
215
|
+
>>> print(repr(Test(1)))
|
|
216
|
+
Test(param=1)
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
def __repr__(self) -> str:
|
|
220
|
+
"""
|
|
221
|
+
Generate a string representation of the instance's attributes.
|
|
222
|
+
|
|
223
|
+
This function retrieves attributes from either the ``__dict__`` or
|
|
224
|
+
``__slots__`` of the instance, excluding private attributes (those
|
|
225
|
+
starting with an underscore). The attributes are returned as a
|
|
226
|
+
formatted string, with each attribute represented as ``"key=value"``.
|
|
227
|
+
|
|
228
|
+
Convert ``self.__dict__`` from ``{"a": "b"}`` to ``a=repr(b)``
|
|
229
|
+
or ``self.__slots__`` from ``("a",)`` to ``a=repr(self.a)``
|
|
230
|
+
(excluding private attributes)
|
|
231
|
+
"""
|
|
232
|
+
# Default output
|
|
233
|
+
out = []
|
|
234
|
+
sep = ", " # Separator
|
|
235
|
+
|
|
236
|
+
# Get attributes
|
|
237
|
+
cls_dict = getattr(self, "__dict__", None)
|
|
238
|
+
cls_slots = getattr(self, "__slots__", None)
|
|
239
|
+
|
|
240
|
+
# Check if __dict__ exist and len(__dict__) > 0
|
|
241
|
+
if cls_dict is not None and len(cls_dict) > 0:
|
|
242
|
+
out = [
|
|
243
|
+
f"{k}={repr(v)}"
|
|
244
|
+
for k, v in self.__dict__.items()
|
|
245
|
+
if not k.startswith("_")
|
|
246
|
+
]
|
|
247
|
+
|
|
248
|
+
# Check if __slots__ exist and len(__slots__) > 0
|
|
249
|
+
elif cls_slots is not None and len(cls_slots) > 0:
|
|
250
|
+
out = [
|
|
251
|
+
f"{x}={repr(getattr(self, x))}"
|
|
252
|
+
for x in self.__slots__ # type: ignore
|
|
253
|
+
if not x.startswith("_")
|
|
254
|
+
]
|
|
255
|
+
|
|
256
|
+
# Return out
|
|
257
|
+
return f"{self.__class__.__name__}({sep.join(out)})"
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
# Class
|
|
261
|
+
# ---------------------------------------------------------------------------
|
|
262
|
+
class BaseClass(ShowAllMethodsMixin, AutoREPRMixin):
|
|
263
|
+
"""Base class"""
|
|
264
|
+
|
|
265
|
+
def __str__(self) -> str:
|
|
266
|
+
return repr(self)
|
|
267
|
+
|
|
268
|
+
def __format__(self, format_spec: str) -> str:
|
|
269
|
+
"""
|
|
270
|
+
Formats the object according to the specified format.
|
|
271
|
+
If no format_spec is provided, returns the object's string representation.
|
|
272
|
+
(Currently a dummy function)
|
|
273
|
+
|
|
274
|
+
Usage
|
|
275
|
+
-----
|
|
276
|
+
>>> print(f"{<object>:<format_spec>}")
|
|
277
|
+
>>> print(<object>.__format__(<format_spec>))
|
|
278
|
+
>>> print(format(<object>, <format_spec>))
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
return self.__str__()
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
# Metaclass
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
class PositiveInitArgsMeta(type):
|
|
287
|
+
"""Make sure that every args in a class __init__ is positive"""
|
|
288
|
+
|
|
289
|
+
def __call__(cls, *args, **kwargs):
|
|
290
|
+
# Check if all positional and keyword arguments are positive
|
|
291
|
+
for arg in args:
|
|
292
|
+
if isinstance(arg, (int, float)) and arg < 0:
|
|
293
|
+
raise ValueError(f"Argument {arg} must be positive")
|
|
294
|
+
for key, value in kwargs.items():
|
|
295
|
+
if isinstance(value, (int, float)) and value < 0:
|
|
296
|
+
raise ValueError(f"Argument {key}={value} must be positive")
|
|
297
|
+
|
|
298
|
+
# Call the original __init__ method
|
|
299
|
+
return super().__call__(*args, **kwargs)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Core
|
|
3
|
+
-------------
|
|
4
|
+
Bases for other features (with library)
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 12/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
# Class
|
|
14
|
+
"ShowAllMethodsMixinInspectVer",
|
|
15
|
+
# Metaclass
|
|
16
|
+
"PerformanceTrackingMeta",
|
|
17
|
+
# Class decorator
|
|
18
|
+
"positive_class_init_args",
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# Library
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
import time
|
|
25
|
+
import tracemalloc
|
|
26
|
+
from collections.abc import Callable
|
|
27
|
+
from functools import wraps
|
|
28
|
+
from inspect import getmro, isfunction
|
|
29
|
+
from types import MethodType
|
|
30
|
+
from typing import Any, ParamSpec, TypeVar
|
|
31
|
+
|
|
32
|
+
# Type
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
P = ParamSpec("P") # Parameter type
|
|
35
|
+
T = TypeVar("T", bound=type) # Type type - Can be any subtype of `type`
|
|
36
|
+
# R = TypeVar("R") # Return type
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# Class
|
|
40
|
+
# ---------------------------------------------------------------------------
|
|
41
|
+
class ShowAllMethodsMixinInspectVer:
|
|
42
|
+
@classmethod
|
|
43
|
+
def show_all_methods(
|
|
44
|
+
cls,
|
|
45
|
+
include_classmethod: bool = True,
|
|
46
|
+
classmethod_indicator: str = "<classmethod>",
|
|
47
|
+
) -> dict[str, list[str]]:
|
|
48
|
+
result = {}
|
|
49
|
+
for base in getmro(cls)[::-1]:
|
|
50
|
+
methods = []
|
|
51
|
+
# for name, attr in inspect.getmembers(base, predicate=callable):
|
|
52
|
+
for name, attr in base.__dict__.items():
|
|
53
|
+
if name.startswith("__"):
|
|
54
|
+
continue
|
|
55
|
+
if isfunction(attr):
|
|
56
|
+
methods.append(name)
|
|
57
|
+
# if inspect.ismethod(attr):
|
|
58
|
+
if isinstance(attr, (classmethod, MethodType)) and include_classmethod:
|
|
59
|
+
methods.append(f"{name} {classmethod_indicator}")
|
|
60
|
+
if methods:
|
|
61
|
+
result[base.__name__] = sorted(methods)
|
|
62
|
+
return result
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Metaclass
|
|
66
|
+
# ---------------------------------------------------------------------------
|
|
67
|
+
class PerformanceTrackingMeta(type):
|
|
68
|
+
"""
|
|
69
|
+
A metaclass that tracks the instantiation time of classes.
|
|
70
|
+
|
|
71
|
+
Usage:
|
|
72
|
+
------
|
|
73
|
+
>>> class Demo(metaclass=PerformanceTrackingMeta):
|
|
74
|
+
... def __init__(self):...
|
|
75
|
+
>>> Demo()
|
|
76
|
+
--------------------------------------
|
|
77
|
+
Class: Demo
|
|
78
|
+
Memory usage: 0.000000 MB
|
|
79
|
+
Peak memory usage: 0.000008 MB
|
|
80
|
+
Time elapsed: 0.000001 s
|
|
81
|
+
--------------------------------------
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __new__(mcs, name: str, bases: tuple, attrs: dict[str, Any]):
|
|
85
|
+
"""
|
|
86
|
+
Intercepts class creation to wrap the ``__init__`` method.
|
|
87
|
+
|
|
88
|
+
Parameters
|
|
89
|
+
----------
|
|
90
|
+
mcs
|
|
91
|
+
The metaclass
|
|
92
|
+
|
|
93
|
+
name : str
|
|
94
|
+
The class name
|
|
95
|
+
|
|
96
|
+
bases : tuple
|
|
97
|
+
Tuple of base classes
|
|
98
|
+
|
|
99
|
+
attrs : dict[str, Any]
|
|
100
|
+
Dictionary of attributes
|
|
101
|
+
"""
|
|
102
|
+
if "__init__" in attrs:
|
|
103
|
+
original_init = attrs["__init__"]
|
|
104
|
+
|
|
105
|
+
@wraps(original_init)
|
|
106
|
+
def wrapped_init(self, *args, **kwargs):
|
|
107
|
+
# Performance check
|
|
108
|
+
tracemalloc.start()
|
|
109
|
+
start_time = time.perf_counter()
|
|
110
|
+
|
|
111
|
+
# Run __init__()
|
|
112
|
+
original_init(self, *args, **kwargs)
|
|
113
|
+
|
|
114
|
+
# Performance stop
|
|
115
|
+
current, peak = tracemalloc.get_traced_memory()
|
|
116
|
+
end_time = time.perf_counter()
|
|
117
|
+
tracemalloc.stop()
|
|
118
|
+
creation_time = end_time - start_time
|
|
119
|
+
|
|
120
|
+
# Print output
|
|
121
|
+
print(
|
|
122
|
+
f"{''.ljust(38, '-')}\n"
|
|
123
|
+
f"Class: {name}\n"
|
|
124
|
+
f"Memory usage:\t\t {current / 10**6:,.6f} MB\n"
|
|
125
|
+
f"Peak memory usage:\t {peak / 10**6:,.6f} MB\n"
|
|
126
|
+
f"Time elapsed:\t\t {creation_time:,.6f} s\n"
|
|
127
|
+
f"{''.ljust(38, '-')}"
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
attrs["__init__"] = wrapped_init
|
|
131
|
+
return super().__new__(mcs, name, bases, attrs)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Decorator
|
|
135
|
+
# ---------------------------------------------------------------------------
|
|
136
|
+
def positive_class_init_args(cls: T):
|
|
137
|
+
"""
|
|
138
|
+
A class decorator that ensures all arguments in the ``__init__()`` method are positive.
|
|
139
|
+
"""
|
|
140
|
+
# original_init: Callable[P, None] | None = getattr(cls, "__init__", None)
|
|
141
|
+
# if original_init is None:
|
|
142
|
+
# return cls
|
|
143
|
+
try:
|
|
144
|
+
original_init: Callable[P, None] = cls.__init__ # type: ignore
|
|
145
|
+
except AttributeError:
|
|
146
|
+
return cls
|
|
147
|
+
|
|
148
|
+
@wraps(original_init)
|
|
149
|
+
def new_init(self, *args: P.args, **kwargs: P.kwargs):
|
|
150
|
+
# Check if all positional arguments are positive
|
|
151
|
+
for arg in args:
|
|
152
|
+
if isinstance(arg, (int, float)) and arg < 0:
|
|
153
|
+
raise ValueError(f"Argument {arg} must be positive")
|
|
154
|
+
|
|
155
|
+
# Check if all keyword arguments are positive
|
|
156
|
+
for key, value in kwargs.items():
|
|
157
|
+
if isinstance(value, (int, float)) and value < 0:
|
|
158
|
+
raise ValueError(f"Argument {key}={value} must be positive")
|
|
159
|
+
|
|
160
|
+
# Call the original __init__ method
|
|
161
|
+
original_init(self, *args, **kwargs)
|
|
162
|
+
|
|
163
|
+
# setattr(cls, "__init__", new_init)
|
|
164
|
+
cls.__init__ = new_init # type: ignore
|
|
165
|
+
return cls
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Core
|
|
3
|
+
-------------
|
|
4
|
+
Decorator
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 22/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["dummy_decorator", "dummy_decorator_with_args"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
from collections.abc import Callable
|
|
18
|
+
from functools import wraps
|
|
19
|
+
from typing import ParamSpec, TypeVar, overload
|
|
20
|
+
|
|
21
|
+
# Type
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
P = ParamSpec("P") # Parameter type
|
|
24
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
25
|
+
T = TypeVar("T", bound=type) # Type type - Can be any subtype of `type`
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# Decorator
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
@overload
|
|
31
|
+
def dummy_decorator(obj: T) -> T: ...
|
|
32
|
+
@overload
|
|
33
|
+
def dummy_decorator(obj: Callable[P, R]) -> Callable[P, R]: ...
|
|
34
|
+
def dummy_decorator(obj: Callable[P, R] | T) -> Callable[P, R] | T:
|
|
35
|
+
"""
|
|
36
|
+
This is a decorator that does nothing. Normally used as a placeholder
|
|
37
|
+
"""
|
|
38
|
+
if isinstance(obj, type):
|
|
39
|
+
return obj
|
|
40
|
+
|
|
41
|
+
@wraps(obj)
|
|
42
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
43
|
+
return obj(*args, **kwargs)
|
|
44
|
+
|
|
45
|
+
return wrapper
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def dummy_decorator_with_args(*args, **kwargs):
|
|
49
|
+
"""
|
|
50
|
+
This is a decorator with args and kwargs that does nothing. Normally used as a placeholder
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
@overload
|
|
54
|
+
def decorator(obj: T) -> T: ...
|
|
55
|
+
@overload
|
|
56
|
+
def decorator(obj: Callable[P, R]) -> Callable[P, R]: ...
|
|
57
|
+
def decorator(obj: Callable[P, R] | T) -> Callable[P, R] | T:
|
|
58
|
+
if isinstance(obj, type):
|
|
59
|
+
return obj
|
|
60
|
+
|
|
61
|
+
@wraps(obj)
|
|
62
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
63
|
+
return obj(*args, **kwargs)
|
|
64
|
+
|
|
65
|
+
return wrapper
|
|
66
|
+
|
|
67
|
+
return decorator
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Core
|
|
3
|
+
-------------
|
|
4
|
+
Sphinx docstring decorator
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 13/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
"SphinxDocstring",
|
|
14
|
+
"SphinxDocstringMode",
|
|
15
|
+
"versionadded",
|
|
16
|
+
"versionchanged",
|
|
17
|
+
"deprecated",
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Library
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
from collections.abc import Callable
|
|
24
|
+
from enum import Enum
|
|
25
|
+
from functools import partial, wraps
|
|
26
|
+
from string import Template
|
|
27
|
+
from typing import ClassVar, ParamSpec, TypeVar, overload
|
|
28
|
+
|
|
29
|
+
# Type
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
P = ParamSpec("P") # Parameter type
|
|
32
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
33
|
+
T = TypeVar("T", bound=type) # Type type - Can be any subtype of `type`
|
|
34
|
+
|
|
35
|
+
_SPHINX_DOCS_TEMPLATE = Template("$line_break*$mode in version $version$reason*")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Class
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
class SphinxDocstringMode(Enum):
|
|
41
|
+
"""
|
|
42
|
+
Enum representing the mode of the version change
|
|
43
|
+
(added, changed, or deprecated)
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
ADDED = "Added"
|
|
47
|
+
CHANGED = "Changed"
|
|
48
|
+
DEPRECATED = "Deprecated"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class SphinxDocstring:
|
|
52
|
+
"""
|
|
53
|
+
A class-based decorator to add a 'Version added',
|
|
54
|
+
'Version changed', or 'Deprecated' note to a function's docstring,
|
|
55
|
+
formatted for Sphinx documentation.
|
|
56
|
+
"""
|
|
57
|
+
|
|
58
|
+
_LINEBREAK: ClassVar[str] = "\n\n" # Use ClassVar for constant
|
|
59
|
+
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
version: str,
|
|
63
|
+
reason: str | None = None,
|
|
64
|
+
mode: SphinxDocstringMode = SphinxDocstringMode.ADDED,
|
|
65
|
+
) -> None:
|
|
66
|
+
"""
|
|
67
|
+
Initializes the SphinxDocstring decorator.
|
|
68
|
+
|
|
69
|
+
Parameters
|
|
70
|
+
----------
|
|
71
|
+
version : str
|
|
72
|
+
The version in which the function was added, changed, or deprecated.
|
|
73
|
+
|
|
74
|
+
reason : str | None, optional
|
|
75
|
+
An optional reason or description for the change
|
|
76
|
+
or deprecation, by default ``None``
|
|
77
|
+
|
|
78
|
+
mode : SphinxDocstringMode | int, optional
|
|
79
|
+
Specifies whether the function was 'added', 'changed', or 'deprecated',
|
|
80
|
+
by default SphinxDocstringMode.ADDED
|
|
81
|
+
|
|
82
|
+
Usage
|
|
83
|
+
-----
|
|
84
|
+
Use this as a decorator (``@SphinxDocstring(<parameters>)``)
|
|
85
|
+
"""
|
|
86
|
+
self.version = version
|
|
87
|
+
self.reason = reason
|
|
88
|
+
self.mode = mode
|
|
89
|
+
|
|
90
|
+
@overload
|
|
91
|
+
def __call__(self, obj: T) -> T: ... # Class overload
|
|
92
|
+
@overload
|
|
93
|
+
def __call__(self, obj: Callable[P, R]) -> Callable[P, R]: ... # Function overload
|
|
94
|
+
def __call__(self, obj: T | Callable[P, R]) -> T | Callable[P, R]:
|
|
95
|
+
# Class wrapper
|
|
96
|
+
if isinstance(obj, type): # if inspect.isclass(obj):
|
|
97
|
+
obj.__doc__ = (obj.__doc__ or "") + self._generate_version_note(
|
|
98
|
+
num_of_white_spaces=self._calculate_white_space(obj.__doc__)
|
|
99
|
+
)
|
|
100
|
+
return obj
|
|
101
|
+
|
|
102
|
+
# Function wrapper
|
|
103
|
+
@wraps(obj)
|
|
104
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
|
|
105
|
+
return obj(*args, **kwargs)
|
|
106
|
+
|
|
107
|
+
# Add docstring
|
|
108
|
+
# version_note = self._generate_version_note()
|
|
109
|
+
# if wrapper.__doc__ is None:
|
|
110
|
+
# wrapper.__doc__ = version_note
|
|
111
|
+
# else:
|
|
112
|
+
# wrapper.__doc__ += version_note
|
|
113
|
+
|
|
114
|
+
wrapper.__doc__ = (wrapper.__doc__ or "") + self._generate_version_note(
|
|
115
|
+
num_of_white_spaces=self._calculate_white_space(wrapper.__doc__)
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return wrapper
|
|
119
|
+
|
|
120
|
+
def _calculate_white_space(self, docs: str | None) -> int:
|
|
121
|
+
"""
|
|
122
|
+
Calculates the number of leading white spaces
|
|
123
|
+
in __doc__ of original function
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
res = 0
|
|
127
|
+
if docs is None:
|
|
128
|
+
return res
|
|
129
|
+
|
|
130
|
+
# # Index of the last whitespaces
|
|
131
|
+
# # https://stackoverflow.com/a/13649118
|
|
132
|
+
# res = next((i for i, c in enumerate(docs) if not c.isspace()), 0)
|
|
133
|
+
# return res if res % 2 == 0 else res - 1
|
|
134
|
+
|
|
135
|
+
# Alternative solution
|
|
136
|
+
for char in docs:
|
|
137
|
+
if char == " ":
|
|
138
|
+
res += 1
|
|
139
|
+
if char == "\t":
|
|
140
|
+
res += 4
|
|
141
|
+
if not char.isspace():
|
|
142
|
+
break
|
|
143
|
+
return res
|
|
144
|
+
|
|
145
|
+
def _generate_version_note(self, num_of_white_spaces: int) -> str:
|
|
146
|
+
"""
|
|
147
|
+
Generates the version note string based on the mode
|
|
148
|
+
"""
|
|
149
|
+
reason_str = f": {self.reason}" if self.reason else ""
|
|
150
|
+
# return f"{self._LINEBREAK}*{self.mode.value} in version {self.version}{reason_str}*"
|
|
151
|
+
return _SPHINX_DOCS_TEMPLATE.substitute(
|
|
152
|
+
line_break=self._LINEBREAK + " " * num_of_white_spaces,
|
|
153
|
+
mode=self.mode.value,
|
|
154
|
+
version=self.version,
|
|
155
|
+
reason=reason_str,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
# Partial
|
|
160
|
+
# ---------------------------------------------------------------------------
|
|
161
|
+
versionadded = partial(SphinxDocstring, mode=SphinxDocstringMode.ADDED)
|
|
162
|
+
versionchanged = partial(SphinxDocstring, mode=SphinxDocstringMode.CHANGED)
|
|
163
|
+
deprecated = partial(SphinxDocstring, mode=SphinxDocstringMode.DEPRECATED)
|