absfuyu 5.0.0__py3-none-any.whl → 6.1.2__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 +3 -3
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +30 -14
- absfuyu/cli/config_group.py +9 -2
- absfuyu/cli/do_group.py +23 -6
- absfuyu/cli/game_group.py +27 -2
- absfuyu/cli/tool_group.py +81 -11
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +12 -8
- absfuyu/core/baseclass.py +929 -96
- absfuyu/core/baseclass2.py +44 -3
- absfuyu/core/decorator.py +70 -4
- absfuyu/core/docstring.py +64 -41
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +19 -6
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +204 -16
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +151 -34
- absfuyu/dxt/listext.py +969 -127
- absfuyu/dxt/strext.py +77 -17
- 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 +3 -2
- absfuyu/extra/da/__init__.py +72 -0
- absfuyu/extra/da/dadf.py +1600 -0
- absfuyu/extra/da/dadf_base.py +186 -0
- absfuyu/extra/da/df_func.py +181 -0
- absfuyu/extra/da/mplt.py +219 -0
- 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 +87 -0
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +7 -20
- absfuyu/fun/rubik.py +442 -0
- 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 -3
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +4 -4
- absfuyu/general/content.py +4 -4
- 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 +28 -2
- absfuyu/tools/checksum.py +144 -9
- absfuyu/tools/converter.py +120 -34
- absfuyu/tools/generator.py +461 -0
- absfuyu/tools/inspector.py +752 -0
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +47 -9
- absfuyu/tools/passwordlib.py +89 -25
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +718 -0
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +138 -0
- absfuyu/util/__init__.py +114 -6
- absfuyu/util/api.py +41 -18
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +43 -14
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +702 -82
- absfuyu/util/performance.py +122 -7
- absfuyu/util/shorten_number.py +244 -21
- absfuyu/util/text_table.py +481 -0
- absfuyu/util/zipped.py +8 -7
- absfuyu/version.py +79 -59
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -1078
- absfuyu/general/generator.py +0 -303
- absfuyu-5.0.0.dist-info/RECORD +0 -68
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/core/baseclass2.py
CHANGED
|
@@ -3,8 +3,8 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Bases for other features (with library)
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated: 12/
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -62,6 +62,47 @@ class ShowAllMethodsMixinInspectVer:
|
|
|
62
62
|
return result
|
|
63
63
|
|
|
64
64
|
|
|
65
|
+
class UniversalConfigMixin:
|
|
66
|
+
"""
|
|
67
|
+
Universal config Mixin
|
|
68
|
+
|
|
69
|
+
This use these attributes:
|
|
70
|
+
- _instance_config
|
|
71
|
+
- config
|
|
72
|
+
|
|
73
|
+
Example:
|
|
74
|
+
--------
|
|
75
|
+
>>> class Test(UniversalConfigMixin):
|
|
76
|
+
>>> DEFAULT_CONFIG = {"test": True}
|
|
77
|
+
>>> test = Test(config={"new_key": True})
|
|
78
|
+
>>> print(test.config)
|
|
79
|
+
{'test': True, 'new_key': True}
|
|
80
|
+
"""
|
|
81
|
+
|
|
82
|
+
DEFAULT_CONFIG = {}
|
|
83
|
+
|
|
84
|
+
def __init__(self, **kwargs) -> None:
|
|
85
|
+
try:
|
|
86
|
+
super().__init__(**kwargs)
|
|
87
|
+
except TypeError:
|
|
88
|
+
pass
|
|
89
|
+
# instance override
|
|
90
|
+
self._instance_config = kwargs.get("config", {})
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def config(self):
|
|
94
|
+
# Priority: instance > class > default
|
|
95
|
+
merged = dict(self.DEFAULT_CONFIG)
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# merged.update(getattr(self, "CLASS_CONFIG", {}))
|
|
99
|
+
merged.update(self._instance_config)
|
|
100
|
+
except Exception:
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
return merged
|
|
104
|
+
|
|
105
|
+
|
|
65
106
|
# Metaclass
|
|
66
107
|
# ---------------------------------------------------------------------------
|
|
67
108
|
class PerformanceTrackingMeta(type):
|
|
@@ -71,7 +112,7 @@ class PerformanceTrackingMeta(type):
|
|
|
71
112
|
Usage:
|
|
72
113
|
------
|
|
73
114
|
>>> class Demo(metaclass=PerformanceTrackingMeta):
|
|
74
|
-
... def __init__(self)
|
|
115
|
+
... def __init__(self): ...
|
|
75
116
|
>>> Demo()
|
|
76
117
|
--------------------------------------
|
|
77
118
|
Class: Demo
|
absfuyu/core/decorator.py
CHANGED
|
@@ -3,20 +3,24 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Decorator
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
11
11
|
# ---------------------------------------------------------------------------
|
|
12
|
-
__all__ = [
|
|
12
|
+
__all__ = [
|
|
13
|
+
"dummy_decorator",
|
|
14
|
+
"dummy_decorator_with_args",
|
|
15
|
+
"add_subclass_methods_decorator",
|
|
16
|
+
]
|
|
13
17
|
|
|
14
18
|
|
|
15
19
|
# Library
|
|
16
20
|
# ---------------------------------------------------------------------------
|
|
17
21
|
from collections.abc import Callable
|
|
18
22
|
from functools import wraps
|
|
19
|
-
from typing import ParamSpec, TypeVar, overload
|
|
23
|
+
from typing import ParamSpec, TypeVar, cast, overload
|
|
20
24
|
|
|
21
25
|
# Type
|
|
22
26
|
# ---------------------------------------------------------------------------
|
|
@@ -52,8 +56,10 @@ def dummy_decorator_with_args(*args, **kwargs):
|
|
|
52
56
|
|
|
53
57
|
@overload
|
|
54
58
|
def decorator(obj: T) -> T: ...
|
|
59
|
+
|
|
55
60
|
@overload
|
|
56
61
|
def decorator(obj: Callable[P, R]) -> Callable[P, R]: ...
|
|
62
|
+
|
|
57
63
|
def decorator(obj: Callable[P, R] | T) -> Callable[P, R] | T:
|
|
58
64
|
if isinstance(obj, type):
|
|
59
65
|
return obj
|
|
@@ -65,3 +71,63 @@ def dummy_decorator_with_args(*args, **kwargs):
|
|
|
65
71
|
return wrapper
|
|
66
72
|
|
|
67
73
|
return decorator
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def add_subclass_methods_decorator(cls: T) -> T:
|
|
77
|
+
"""
|
|
78
|
+
Class decorator replace the ``__init_subclass__`` method.
|
|
79
|
+
|
|
80
|
+
This method populates a dictionary with subclass names as keys
|
|
81
|
+
and their available methods as values.
|
|
82
|
+
|
|
83
|
+
- Create 2 class attributes: ``_METHOD_INCLUDE`` (bool) and ``SUBCLASS_METHODS`` (dict[str, list[str]])
|
|
84
|
+
- Automatically add subclass methods to class variable: ``SUBCLASS_METHODS``
|
|
85
|
+
- Set class attribute ``_METHOD_INCLUDE`` to ``False`` to exclude from ``SUBCLASS_METHODS``
|
|
86
|
+
|
|
87
|
+
Example:
|
|
88
|
+
--------
|
|
89
|
+
>>> # Normal behavior
|
|
90
|
+
>>> @add_subclass_methods_decorator
|
|
91
|
+
>>> class TestParent: ...
|
|
92
|
+
>>> class TestChild(TestParent):
|
|
93
|
+
... def method1(self): ...
|
|
94
|
+
>>> TestChild.SUBCLASS_METHODS
|
|
95
|
+
{'__main__.TestChild': ['method1']}
|
|
96
|
+
|
|
97
|
+
>>> # Hidden from ``SUBCLASS_METHODS``
|
|
98
|
+
>>> @add_subclass_methods_decorator
|
|
99
|
+
>>> class TestParent: ...
|
|
100
|
+
>>> class TestChildHidden(TestParent):
|
|
101
|
+
... _METHOD_INCLUDE = False
|
|
102
|
+
... def method1(self): ...
|
|
103
|
+
>>> TestChildHidden.SUBCLASS_METHODS
|
|
104
|
+
{}
|
|
105
|
+
"""
|
|
106
|
+
|
|
107
|
+
# Check for class
|
|
108
|
+
if not isinstance(cls, type):
|
|
109
|
+
raise ValueError("Object is not a class")
|
|
110
|
+
|
|
111
|
+
class AutoSubclassMixin:
|
|
112
|
+
_METHOD_INCLUDE: bool = True # Include in SUBCLASS_METHODS
|
|
113
|
+
SUBCLASS_METHODS: dict[str, list[str]] = {}
|
|
114
|
+
|
|
115
|
+
def __init_subclass__(cls, *args, **kwargs) -> None:
|
|
116
|
+
"""
|
|
117
|
+
This create a dictionary with:
|
|
118
|
+
- key (str) : Subclass
|
|
119
|
+
- value (list[str]): List of available methods
|
|
120
|
+
"""
|
|
121
|
+
super().__init_subclass__(*args, **kwargs)
|
|
122
|
+
|
|
123
|
+
if cls._METHOD_INCLUDE and not any(
|
|
124
|
+
[x.endswith(cls.__name__) for x in cls.SUBCLASS_METHODS.keys()]
|
|
125
|
+
):
|
|
126
|
+
methods_list: list[str] = [
|
|
127
|
+
k for k, v in cls.__dict__.items() if callable(v)
|
|
128
|
+
]
|
|
129
|
+
if len(methods_list) > 0:
|
|
130
|
+
name = f"{cls.__module__}.{cls.__name__}"
|
|
131
|
+
cls.SUBCLASS_METHODS.update({name: sorted(methods_list)})
|
|
132
|
+
|
|
133
|
+
return cast(T, type("AutoSubclass", (AutoSubclassMixin, cls), {}))
|
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:
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -28,9 +28,9 @@ from typing import ClassVar, ParamSpec, TypeVar, overload
|
|
|
28
28
|
|
|
29
29
|
# Type
|
|
30
30
|
# ---------------------------------------------------------------------------
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
34
|
|
|
35
35
|
_SPHINX_DOCS_TEMPLATE = Template("$line_break*$mode in version $version$reason*")
|
|
36
36
|
|
|
@@ -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:
|
|
@@ -53,6 +54,24 @@ class SphinxDocstring:
|
|
|
53
54
|
A class-based decorator to add a 'Version added',
|
|
54
55
|
'Version changed', or 'Deprecated' note to a function's docstring,
|
|
55
56
|
formatted for Sphinx documentation.
|
|
57
|
+
|
|
58
|
+
Parameters
|
|
59
|
+
----------
|
|
60
|
+
version : str
|
|
61
|
+
The version in which the function was added, changed, or deprecated.
|
|
62
|
+
|
|
63
|
+
reason : str | None, optional
|
|
64
|
+
An optional reason or description for the change
|
|
65
|
+
or deprecation, by default ``None``
|
|
66
|
+
|
|
67
|
+
mode : SphinxDocstringMode, optional
|
|
68
|
+
Specifies whether the function was 'added', 'changed', or 'deprecated',
|
|
69
|
+
by default SphinxDocstringMode.ADDED
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
Usage
|
|
73
|
+
-----
|
|
74
|
+
Use this as a decorator (``@SphinxDocstring(<parameters>)``)
|
|
56
75
|
"""
|
|
57
76
|
|
|
58
77
|
_LINEBREAK: ClassVar[str] = "\n\n" # Use ClassVar for constant
|
|
@@ -75,7 +94,7 @@ class SphinxDocstring:
|
|
|
75
94
|
An optional reason or description for the change
|
|
76
95
|
or deprecation, by default ``None``
|
|
77
96
|
|
|
78
|
-
mode : SphinxDocstringMode
|
|
97
|
+
mode : SphinxDocstringMode, optional
|
|
79
98
|
Specifies whether the function was 'added', 'changed', or 'deprecated',
|
|
80
99
|
by default SphinxDocstringMode.ADDED
|
|
81
100
|
|
|
@@ -87,35 +106,27 @@ class SphinxDocstring:
|
|
|
87
106
|
self.reason = reason
|
|
88
107
|
self.mode = mode
|
|
89
108
|
|
|
90
|
-
@overload
|
|
91
|
-
def __call__(self, obj:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
def __call__(self, obj:
|
|
109
|
+
@overload # Class overload
|
|
110
|
+
def __call__(self, obj: _T) -> _T: ...
|
|
111
|
+
|
|
112
|
+
@overload # Function overload
|
|
113
|
+
def __call__(self, obj: Callable[_P, _R]) -> Callable[_P, _R]: ...
|
|
114
|
+
|
|
115
|
+
def __call__(self, obj: _T | Callable[_P, _R]) -> _T | Callable[_P, _R]:
|
|
116
|
+
"""
|
|
117
|
+
Decorator for class and callable
|
|
118
|
+
"""
|
|
119
|
+
|
|
95
120
|
# Class wrapper
|
|
96
121
|
if isinstance(obj, type): # if inspect.isclass(obj):
|
|
97
|
-
|
|
98
|
-
num_of_white_spaces=self._calculate_white_space(obj.__doc__)
|
|
99
|
-
)
|
|
100
|
-
return obj
|
|
122
|
+
return self._update_doc(obj)
|
|
101
123
|
|
|
102
124
|
# Function wrapper
|
|
103
125
|
@wraps(obj)
|
|
104
|
-
def wrapper(*args:
|
|
126
|
+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
105
127
|
return obj(*args, **kwargs)
|
|
106
128
|
|
|
107
|
-
|
|
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
|
|
129
|
+
return self._update_doc(wrapper)
|
|
119
130
|
|
|
120
131
|
def _calculate_white_space(self, docs: str | None) -> int:
|
|
121
132
|
"""
|
|
@@ -127,19 +138,19 @@ class SphinxDocstring:
|
|
|
127
138
|
if docs is None:
|
|
128
139
|
return res
|
|
129
140
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
141
|
+
try:
|
|
142
|
+
# Replace tabs with space and split line
|
|
143
|
+
lines = docs.expandtabs(4).splitlines()
|
|
144
|
+
except UnicodeError:
|
|
145
|
+
return res
|
|
146
|
+
else:
|
|
147
|
+
# Get indentation of each line and unique it
|
|
148
|
+
indent_set = {len(line) - len(line.lstrip()) for line in lines[1:]}
|
|
149
|
+
# Drop 0
|
|
150
|
+
res_list = sorted([x for x in indent_set if x > 0])
|
|
151
|
+
|
|
152
|
+
if res_list:
|
|
153
|
+
return res_list[0]
|
|
143
154
|
return res
|
|
144
155
|
|
|
145
156
|
def _generate_version_note(self, num_of_white_spaces: int) -> str:
|
|
@@ -155,9 +166,21 @@ class SphinxDocstring:
|
|
|
155
166
|
reason=reason_str,
|
|
156
167
|
)
|
|
157
168
|
|
|
169
|
+
@overload
|
|
170
|
+
def _update_doc(self, obj: _T) -> _T: ...
|
|
171
|
+
@overload
|
|
172
|
+
def _update_doc(self, obj: Callable[_P, _R]) -> Callable[_P, _R]: ...
|
|
173
|
+
def _update_doc(self, obj: _T | Callable[_P, _R]) -> _T | Callable[_P, _R]:
|
|
174
|
+
"""Update docstring for an object"""
|
|
175
|
+
obj.__doc__ = (obj.__doc__ or "") + self._generate_version_note(
|
|
176
|
+
num_of_white_spaces=self._calculate_white_space(obj.__doc__)
|
|
177
|
+
)
|
|
178
|
+
return obj
|
|
179
|
+
|
|
158
180
|
|
|
159
181
|
# Partial
|
|
160
182
|
# ---------------------------------------------------------------------------
|
|
161
183
|
versionadded = partial(SphinxDocstring, mode=SphinxDocstringMode.ADDED)
|
|
162
184
|
versionchanged = partial(SphinxDocstring, mode=SphinxDocstringMode.CHANGED)
|
|
163
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:
|
|
6
|
+
Version: 6.1.1
|
|
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
|
@@ -3,8 +3,8 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Dummy functions when other libraries are unvailable
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -12,6 +12,7 @@ Date updated: 14/02/2025 (dd/mm/yyyy)
|
|
|
12
12
|
__all__ = [
|
|
13
13
|
"tqdm",
|
|
14
14
|
"unidecode",
|
|
15
|
+
"dummy_function",
|
|
15
16
|
]
|
|
16
17
|
|
|
17
18
|
|
|
@@ -23,8 +24,9 @@ from importlib import import_module
|
|
|
23
24
|
# ---------------------------------------------------------------------------
|
|
24
25
|
# tqdm wrapper
|
|
25
26
|
try:
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
_tqdm = import_module("tqdm")
|
|
28
|
+
tqdm = getattr(_tqdm, "tqdm") # noqa
|
|
29
|
+
except (ImportError, AttributeError):
|
|
28
30
|
|
|
29
31
|
def tqdm(iterable, *args, **kwargs):
|
|
30
32
|
"""
|
|
@@ -36,8 +38,9 @@ except ModuleNotFoundError:
|
|
|
36
38
|
|
|
37
39
|
# unidecode wrapper
|
|
38
40
|
try:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
_unidecode = import_module("unidecode")
|
|
42
|
+
unidecode = getattr(_unidecode, "unidecode") # noqa
|
|
43
|
+
except (ImportError, AttributeError):
|
|
41
44
|
|
|
42
45
|
def unidecode(*args, **kwargs):
|
|
43
46
|
"""
|
|
@@ -45,3 +48,13 @@ except ModuleNotFoundError:
|
|
|
45
48
|
install package ``unidecode`` to fully use this feature
|
|
46
49
|
"""
|
|
47
50
|
return args[0]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# dummy
|
|
54
|
+
def dummy_function(*args, **kwargs):
|
|
55
|
+
"""This is a dummy function"""
|
|
56
|
+
if args:
|
|
57
|
+
return args[0]
|
|
58
|
+
if kwargs:
|
|
59
|
+
return kwargs[list(kwargs)[0]]
|
|
60
|
+
return None
|
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.1
|
|
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
|