absfuyu 4.2.0__py3-none-any.whl → 5.0.1__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.

Files changed (72) hide show
  1. absfuyu/__init__.py +4 -4
  2. absfuyu/__main__.py +13 -1
  3. absfuyu/cli/__init__.py +2 -2
  4. absfuyu/cli/color.py +9 -2
  5. absfuyu/cli/config_group.py +2 -2
  6. absfuyu/cli/do_group.py +2 -37
  7. absfuyu/cli/game_group.py +2 -2
  8. absfuyu/cli/tool_group.py +7 -7
  9. absfuyu/config/__init__.py +17 -34
  10. absfuyu/core/__init__.py +49 -0
  11. absfuyu/core/baseclass.py +299 -0
  12. absfuyu/core/baseclass2.py +165 -0
  13. absfuyu/core/decorator.py +67 -0
  14. absfuyu/core/docstring.py +166 -0
  15. absfuyu/core/dummy_cli.py +67 -0
  16. absfuyu/core/dummy_func.py +49 -0
  17. absfuyu/dxt/__init__.py +42 -0
  18. absfuyu/dxt/dictext.py +201 -0
  19. absfuyu/dxt/dxt_support.py +79 -0
  20. absfuyu/dxt/intext.py +586 -0
  21. absfuyu/dxt/listext.py +508 -0
  22. absfuyu/dxt/strext.py +530 -0
  23. absfuyu/extra/__init__.py +12 -0
  24. absfuyu/extra/beautiful.py +252 -0
  25. absfuyu/{extensions → extra}/data_analysis.py +51 -82
  26. absfuyu/fun/__init__.py +110 -135
  27. absfuyu/fun/tarot.py +11 -19
  28. absfuyu/game/__init__.py +8 -2
  29. absfuyu/game/game_stat.py +8 -2
  30. absfuyu/game/sudoku.py +9 -3
  31. absfuyu/game/tictactoe.py +14 -7
  32. absfuyu/game/wordle.py +16 -10
  33. absfuyu/general/__init__.py +8 -81
  34. absfuyu/general/content.py +24 -38
  35. absfuyu/general/human.py +108 -228
  36. absfuyu/general/shape.py +1334 -0
  37. absfuyu/logger.py +10 -15
  38. absfuyu/pkg_data/__init__.py +137 -100
  39. absfuyu/pkg_data/deprecated.py +133 -0
  40. absfuyu/sort.py +6 -130
  41. absfuyu/tools/__init__.py +2 -2
  42. absfuyu/tools/checksum.py +33 -22
  43. absfuyu/tools/converter.py +51 -48
  44. absfuyu/{general → tools}/generator.py +17 -42
  45. absfuyu/tools/keygen.py +25 -30
  46. absfuyu/tools/obfuscator.py +246 -112
  47. absfuyu/tools/passwordlib.py +100 -30
  48. absfuyu/tools/shutdownizer.py +68 -47
  49. absfuyu/tools/web.py +4 -11
  50. absfuyu/util/__init__.py +17 -17
  51. absfuyu/util/api.py +10 -15
  52. absfuyu/util/json_method.py +7 -24
  53. absfuyu/util/lunar.py +5 -11
  54. absfuyu/util/path.py +22 -27
  55. absfuyu/util/performance.py +43 -67
  56. absfuyu/util/shorten_number.py +65 -14
  57. absfuyu/util/zipped.py +11 -17
  58. absfuyu/version.py +59 -42
  59. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/METADATA +41 -14
  60. absfuyu-5.0.1.dist-info/RECORD +68 -0
  61. absfuyu/core.py +0 -57
  62. absfuyu/everything.py +0 -32
  63. absfuyu/extensions/__init__.py +0 -12
  64. absfuyu/extensions/beautiful.py +0 -188
  65. absfuyu/fun/WGS.py +0 -134
  66. absfuyu/general/data_extension.py +0 -1796
  67. absfuyu/tools/stats.py +0 -226
  68. absfuyu/util/pkl.py +0 -67
  69. absfuyu-4.2.0.dist-info/RECORD +0 -59
  70. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/WHEEL +0 -0
  71. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.dist-info}/entry_points.txt +0 -0
  72. {absfuyu-4.2.0.dist-info → absfuyu-5.0.1.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 (`absfuyu[cli]` required):
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__ = "4.2.0"
25
+ __version__ = "5.0.1"
26
26
  __all__ = [
27
27
  "core",
28
28
  "config",
29
- "everything",
30
- "extensions",
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: 25/02/2025 (dd/mm/yyyy)
5
8
  """
6
9
 
7
- from absfuyu.cli import cli
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/__init__.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU
3
3
  -------
4
4
  COMMAND LINE INTERFACE
5
5
 
6
- Version: 1.1.0
7
- Date updated: 09/02/2025 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 16/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["cli"]
absfuyu/cli/color.py CHANGED
@@ -3,14 +3,21 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Color
5
5
 
6
- Version: 1.0.0
7
- Date updated: 14/04/2024 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 22/02/2025 (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,
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Config
5
5
 
6
- Version: 1.0.0
7
- Date updated: 14/04/2024 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 16/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["config_group"]
absfuyu/cli/do_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Do
5
5
 
6
- Version: 1.4.0
7
- Date updated: 07/02/2025 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 25/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["do_group"]
@@ -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/game_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Game
5
5
 
6
- Version: 1.0.0
7
- Date updated: 14/04/2024 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 16/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["game_group"]
absfuyu/cli/tool_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Tool
5
5
 
6
- Version: 1.0.0
7
- Date updated: 10/02/2025 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 22/02/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["tool_group"]
@@ -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: str | Literal["md5", "sha1", "sha256", "sha512"],
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")
@@ -3,34 +3,37 @@ Absfuyu: Configuration
3
3
  ----------------------
4
4
  Package configuration module
5
5
 
6
- Version: 2.0.5
7
- Date updated: 14/11/2024 (dd/mm/yyyy)
6
+ Version: 5.0.0
7
+ Date updated: 22/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 CONFIG_PATH
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
- # ]
@@ -0,0 +1,49 @@
1
+ """
2
+ Absfuyu: Core
3
+ -------------
4
+ Bases for other features
5
+
6
+ Version: 5.0.0
7
+ Date updated: 25/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: 25/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
+ print_result: bool = False,
55
+ include_classmethod: bool = True,
56
+ classmethod_indicator: str = "<classmethod>",
57
+ include_staticmethod: bool = True,
58
+ staticmethod_indicator: str = "<staticmethod>",
59
+ include_private_method: 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
+ print_result : bool, optional
68
+ Beautifully print the output, by default ``False``
69
+
70
+ include_classmethod : bool, optional
71
+ Whether to include classmethod in the output, by default ``True``
72
+
73
+ classmethod_indicator : str, optional
74
+ A string used to mark classmethod in the output. This string is appended
75
+ to the name of each classmethod to visually differentiate it from regular
76
+ instance methods, by default ``"<classmethod>"``
77
+
78
+ include_staticmethod : bool, optional
79
+ Whether to include staticmethod in the output, by default ``True``
80
+
81
+ staticmethod_indicator : str, optional
82
+ A string used to mark staticmethod in the output. This string is appended
83
+ to the name of each staticmethod to visually differentiate it from regular
84
+ instance methods, by default ``"<staticmethod>"``
85
+
86
+ include_private_method : bool, optional
87
+ Whether to include private method in 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)