absfuyu 5.6.1__py3-none-any.whl → 6.1.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 absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +2 -2
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +20 -2
- absfuyu/cli/tool_group.py +68 -4
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +10 -6
- absfuyu/core/baseclass.py +104 -34
- absfuyu/core/baseclass2.py +43 -2
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +4 -2
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +188 -6
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +72 -4
- absfuyu/dxt/listext.py +495 -23
- absfuyu/dxt/strext.py +2 -2
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +39 -3
- absfuyu/extra/da/dadf.py +458 -29
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +89 -5
- absfuyu/extra/da/mplt.py +2 -2
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +4 -6
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +2 -20
- absfuyu/fun/rubik.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -2
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +119 -4
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +24 -7
- absfuyu/tools/inspector.py +2 -2
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +213 -10
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +5 -8
- absfuyu/util/__init__.py +31 -2
- absfuyu/util/api.py +7 -4
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +313 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +206 -13
- absfuyu/util/text_table.py +2 -2
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +22 -19
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/METADATA +37 -8
- absfuyu-6.1.3.dist-info/RECORD +105 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -21
- absfuyu-5.6.1.dist-info/RECORD +0 -79
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/licenses/LICENSE +0 -0
absfuyu/core/docstring.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Sphinx docstring decorator
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
6
|
+
Version: 6.1.2
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -46,6 +46,7 @@ class SphinxDocstringMode(Enum):
|
|
|
46
46
|
ADDED = "Added"
|
|
47
47
|
CHANGED = "Changed"
|
|
48
48
|
DEPRECATED = "Deprecated"
|
|
49
|
+
PENDING_REMOVE = "Pending to remove"
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
class SphinxDocstring:
|
|
@@ -182,3 +183,4 @@ class SphinxDocstring:
|
|
|
182
183
|
versionadded = partial(SphinxDocstring, mode=SphinxDocstringMode.ADDED)
|
|
183
184
|
versionchanged = partial(SphinxDocstring, mode=SphinxDocstringMode.CHANGED)
|
|
184
185
|
deprecated = partial(SphinxDocstring, mode=SphinxDocstringMode.DEPRECATED)
|
|
186
|
+
pendingremove = partial(SphinxDocstring, mode=SphinxDocstringMode.PENDING_REMOVE)
|
absfuyu/core/dummy_cli.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Dummy cli
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
6
|
+
Version: 6.1.2
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -57,7 +57,7 @@ def get_parser(
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def cli() -> None:
|
|
60
|
-
desc = "This is a dummy cli,
|
|
60
|
+
desc = "This is a dummy cli, install <click> and <colorama> package to use this feature"
|
|
61
61
|
arg_parser = get_parser(
|
|
62
62
|
name=__title__,
|
|
63
63
|
description=desc,
|
absfuyu/core/dummy_func.py
CHANGED
absfuyu/dxt/__init__.py
CHANGED
absfuyu/dxt/base_type.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Data Extension
|
|
3
|
+
-----------------------
|
|
4
|
+
Base type expansion
|
|
5
|
+
|
|
6
|
+
Version: 6.1.2
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
# Module Package
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
__all__ = []
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
# Library
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
from abc import ABC
|
|
20
|
+
from typing import Generic, Self, TypeVar
|
|
21
|
+
|
|
22
|
+
# Class
|
|
23
|
+
# ---------------------------------------------------------------------------
|
|
24
|
+
T = TypeVar("T")
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BaseType(Generic[T], ABC):
|
|
28
|
+
"""
|
|
29
|
+
A universal base class for creating custom immutable type wrappers.
|
|
30
|
+
|
|
31
|
+
Features:
|
|
32
|
+
----------
|
|
33
|
+
- Works with any immutable type (int, str, float, etc.)
|
|
34
|
+
- Provides validation hook via `_validate`
|
|
35
|
+
- Automatically infers the base type from generic argument
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Example:
|
|
39
|
+
--------
|
|
40
|
+
>>> class NonNegativeInt(BaseType[int], int):
|
|
41
|
+
>>> @classmethod
|
|
42
|
+
>>> def _validate(cls, value: int) -> None:
|
|
43
|
+
>>> if value < 0:
|
|
44
|
+
>>> raise ValueError("Value must be non-negative")
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
# Automatically infer `_base_type` when subclassed
|
|
48
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
49
|
+
super().__init_subclass__(*args, **kwargs)
|
|
50
|
+
|
|
51
|
+
# print(getattr(cls, "__orig_bases__", []))
|
|
52
|
+
for base in getattr(cls, "__orig_bases__", []):
|
|
53
|
+
if hasattr(base, "__args__") and base.__args__:
|
|
54
|
+
cls._base_type: type[T] = base.__args__[0]
|
|
55
|
+
break
|
|
56
|
+
else:
|
|
57
|
+
raise TypeError(f"{cls.__name__} must specify a generic base type, e.g. BaseType[int]")
|
|
58
|
+
|
|
59
|
+
def __new__(cls, value: T) -> Self:
|
|
60
|
+
base_type = cls._base_type
|
|
61
|
+
if not isinstance(value, base_type):
|
|
62
|
+
raise TypeError(f"{cls.__name__} must be initialized with {base_type.__name__}, got {type(value).__name__}")
|
|
63
|
+
cls._validate(value)
|
|
64
|
+
return base_type.__new__(cls, value) # type: ignore
|
|
65
|
+
|
|
66
|
+
@classmethod
|
|
67
|
+
def _validate(cls, value: T) -> None:
|
|
68
|
+
"""Override this in subclasses to add custom validation."""
|
|
69
|
+
pass
|
|
70
|
+
|
|
71
|
+
def __repr__(self) -> str:
|
|
72
|
+
# base_name = self._base_type.__name__
|
|
73
|
+
# return f"{self.__class__.__name__}({base_name}={self._base_type(self)!r})" # type: ignore
|
|
74
|
+
return f"{self.__class__.__name__}({self._base_type(self)!r})" # type: ignore
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class IntBase(BaseType[int], int):
|
|
78
|
+
"""
|
|
79
|
+
A base class for creating custom integer-like types.
|
|
80
|
+
Provides a hook for validation and extension.
|
|
81
|
+
|
|
82
|
+
Parameters
|
|
83
|
+
----------
|
|
84
|
+
value : int
|
|
85
|
+
Int value
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
Usage:
|
|
89
|
+
------
|
|
90
|
+
Use ``_validate(cls, value: int) -> None:`` classmethod to create custom validator
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
pass
|
absfuyu/dxt/dictext.py
CHANGED
|
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
dict extension
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
6
|
+
Version: 6.1.2
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
10
12
|
# Module Package
|
|
11
13
|
# ---------------------------------------------------------------------------
|
|
12
14
|
__all__ = ["DictExt", "DictAnalyzeResult"]
|
|
@@ -16,7 +18,7 @@ __all__ = ["DictExt", "DictAnalyzeResult"]
|
|
|
16
18
|
# ---------------------------------------------------------------------------
|
|
17
19
|
import operator
|
|
18
20
|
from collections.abc import Callable
|
|
19
|
-
from typing import Any, NamedTuple, Self, TypeVar
|
|
21
|
+
from typing import Any, Literal, NamedTuple, Self, TypeVar, overload
|
|
20
22
|
|
|
21
23
|
from absfuyu.core import GetClassMembersMixin, versionadded, versionchanged
|
|
22
24
|
|
|
@@ -28,6 +30,8 @@ from absfuyu.core import GetClassMembersMixin, versionadded, versionchanged
|
|
|
28
30
|
# ---------------------------------------------------------------------------
|
|
29
31
|
KT = TypeVar("KT")
|
|
30
32
|
VT = TypeVar("VT")
|
|
33
|
+
VT2 = TypeVar("VT2")
|
|
34
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
31
35
|
|
|
32
36
|
|
|
33
37
|
# Class
|
|
@@ -43,9 +47,9 @@ class DictAnalyzeResult(NamedTuple):
|
|
|
43
47
|
min_list: list
|
|
44
48
|
|
|
45
49
|
|
|
46
|
-
class
|
|
50
|
+
class DictExtLegacy(GetClassMembersMixin, dict[KT, VT]):
|
|
47
51
|
"""
|
|
48
|
-
``dict`` extension
|
|
52
|
+
``dict`` extension (with no generic type-hints)
|
|
49
53
|
|
|
50
54
|
>>> # For a list of new methods
|
|
51
55
|
>>> DictExt.show_all_methods()
|
|
@@ -107,7 +111,7 @@ class DictExt(GetClassMembersMixin, dict[KT, VT]):
|
|
|
107
111
|
{9: 'abc'}
|
|
108
112
|
"""
|
|
109
113
|
# return self.__class__(zip(self.values(), self.keys()))
|
|
110
|
-
return self.__class__({v: k for k, v in self.items()})
|
|
114
|
+
return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
111
115
|
|
|
112
116
|
def apply(self, func: Callable[[Any], Any], apply_to_value: bool = True) -> Self:
|
|
113
117
|
"""
|
|
@@ -205,3 +209,181 @@ class DictExt(GetClassMembersMixin, dict[KT, VT]):
|
|
|
205
209
|
merged_output[k] = [value_self, value_other]
|
|
206
210
|
|
|
207
211
|
return self.__class__(merged_output)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
# Type-hints are hard coded, no work around Self[KT, VT] yet
|
|
215
|
+
class DictExt(GetClassMembersMixin, dict[KT, VT]):
|
|
216
|
+
"""
|
|
217
|
+
``dict`` extension
|
|
218
|
+
|
|
219
|
+
>>> # For a list of new methods
|
|
220
|
+
>>> DictExt.show_all_methods()
|
|
221
|
+
"""
|
|
222
|
+
|
|
223
|
+
# Analyze
|
|
224
|
+
@versionchanged("3.3.0", reason="Updated return type")
|
|
225
|
+
def analyze(self) -> DictAnalyzeResult:
|
|
226
|
+
"""
|
|
227
|
+
Analyze all the key values (``int``, ``float``)
|
|
228
|
+
in ``dict`` then return highest/lowest index
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
dict
|
|
233
|
+
Analyzed data
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
--------
|
|
238
|
+
>>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
|
|
239
|
+
>>> test.analyze()
|
|
240
|
+
DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
|
|
241
|
+
"""
|
|
242
|
+
try:
|
|
243
|
+
dct: dict = self.copy()
|
|
244
|
+
|
|
245
|
+
max_val: int | float = max(list(dct.values()))
|
|
246
|
+
min_val: int | float = min(list(dct.values()))
|
|
247
|
+
max_list = []
|
|
248
|
+
min_list = []
|
|
249
|
+
|
|
250
|
+
for k, v in dct.items():
|
|
251
|
+
if v == max_val:
|
|
252
|
+
max_list.append((k, v))
|
|
253
|
+
if v == min_val:
|
|
254
|
+
min_list.append((k, v))
|
|
255
|
+
|
|
256
|
+
return DictAnalyzeResult(max_val, min_val, max_list, min_list)
|
|
257
|
+
|
|
258
|
+
except TypeError:
|
|
259
|
+
err_msg = "Value must be int or float"
|
|
260
|
+
# logger.error(err_msg)
|
|
261
|
+
raise ValueError(err_msg) # noqa: B904
|
|
262
|
+
|
|
263
|
+
# Swap
|
|
264
|
+
def swap_items(self) -> DictExt[VT, KT]:
|
|
265
|
+
"""
|
|
266
|
+
Swap ``dict.keys()`` with ``dict.values()``
|
|
267
|
+
|
|
268
|
+
Returns
|
|
269
|
+
-------
|
|
270
|
+
DictExt
|
|
271
|
+
Swapped dict
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
Example:
|
|
275
|
+
--------
|
|
276
|
+
>>> test = DictExt({"abc": 9})
|
|
277
|
+
>>> test.swap_items()
|
|
278
|
+
{9: 'abc'}
|
|
279
|
+
"""
|
|
280
|
+
# return self.__class__(zip(self.values(), self.keys()))
|
|
281
|
+
out: dict[VT, KT] = {v: k for k, v in self.items()}
|
|
282
|
+
# return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
283
|
+
return self.__class__(out) # type: ignore
|
|
284
|
+
|
|
285
|
+
# Apply
|
|
286
|
+
@overload
|
|
287
|
+
def apply(self, func: Callable[[VT], R]) -> DictExt[KT, R]: ...
|
|
288
|
+
|
|
289
|
+
@overload
|
|
290
|
+
def apply(self, func: Callable[[KT], R], apply_to_value: Literal[False] = ...) -> DictExt[R, VT]: ...
|
|
291
|
+
|
|
292
|
+
def apply(self, func: Callable[[KT | VT], R], apply_to_value: bool = True) -> DictExt[KT | R, VT | R]: # type: ignore
|
|
293
|
+
"""
|
|
294
|
+
Apply function to ``DictExt.keys()`` or ``DictExt.values()``
|
|
295
|
+
|
|
296
|
+
Parameters
|
|
297
|
+
----------
|
|
298
|
+
func : Callable
|
|
299
|
+
Callable function
|
|
300
|
+
|
|
301
|
+
apply_to_value : bool
|
|
302
|
+
| ``True``: Apply ``func`` to ``DictExt.values()``
|
|
303
|
+
| ``False``: Apply ``func`` to ``DictExt.keys()``
|
|
304
|
+
|
|
305
|
+
Returns
|
|
306
|
+
-------
|
|
307
|
+
DictExt
|
|
308
|
+
DictExt
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
Example:
|
|
312
|
+
--------
|
|
313
|
+
>>> test = DictExt({"abc": 9})
|
|
314
|
+
>>> test.apply(str)
|
|
315
|
+
{'abc': '9'}
|
|
316
|
+
"""
|
|
317
|
+
if apply_to_value:
|
|
318
|
+
new_dict_v: dict[KT, R] = {k: func(v) for k, v in self.items()}
|
|
319
|
+
return self.__class__(new_dict_v) # type: ignore
|
|
320
|
+
else:
|
|
321
|
+
new_dict_k: dict[R, VT] = {func(k): v for k, v in self.items()}
|
|
322
|
+
return self.__class__(new_dict_k) # type: ignore
|
|
323
|
+
|
|
324
|
+
# Aggregate
|
|
325
|
+
@versionchanged("5.0.0", reason="Updated to handle more types and operator")
|
|
326
|
+
@versionadded("3.4.0")
|
|
327
|
+
def aggregate(
|
|
328
|
+
self,
|
|
329
|
+
other_dict: dict[KT, VT | VT2],
|
|
330
|
+
default_value: Any = 0,
|
|
331
|
+
operator_func: Callable[[Any, Any], R] = operator.add, # operator add
|
|
332
|
+
):
|
|
333
|
+
"""
|
|
334
|
+
Aggregates the values of the current dictionary with another dictionary.
|
|
335
|
+
|
|
336
|
+
For each unique key, this method applies the specified operator to the values
|
|
337
|
+
from both dictionaries. If a key exists in only one dictionary, its value is used.
|
|
338
|
+
If an error occurs during aggregation (e.g., incompatible types), the values
|
|
339
|
+
from both dictionaries are returned as a list.
|
|
340
|
+
|
|
341
|
+
Parameters
|
|
342
|
+
----------
|
|
343
|
+
other_dict : dict
|
|
344
|
+
The dictionary to aggregate with.
|
|
345
|
+
|
|
346
|
+
default_value : Any, optional
|
|
347
|
+
The value to use for missing keys, by default ``0``
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
operator_func : Callable[[Any, Any], Any], optional
|
|
351
|
+
A function that takes two arguments and returns a single value,
|
|
352
|
+
by default ``operator.add``
|
|
353
|
+
|
|
354
|
+
Returns
|
|
355
|
+
-------
|
|
356
|
+
Self
|
|
357
|
+
A new instance of the aggregated dictionary.
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
--------
|
|
362
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
363
|
+
>>> agg = {"test1": 10, "test2": 1}
|
|
364
|
+
>>> print(test.aggregate(agg))
|
|
365
|
+
{'test1': 10, 'test': 5, 'test2': 10}
|
|
366
|
+
|
|
367
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
368
|
+
>>> agg = {"test1": 10, "test2": "1"}
|
|
369
|
+
>>> print(test.aggregate(agg))
|
|
370
|
+
{'test1': 10, 'test': 5, 'test2': [9, '1']}
|
|
371
|
+
"""
|
|
372
|
+
merged_output: dict[KT, VT | R | list[VT | VT2]] = {}
|
|
373
|
+
|
|
374
|
+
# Create a set of all unique keys from both dictionaries
|
|
375
|
+
all_keys = set(self) | set(other_dict)
|
|
376
|
+
|
|
377
|
+
for k in all_keys:
|
|
378
|
+
# Retrieve values with default fallback
|
|
379
|
+
value_self = self.get(k, default_value)
|
|
380
|
+
value_other = other_dict.get(k, default_value)
|
|
381
|
+
|
|
382
|
+
try:
|
|
383
|
+
# Attempt to apply the operator for existing keys
|
|
384
|
+
merged_output[k] = operator_func(value_self, value_other)
|
|
385
|
+
except TypeError:
|
|
386
|
+
# If a TypeError occurs (e.g., if values are not compatible), store values as a list
|
|
387
|
+
merged_output[k] = [value_self, value_other] # type: ignore
|
|
388
|
+
|
|
389
|
+
return self.__class__(merged_output) # type: ignore
|
absfuyu/dxt/dxt_support.py
CHANGED
absfuyu/dxt/intext.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
|
|
|
3
3
|
-----------------------
|
|
4
4
|
int extension
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
6
|
+
Version: 6.1.2
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -15,8 +15,9 @@ __all__ = ["IntExt", "Pow"]
|
|
|
15
15
|
# Library
|
|
16
16
|
# ---------------------------------------------------------------------------
|
|
17
17
|
import math
|
|
18
|
+
from abc import ABC
|
|
18
19
|
from collections import Counter
|
|
19
|
-
from typing import Any, Literal, Self, overload
|
|
20
|
+
from typing import Any, Literal, Self, overload, override
|
|
20
21
|
|
|
21
22
|
from absfuyu.core.baseclass import GetClassMembersMixin
|
|
22
23
|
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
@@ -493,7 +494,7 @@ class IntExt(GetClassMembersMixin, int):
|
|
|
493
494
|
return divi_list
|
|
494
495
|
|
|
495
496
|
@overload
|
|
496
|
-
def prime_factor(self) -> list[Pow]: ...
|
|
497
|
+
def prime_factor(self) -> list[Pow]: ... # type: ignore
|
|
497
498
|
|
|
498
499
|
@overload
|
|
499
500
|
def prime_factor(self, short_form: Literal[False] = ...) -> list[int]: ...
|
|
@@ -633,3 +634,70 @@ class IntExt(GetClassMembersMixin, int):
|
|
|
633
634
|
|
|
634
635
|
quotient, remainder = divmod(self, p)
|
|
635
636
|
return [quotient + (i >= (p - remainder)) for i in range(p)]
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# Class
|
|
640
|
+
# ---------------------------------------------------------------------------
|
|
641
|
+
class IntBase(int, ABC):
|
|
642
|
+
"""
|
|
643
|
+
A base class for creating custom integer-like types.
|
|
644
|
+
Provides a hook for validation and extension.
|
|
645
|
+
|
|
646
|
+
Usage:
|
|
647
|
+
------
|
|
648
|
+
Use ``_validate(cls, value: int) -> None:`` classmethod to create custom validator
|
|
649
|
+
"""
|
|
650
|
+
|
|
651
|
+
def __new__(cls, value: int = 0) -> Self:
|
|
652
|
+
# Ensure the value is an integer
|
|
653
|
+
if not isinstance(value, int):
|
|
654
|
+
raise TypeError(f"{cls.__name__} must be initialized with an int, got {type(value).__name__}")
|
|
655
|
+
# Optional validation hook
|
|
656
|
+
cls._validate(value)
|
|
657
|
+
return int.__new__(cls, value)
|
|
658
|
+
|
|
659
|
+
@classmethod
|
|
660
|
+
def _validate(cls, value: int) -> None:
|
|
661
|
+
"""Override in subclasses to add custom validation."""
|
|
662
|
+
pass
|
|
663
|
+
|
|
664
|
+
def __repr__(self) -> str:
|
|
665
|
+
return f"{self.__class__.__name__}({int(self)})"
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
class PositiveInt(IntBase):
|
|
669
|
+
"""Only allows positive int"""
|
|
670
|
+
|
|
671
|
+
@classmethod
|
|
672
|
+
@override
|
|
673
|
+
def _validate(cls, value: int) -> None:
|
|
674
|
+
if value < 0:
|
|
675
|
+
raise ValueError(f"{cls.__name__} must be non-negative")
|
|
676
|
+
|
|
677
|
+
|
|
678
|
+
class NegativeInt(IntBase):
|
|
679
|
+
"""Only allows negative int"""
|
|
680
|
+
|
|
681
|
+
@classmethod
|
|
682
|
+
@override
|
|
683
|
+
def _validate(cls, value: int) -> None:
|
|
684
|
+
if value >= 0:
|
|
685
|
+
raise ValueError(f"{cls.__name__} must be non-positive")
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
class IntWithNote(IntBase):
|
|
689
|
+
"""Int with additional note"""
|
|
690
|
+
|
|
691
|
+
def __new__(cls, value: int = 0) -> Self:
|
|
692
|
+
ins = super().__new__(cls, value)
|
|
693
|
+
ins.note = ""
|
|
694
|
+
return ins
|
|
695
|
+
|
|
696
|
+
def __repr__(self) -> str:
|
|
697
|
+
if self.note == "":
|
|
698
|
+
return f"{int(self)}"
|
|
699
|
+
# return super().__repr__()
|
|
700
|
+
return f"{self.__class__.__name__}({int(self)}, note={repr(self.note)})"
|
|
701
|
+
|
|
702
|
+
def add_note(self, note: str) -> None:
|
|
703
|
+
self.note = note
|