absfuyu 4.2.0__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/color.py +7 -0
- absfuyu/cli/do_group.py +0 -35
- absfuyu/cli/tool_group.py +5 -5
- 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 +2 -2
- absfuyu/extra/beautiful.py +251 -0
- absfuyu/{extensions → 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 +136 -99
- absfuyu/pkg_data/deprecated.py +133 -0
- absfuyu/sort.py +6 -130
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +33 -22
- absfuyu/tools/converter.py +51 -48
- absfuyu/tools/keygen.py +25 -30
- absfuyu/tools/obfuscator.py +246 -112
- absfuyu/tools/passwordlib.py +99 -29
- absfuyu/tools/shutdownizer.py +68 -47
- 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-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/METADATA +41 -14
- 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/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.2.0.dist-info/RECORD +0 -59
- {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/WHEEL +0 -0
- {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/entry_points.txt +0 -0
- {absfuyu-4.2.0.dist-info → absfuyu-5.0.0.dist-info}/licenses/LICENSE +0 -0
absfuyu/__init__.py
CHANGED
|
@@ -15,19 +15,19 @@ Normal import:
|
|
|
15
15
|
>>> import absfuyu
|
|
16
16
|
>>> help(absfuyu)
|
|
17
17
|
|
|
18
|
-
Using in cmd
|
|
18
|
+
Using in cmd:
|
|
19
19
|
``$ fuyu --help``
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
__title__ = "absfuyu"
|
|
23
23
|
__author__ = "AbsoluteWinter"
|
|
24
24
|
__license__ = "MIT License"
|
|
25
|
-
__version__ = "
|
|
25
|
+
__version__ = "5.0.0"
|
|
26
26
|
__all__ = [
|
|
27
27
|
"core",
|
|
28
28
|
"config",
|
|
29
|
-
"
|
|
30
|
-
"
|
|
29
|
+
"dxt",
|
|
30
|
+
"extra",
|
|
31
31
|
"logger",
|
|
32
32
|
"fun",
|
|
33
33
|
"game",
|
absfuyu/__main__.py
CHANGED
|
@@ -2,14 +2,26 @@
|
|
|
2
2
|
ABSFUYU
|
|
3
3
|
-------
|
|
4
4
|
COMMAND LINE INTERFACE
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 18/02/2025 (dd/mm/yyyy)
|
|
5
8
|
"""
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
# Library
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
try:
|
|
13
|
+
from absfuyu.cli import cli
|
|
14
|
+
except ModuleNotFoundError: # Check for `click`, `colorama`
|
|
15
|
+
from absfuyu.core.dummy_cli import cli
|
|
8
16
|
|
|
9
17
|
|
|
18
|
+
# Function
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
10
20
|
def main() -> None:
|
|
11
21
|
cli()
|
|
12
22
|
|
|
13
23
|
|
|
24
|
+
# Run
|
|
25
|
+
# ---------------------------------------------------------------------------
|
|
14
26
|
if __name__ == "__main__":
|
|
15
27
|
main()
|
absfuyu/cli/color.py
CHANGED
|
@@ -7,10 +7,17 @@ Version: 1.0.0
|
|
|
7
7
|
Date updated: 14/04/2024 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
10
12
|
__all__ = ["COLOR"]
|
|
11
13
|
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
12
17
|
import colorama
|
|
13
18
|
|
|
19
|
+
# Color
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
14
21
|
COLOR = {
|
|
15
22
|
"green": colorama.Fore.LIGHTGREEN_EX,
|
|
16
23
|
"GREEN": colorama.Fore.GREEN,
|
absfuyu/cli/do_group.py
CHANGED
|
@@ -16,7 +16,6 @@ import click
|
|
|
16
16
|
from absfuyu import __title__
|
|
17
17
|
from absfuyu.cli.color import COLOR
|
|
18
18
|
from absfuyu.core import __package_feature__
|
|
19
|
-
from absfuyu.general.human import Human2
|
|
20
19
|
from absfuyu.tools.shutdownizer import ShutDownizer
|
|
21
20
|
from absfuyu.util.zipped import Zipper
|
|
22
21
|
from absfuyu.version import PkgVersion
|
|
@@ -60,37 +59,6 @@ def install(pkg: str) -> None:
|
|
|
60
59
|
click.echo(f"{COLOR['green']}absfuyu[{pkg}] installed")
|
|
61
60
|
|
|
62
61
|
|
|
63
|
-
@click.command()
|
|
64
|
-
def advice() -> None:
|
|
65
|
-
"""Give some recommendation when bored"""
|
|
66
|
-
from absfuyu.fun import im_bored
|
|
67
|
-
|
|
68
|
-
click.echo(f"{COLOR['green']}{im_bored()}")
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@click.command(name="fs")
|
|
72
|
-
@click.argument("date", type=str)
|
|
73
|
-
@click.argument("number_string", type=str)
|
|
74
|
-
def fs(date: str, number_string: str) -> None:
|
|
75
|
-
"""Feng-shui W.I.P"""
|
|
76
|
-
|
|
77
|
-
instance = Human2(date)
|
|
78
|
-
print(instance.fs(number_string))
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
@click.command(name="info")
|
|
82
|
-
@click.argument("date", type=str)
|
|
83
|
-
def info(date: str) -> None:
|
|
84
|
-
"""
|
|
85
|
-
Day info, format: yyyymmdd
|
|
86
|
-
|
|
87
|
-
Remake this
|
|
88
|
-
"""
|
|
89
|
-
|
|
90
|
-
instance = Human2(date)
|
|
91
|
-
print(instance.info())
|
|
92
|
-
|
|
93
|
-
|
|
94
62
|
@click.command(name="unzip")
|
|
95
63
|
@click.argument("dir", type=str)
|
|
96
64
|
def unzip_files_in_dir(dir: str) -> None:
|
|
@@ -116,8 +84,5 @@ def do_group() -> None:
|
|
|
116
84
|
|
|
117
85
|
do_group.add_command(update)
|
|
118
86
|
do_group.add_command(install)
|
|
119
|
-
do_group.add_command(advice)
|
|
120
|
-
do_group.add_command(fs)
|
|
121
|
-
do_group.add_command(info)
|
|
122
87
|
do_group.add_command(unzip_files_in_dir)
|
|
123
88
|
do_group.add_command(os_shutdown)
|
absfuyu/cli/tool_group.py
CHANGED
|
@@ -59,19 +59,19 @@ from absfuyu.tools.converter import Base64EncodeDecode, Text2Chemistry
|
|
|
59
59
|
)
|
|
60
60
|
def file_checksum(
|
|
61
61
|
file_path: str,
|
|
62
|
-
hash_mode:
|
|
62
|
+
hash_mode: Literal["md5", "sha1", "sha256", "sha512"],
|
|
63
63
|
save_result: bool,
|
|
64
64
|
recursive_mode: bool,
|
|
65
|
-
hash_to_compare: str,
|
|
65
|
+
hash_to_compare: str | None,
|
|
66
66
|
) -> None:
|
|
67
67
|
"""Checksum for file/directory"""
|
|
68
68
|
# print(hash_mode, save_result, recursive_mode)
|
|
69
69
|
instance = Checksum(file_path, hash_mode=hash_mode, save_result_to_file=save_result)
|
|
70
70
|
res = instance.checksum(recursive=recursive_mode)
|
|
71
|
-
if hash_to_compare:
|
|
72
|
-
print(res == hash_to_compare)
|
|
73
|
-
else:
|
|
71
|
+
if hash_to_compare is None:
|
|
74
72
|
print(res)
|
|
73
|
+
else:
|
|
74
|
+
print(res == hash_to_compare)
|
|
75
75
|
|
|
76
76
|
|
|
77
77
|
@click.command(name="t2c")
|
absfuyu/config/__init__.py
CHANGED
|
@@ -3,34 +3,37 @@ Absfuyu: Configuration
|
|
|
3
3
|
----------------------
|
|
4
4
|
Package configuration module
|
|
5
5
|
|
|
6
|
-
Version:
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 12/02/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module level
|
|
11
|
-
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
12
|
__all__ = [
|
|
13
13
|
"ABSFUYU_CONFIG",
|
|
14
14
|
"Config",
|
|
15
|
+
"CONFIG_PATH",
|
|
15
16
|
# "Setting"
|
|
16
17
|
]
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
# Library
|
|
20
|
-
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
from importlib.resources import files
|
|
21
23
|
from pathlib import Path
|
|
22
24
|
from typing import Any, TypedDict
|
|
23
25
|
|
|
24
|
-
from absfuyu.core import
|
|
26
|
+
from absfuyu.core import BaseClass
|
|
25
27
|
from absfuyu.util.json_method import JsonFile
|
|
26
28
|
|
|
27
29
|
# Setting
|
|
28
|
-
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
CONFIG_PATH = files("absfuyu.config").joinpath("config.json")
|
|
29
32
|
_SPACE_REPLACE = "-" # Replace " " character in setting name
|
|
30
33
|
|
|
31
34
|
|
|
32
35
|
# Type hint
|
|
33
|
-
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
34
37
|
class SettingDictFormat(TypedDict):
|
|
35
38
|
"""
|
|
36
39
|
Format for the ``setting`` section in ``config``
|
|
@@ -59,8 +62,8 @@ class ConfigFormat(TypedDict):
|
|
|
59
62
|
|
|
60
63
|
|
|
61
64
|
# Class
|
|
62
|
-
|
|
63
|
-
class Setting:
|
|
65
|
+
# ---------------------------------------------------------------------------
|
|
66
|
+
class Setting(BaseClass):
|
|
64
67
|
"""Setting"""
|
|
65
68
|
|
|
66
69
|
def __init__(self, name: str, value: Any, default: Any, help_: str = "") -> None:
|
|
@@ -78,9 +81,6 @@ class Setting:
|
|
|
78
81
|
def __str__(self) -> str:
|
|
79
82
|
return f"{self.__class__.__name__}({self.name}: {self.value})"
|
|
80
83
|
|
|
81
|
-
def __repr__(self) -> str:
|
|
82
|
-
return self.__str__()
|
|
83
|
-
|
|
84
84
|
@classmethod
|
|
85
85
|
def from_dict(cls, dict_data: dict[str, SettingDictFormat]):
|
|
86
86
|
"""
|
|
@@ -114,7 +114,7 @@ class Setting:
|
|
|
114
114
|
return output
|
|
115
115
|
|
|
116
116
|
|
|
117
|
-
class Config:
|
|
117
|
+
class Config(BaseClass):
|
|
118
118
|
"""
|
|
119
119
|
Config handling
|
|
120
120
|
"""
|
|
@@ -126,10 +126,10 @@ class Config:
|
|
|
126
126
|
self.config_path: Path = config_file
|
|
127
127
|
self.json_engine: JsonFile = JsonFile(self.config_path)
|
|
128
128
|
|
|
129
|
-
if name:
|
|
130
|
-
self.name = name
|
|
131
|
-
else:
|
|
129
|
+
if name is None:
|
|
132
130
|
self.name = self.config_path.name
|
|
131
|
+
else:
|
|
132
|
+
self.name = name
|
|
133
133
|
|
|
134
134
|
# Data
|
|
135
135
|
self.settings: list[Setting] = None # type: ignore
|
|
@@ -138,9 +138,6 @@ class Config:
|
|
|
138
138
|
def __str__(self) -> str:
|
|
139
139
|
return f"{self.__class__.__name__}({self.config_path.name})"
|
|
140
140
|
|
|
141
|
-
def __repr__(self) -> str:
|
|
142
|
-
return self.__str__()
|
|
143
|
-
|
|
144
141
|
# Data prepare and export
|
|
145
142
|
def _fetch_data(self) -> None:
|
|
146
143
|
"""Load data from ``self.config_file`` file"""
|
|
@@ -275,19 +272,5 @@ class Config:
|
|
|
275
272
|
|
|
276
273
|
|
|
277
274
|
# Init
|
|
278
|
-
|
|
275
|
+
# ---------------------------------------------------------------------------
|
|
279
276
|
ABSFUYU_CONFIG = Config(CONFIG_PATH) # type: ignore
|
|
280
|
-
|
|
281
|
-
# TODO: Create a config file when not available [W.I.P]
|
|
282
|
-
# _settings = [
|
|
283
|
-
# Setting(
|
|
284
|
-
# "auto-install-extra", False, False, "Automatically install required packages"
|
|
285
|
-
# ),
|
|
286
|
-
# Setting("first-run", True, True, "Check if this package has ever been run"),
|
|
287
|
-
# Setting(
|
|
288
|
-
# "luckgod-mode",
|
|
289
|
-
# False,
|
|
290
|
-
# False,
|
|
291
|
-
# "A chance that the machine will be randomly shutdown",
|
|
292
|
-
# ),
|
|
293
|
-
# ]
|
absfuyu/core/__init__.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Core
|
|
3
|
+
-------------
|
|
4
|
+
Bases for other features
|
|
5
|
+
|
|
6
|
+
Version: 5.0.0
|
|
7
|
+
Date updated: 13/02/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
# color
|
|
14
|
+
"CLITextColor",
|
|
15
|
+
# path
|
|
16
|
+
# "CORE_PATH",
|
|
17
|
+
# class
|
|
18
|
+
"ShowAllMethodsMixin",
|
|
19
|
+
"BaseClass",
|
|
20
|
+
# wrapper
|
|
21
|
+
"tqdm",
|
|
22
|
+
"unidecode",
|
|
23
|
+
# decorator
|
|
24
|
+
"deprecated",
|
|
25
|
+
"versionadded",
|
|
26
|
+
"versionchanged",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
__package_feature__ = [
|
|
30
|
+
"beautiful", # BeautifulOutput
|
|
31
|
+
"docs", # For (package) hatch's env use only
|
|
32
|
+
"extra", # DataFrame
|
|
33
|
+
"full", # All package
|
|
34
|
+
"dev",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Library
|
|
39
|
+
# ---------------------------------------------------------------------------
|
|
40
|
+
# from importlib.resources import files
|
|
41
|
+
|
|
42
|
+
# Most used features are imported to core
|
|
43
|
+
from absfuyu.core.baseclass import BaseClass, CLITextColor, ShowAllMethodsMixin
|
|
44
|
+
from absfuyu.core.docstring import deprecated, versionadded, versionchanged
|
|
45
|
+
from absfuyu.core.dummy_func import tqdm, unidecode
|
|
46
|
+
|
|
47
|
+
# Path
|
|
48
|
+
# ---------------------------------------------------------------------------
|
|
49
|
+
# CORE_PATH = files("absfuyu")
|
|
@@ -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)
|