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.

Files changed (102) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +2 -2
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +2 -2
  6. absfuyu/cli/config_group.py +2 -2
  7. absfuyu/cli/do_group.py +2 -2
  8. absfuyu/cli/game_group.py +20 -2
  9. absfuyu/cli/tool_group.py +68 -4
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +10 -6
  12. absfuyu/core/baseclass.py +104 -34
  13. absfuyu/core/baseclass2.py +43 -2
  14. absfuyu/core/decorator.py +2 -2
  15. absfuyu/core/docstring.py +4 -2
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +2 -2
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +188 -6
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +72 -4
  23. absfuyu/dxt/listext.py +495 -23
  24. absfuyu/dxt/strext.py +2 -2
  25. absfuyu/extra/__init__.py +2 -2
  26. absfuyu/extra/audio/__init__.py +8 -0
  27. absfuyu/extra/audio/_util.py +57 -0
  28. absfuyu/extra/audio/convert.py +192 -0
  29. absfuyu/extra/audio/lossless.py +281 -0
  30. absfuyu/extra/beautiful.py +2 -2
  31. absfuyu/extra/da/__init__.py +39 -3
  32. absfuyu/extra/da/dadf.py +458 -29
  33. absfuyu/extra/da/dadf_base.py +2 -2
  34. absfuyu/extra/da/df_func.py +89 -5
  35. absfuyu/extra/da/mplt.py +2 -2
  36. absfuyu/extra/ggapi/__init__.py +8 -0
  37. absfuyu/extra/ggapi/gdrive.py +223 -0
  38. absfuyu/extra/ggapi/glicense.py +148 -0
  39. absfuyu/extra/ggapi/glicense_df.py +186 -0
  40. absfuyu/extra/ggapi/gsheet.py +88 -0
  41. absfuyu/extra/img/__init__.py +30 -0
  42. absfuyu/extra/img/converter.py +402 -0
  43. absfuyu/extra/img/dup_check.py +291 -0
  44. absfuyu/extra/pdf.py +4 -6
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +2 -20
  48. absfuyu/fun/rubik.py +2 -2
  49. absfuyu/fun/tarot.py +2 -2
  50. absfuyu/game/__init__.py +2 -2
  51. absfuyu/game/game_stat.py +2 -2
  52. absfuyu/game/schulte.py +78 -0
  53. absfuyu/game/sudoku.py +2 -2
  54. absfuyu/game/tictactoe.py +2 -2
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +2 -2
  57. absfuyu/general/content.py +2 -2
  58. absfuyu/general/human.py +2 -2
  59. absfuyu/general/resrel.py +213 -0
  60. absfuyu/general/shape.py +3 -8
  61. absfuyu/general/tax.py +344 -0
  62. absfuyu/logger.py +806 -59
  63. absfuyu/numbers/__init__.py +13 -0
  64. absfuyu/numbers/number_to_word.py +321 -0
  65. absfuyu/numbers/shorten_number.py +303 -0
  66. absfuyu/numbers/time_duration.py +217 -0
  67. absfuyu/pkg_data/__init__.py +2 -2
  68. absfuyu/pkg_data/deprecated.py +2 -2
  69. absfuyu/pkg_data/logo.py +1462 -0
  70. absfuyu/sort.py +4 -4
  71. absfuyu/tools/__init__.py +2 -2
  72. absfuyu/tools/checksum.py +119 -4
  73. absfuyu/tools/converter.py +2 -2
  74. absfuyu/tools/generator.py +24 -7
  75. absfuyu/tools/inspector.py +2 -2
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +2 -2
  78. absfuyu/tools/passwordlib.py +2 -2
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +213 -10
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +5 -8
  83. absfuyu/util/__init__.py +31 -2
  84. absfuyu/util/api.py +7 -4
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +2 -2
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +313 -4
  91. absfuyu/util/performance.py +2 -2
  92. absfuyu/util/shorten_number.py +206 -13
  93. absfuyu/util/text_table.py +2 -2
  94. absfuyu/util/zipped.py +2 -2
  95. absfuyu/version.py +22 -19
  96. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/METADATA +37 -8
  97. absfuyu-6.1.3.dist-info/RECORD +105 -0
  98. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -21
  100. absfuyu-5.6.1.dist-info/RECORD +0 -79
  101. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/entry_points.txt +0 -0
  102. {absfuyu-5.6.1.dist-info → absfuyu-6.1.3.dist-info}/licenses/LICENSE +0 -0
absfuyu/__init__.py CHANGED
@@ -22,19 +22,21 @@ Using in cmd:
22
22
  __title__ = "absfuyu"
23
23
  __author__ = "AbsoluteWinter"
24
24
  __license__ = "MIT License"
25
- __version__ = "5.6.1"
25
+ __version__ = "6.1.3"
26
26
  __all__ = [
27
27
  "core",
28
28
  "config",
29
29
  "dxt",
30
30
  "extra",
31
- "logger",
32
31
  "fun",
33
32
  "game",
34
33
  "general",
34
+ "numbers",
35
35
  "pkg_data",
36
- "sort",
37
36
  "tools",
38
37
  "util",
38
+ "logger",
39
+ "sort",
40
+ "typings",
39
41
  "version",
40
42
  ]
absfuyu/__main__.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU
3
3
  -------
4
4
  COMMAND LINE INTERFACE
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Library
absfuyu/cli/__init__.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU
3
3
  -------
4
4
  COMMAND LINE INTERFACE
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  __all__ = ["cli"]
@@ -15,6 +15,7 @@ import click
15
15
  import colorama
16
16
 
17
17
  from absfuyu import __title__, __version__
18
+ from absfuyu.cli.audio_group import audio_group
18
19
  from absfuyu.cli.color import COLOR
19
20
  from absfuyu.cli.config_group import config_group
20
21
  from absfuyu.cli.do_group import do_group
@@ -25,6 +26,15 @@ from absfuyu.cli.tool_group import tool_group
25
26
  colorama.init(autoreset=True)
26
27
 
27
28
 
29
+ def quick_info() -> str:
30
+ return (
31
+ f"- os/type: {platform.system().lower()}\n"
32
+ f"- os/kernel: {platform.version()}\n"
33
+ f"- os/arch: {platform.machine().lower()}\n"
34
+ f"- python version: {platform.python_version()}\n"
35
+ )
36
+
37
+
28
38
  @click.command()
29
39
  def version() -> None:
30
40
  """Show current version"""
@@ -50,4 +60,5 @@ cli.add_command(config_group)
50
60
  cli.add_command(do_group)
51
61
  cli.add_command(game_group)
52
62
  cli.add_command(tool_group)
63
+ cli.add_command(audio_group)
53
64
  cli.add_command(version)
@@ -0,0 +1,98 @@
1
+ """
2
+ ABSFUYU CLI
3
+ -----------
4
+ Audio
5
+
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
+ """
9
+
10
+ # Module Package
11
+ # ---------------------------------------------------------------------------
12
+ __all__ = ["audio_group"]
13
+
14
+
15
+ # Library
16
+ # ---------------------------------------------------------------------------
17
+ from pathlib import Path
18
+
19
+ import click
20
+
21
+ from absfuyu.extra.audio.convert import DirectoryAudioConvertMixin
22
+ from absfuyu.extra.audio.lossless import DirectoryAudioLosslessCheckMixin
23
+
24
+
25
+ # CLI
26
+ # ---------------------------------------------------------------------------
27
+ @click.command(name="c3")
28
+ @click.argument("dir_path", type=str)
29
+ @click.option(
30
+ "--recursive",
31
+ "-r",
32
+ "recursive_mode",
33
+ type=bool,
34
+ default=False,
35
+ is_flag=True,
36
+ show_default=True,
37
+ help="Scan for every file in the folder (including child folder)",
38
+ )
39
+ @click.option(
40
+ "--format",
41
+ "-f",
42
+ "from_format",
43
+ type=str,
44
+ default=".flac",
45
+ show_default=True,
46
+ help="From which audio format",
47
+ )
48
+ def convert_to_mp3(dir_path: str, from_format: str, recursive_mode: bool) -> None:
49
+ """Convert to .mp3 file"""
50
+
51
+ engine = DirectoryAudioConvertMixin(Path(dir_path).resolve())
52
+
53
+ try:
54
+ engine.convert_to_mp3(from_format=from_format, recursive=recursive_mode)
55
+ except Exception:
56
+ engine.convert_to_mp3_single_thread(from_format=from_format, recursive=recursive_mode)
57
+ click.echo("Done")
58
+
59
+
60
+ @click.command(name="lc")
61
+ @click.argument("dir_path", type=str)
62
+ @click.option(
63
+ "--recursive",
64
+ "-r",
65
+ "recursive_mode",
66
+ type=bool,
67
+ default=False,
68
+ is_flag=True,
69
+ show_default=True,
70
+ help="Scan for every file in the folder (including child folder)",
71
+ )
72
+ @click.option(
73
+ "--format",
74
+ "-f",
75
+ "format",
76
+ type=str,
77
+ default=".flac",
78
+ show_default=True,
79
+ help="Audio format",
80
+ )
81
+ def lossless_check(dir_path: str, format: str, recursive_mode: bool) -> None:
82
+ """Check for lossless audio file"""
83
+
84
+ engine = DirectoryAudioLosslessCheckMixin(Path(dir_path).resolve())
85
+ try:
86
+ engine.lossless_check(from_format=format, recursive=recursive_mode)
87
+ except Exception:
88
+ engine.lossless_check_single_thread(from_format=format, recursive=recursive_mode)
89
+ click.echo("Done")
90
+
91
+
92
+ @click.group(name="audio")
93
+ def audio_group() -> None:
94
+ """Audio related"""
95
+ pass
96
+
97
+ audio_group.add_command(lossless_check)
98
+ audio_group.add_command(convert_to_mp3)
absfuyu/cli/color.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Color
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Config
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
absfuyu/cli/do_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Do
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
absfuyu/cli/game_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Game
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -17,6 +17,7 @@ __all__ = ["game_group"]
17
17
  import click
18
18
 
19
19
  from absfuyu.game import game_escapeLoop, game_RockPaperScissors
20
+ from absfuyu.game.schulte import SchulteTable
20
21
  from absfuyu.game.sudoku import Sudoku
21
22
  from absfuyu.game.tictactoe import GameMode, TicTacToe
22
23
  from absfuyu.game.wordle import Wordle
@@ -103,6 +104,22 @@ def tictactoe(size: int, game_mode: str, bot_time: float) -> None:
103
104
  instance.play(game_mode, bot_time=bot_time)
104
105
 
105
106
 
107
+ @click.command(name="schulte")
108
+ @click.option(
109
+ "--size",
110
+ "-s",
111
+ "size",
112
+ type=int,
113
+ default=5,
114
+ show_default=True,
115
+ help="Size of the table",
116
+ )
117
+ def schulte_table(size: int) -> None:
118
+ """Schulte table"""
119
+ engine = SchulteTable(size=size)
120
+ engine.make_table()
121
+
122
+
106
123
  @click.group(name="game")
107
124
  def game_group() -> None:
108
125
  """Play game"""
@@ -114,3 +131,4 @@ game_group.add_command(escape_loop)
114
131
  game_group.add_command(wordle_solver)
115
132
  game_group.add_command(sudoku_solver)
116
133
  game_group.add_command(tictactoe)
134
+ game_group.add_command(schulte_table)
absfuyu/cli/tool_group.py CHANGED
@@ -3,8 +3,8 @@ ABSFUYU CLI
3
3
  -----------
4
4
  Tool
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -14,6 +14,7 @@ __all__ = ["tool_group"]
14
14
 
15
15
  # Library
16
16
  # ---------------------------------------------------------------------------
17
+ from pathlib import Path
17
18
  from typing import Literal
18
19
 
19
20
  import click
@@ -24,7 +25,7 @@ from absfuyu.tools.converter import Base64EncodeDecode, Text2Chemistry
24
25
 
25
26
  # CLI
26
27
  # ---------------------------------------------------------------------------
27
- @click.command(name="checksum")
28
+ @click.command(name="cs")
28
29
  @click.argument("file_path", type=str)
29
30
  @click.option(
30
31
  "--hashmode",
@@ -80,6 +81,67 @@ def file_checksum(
80
81
  click.echo(res == hash_to_compare)
81
82
 
82
83
 
84
+ @click.command(name="cimg")
85
+ @click.argument("dir_path", type=str)
86
+ @click.argument("to_format", type=str)
87
+ def convert_img(dir_path: str, to_format: str) -> None:
88
+ """Convert image in directory"""
89
+ from absfuyu.extra.img.converter import ImgConverter
90
+
91
+ engine = ImgConverter(Path(dir_path).resolve())
92
+ engine.img_convert(to_format)
93
+ click.echo("Done")
94
+
95
+
96
+ @click.command(name="rdimg")
97
+ @click.argument("dir_path", type=str)
98
+ @click.option(
99
+ "--dryrun",
100
+ "-d",
101
+ "dry_run",
102
+ type=bool,
103
+ default=False,
104
+ is_flag=True,
105
+ show_default=True,
106
+ help="Simulate deleting only",
107
+ )
108
+ @click.option(
109
+ "--recursive",
110
+ "-r",
111
+ "recursive_mode",
112
+ type=bool,
113
+ default=False,
114
+ is_flag=True,
115
+ show_default=True,
116
+ help="Scan for every file in the folder (including child folder)",
117
+ )
118
+ @click.option(
119
+ "--threshold",
120
+ "-t",
121
+ "threshold",
122
+ type=int,
123
+ default=5,
124
+ show_default=True,
125
+ help="Threshold: 0 for exact image, 5 to 10 for light edit",
126
+ )
127
+ @click.option(
128
+ "--keepmode",
129
+ "-k",
130
+ "keep_mode",
131
+ type=click.Choice(["first", "last", "best"]),
132
+ default="best",
133
+ show_default=True,
134
+ help="Keep mode",
135
+ )
136
+ def remove_dup_img(dir_path: str, threshold: bool, recursive_mode: bool, keep_mode: str, dry_run: bool) -> None:
137
+ """Remove duplicate images in directory"""
138
+ from absfuyu.extra.img.dup_check import DirectoryRemoveDuplicateImageMixin as DRDI
139
+
140
+ engine = DRDI(Path(dir_path).resolve())
141
+ engine.remove_duplicate_images(threshold=threshold, recursive=recursive_mode, keep_mode=keep_mode, dry_run=dry_run)
142
+ click.echo("Done")
143
+
144
+
83
145
  @click.command(name="t2c")
84
146
  @click.argument("text", type=str)
85
147
  def text2chem(text: str) -> None:
@@ -121,7 +183,7 @@ def base64convert_img(img_path: str, data_tag: bool) -> None:
121
183
 
122
184
 
123
185
  @click.group(name="b64")
124
- def base64_group():
186
+ def base64_group() -> None:
125
187
  """Base64 encode decode"""
126
188
  pass
127
189
 
@@ -138,5 +200,7 @@ def tool_group() -> None:
138
200
 
139
201
 
140
202
  tool_group.add_command(file_checksum)
203
+ tool_group.add_command(convert_img)
204
+ tool_group.add_command(remove_dup_img)
141
205
  tool_group.add_command(base64_group)
142
206
  tool_group.add_command(text2chem)
@@ -1,10 +1,10 @@
1
1
  """
2
2
  Absfuyu: Configuration
3
3
  ----------------------
4
- Package configuration module
4
+ Package configuration module - internal use
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module level
absfuyu/core/__init__.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Core
3
3
  -------------
4
4
  Bases for other features
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -27,11 +27,15 @@ __all__ = [
27
27
  ]
28
28
 
29
29
  __package_feature__ = [
30
- "beautiful", # BeautifulOutput
31
- "docs", # For (package) hatch's env use only
32
- "extra", # DataFrame
33
30
  "full", # All package
34
- "dev",
31
+ "docs", # For (package) hatch's env use only
32
+ "extra", # Extra features
33
+ "beautiful", # BeautifulOutput
34
+ "dadf", # DataFrame
35
+ "pdf", # PDF
36
+ "pic", # picture related
37
+ "xml", # XML
38
+ "ggapi", # Google
35
39
  ]
36
40
 
37
41
 
absfuyu/core/baseclass.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Core
3
3
  -------------
4
4
  Bases for other features
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -18,6 +18,7 @@ __all__ = [
18
18
  # Mixins
19
19
  "GetClassMembersMixin",
20
20
  "AutoREPRMixin",
21
+ "AddFormatMixin",
21
22
  # Class
22
23
  "BaseClass",
23
24
  "BaseDataclass",
@@ -27,6 +28,7 @@ __all__ = [
27
28
 
28
29
  # Library
29
30
  # ---------------------------------------------------------------------------
31
+ from collections.abc import Callable
30
32
  from dataclasses import dataclass, field
31
33
  from typing import Any, ClassVar, Literal, Self
32
34
 
@@ -57,7 +59,8 @@ class BaseDataclass:
57
59
  Base dataclass.
58
60
 
59
61
  Contains util methods:
60
- - Get fields
62
+ - _get_fields
63
+ - to_dict
61
64
  """
62
65
 
63
66
  @classmethod
@@ -73,6 +76,22 @@ class BaseDataclass:
73
76
  _fields = getattr(cls, "__dataclass_fields__", ())
74
77
  return tuple(_fields)
75
78
 
79
+ # @versionadded("5.11.0")
80
+ def to_dict(self):
81
+ """
82
+ Convert dataclass into dict
83
+
84
+ Returns
85
+ -------
86
+ dict
87
+ Dataclass converted into dict
88
+ """
89
+ fields = self._get_fields()
90
+ output = {}
91
+ for field in fields:
92
+ output.__setitem__(field, self.__getattribute__(field))
93
+ return output
94
+
76
95
 
77
96
  # Support
78
97
  # ---------------------------------------------------------------------------
@@ -844,20 +863,6 @@ class GetClassMembersMixin:
844
863
  mems[self.__class__.__name__].attributes = attrs
845
864
  return mems
846
865
 
847
- # @remove("6.0.0")
848
- # @deprecated("5.5.0")
849
- # @versionadded("5.1.0")
850
- @classmethod
851
- def _get_methods_and_properties(
852
- cls,
853
- skip_private_attribute: bool = True,
854
- include_private_method: bool = False,
855
- ) -> ClassMembersResult:
856
- """Wrapper of ``cls._get_members``, deprecated"""
857
- return cls._get_members(
858
- dunder=skip_private_attribute, private=include_private_method
859
- )
860
-
861
866
  @classmethod
862
867
  def show_all_methods(
863
868
  cls,
@@ -950,13 +955,6 @@ class GetClassMembersMixin:
950
955
  return result.prioritize_value("properties")
951
956
 
952
957
 
953
- # Temp name for backward compability
954
- # Will be removed at version 6.0.0
955
- MethodNPropertyList = ClassMembers
956
- MethodNPropertyResult = ClassMembersResult
957
- ShowAllMethodsMixin = GetClassMembersMixin
958
-
959
-
960
958
  class AutoREPRMixin:
961
959
  """
962
960
  Generate ``repr()`` output as ``<class(param1=any, param2=any, ...)>``
@@ -1014,19 +1012,38 @@ class AutoREPRMixin:
1014
1012
  return f"{self.__class__.__name__}({sep.join(out)})"
1015
1013
 
1016
1014
 
1017
- # Class
1018
- # ---------------------------------------------------------------------------
1019
- class BaseClass(GetClassMembersMixin, AutoREPRMixin):
1020
- """Base class"""
1015
+ class AddFormatMixin:
1016
+ """
1017
+ This mixin that allows classes to define and register custom format
1018
+ specifications for use with Python's built-in :func:`format` function
1019
+ and f-string formatting.
1021
1020
 
1022
- def __str__(self) -> str:
1023
- return repr(self)
1021
+ This mixin extends the standard ``__format__`` mechanism by letting you
1022
+ attach named formatting presets at runtime. Each format spec is simply a
1023
+ callable that receives the object instance and returns a formatted string.
1024
+
1025
+ Attribute ``_format_specs`` is used and must not be overwritten in any
1026
+ circumstances.
1027
+ """
1028
+
1029
+ def __init__(self) -> None:
1030
+ self._format_specs: dict[str, Callable[[Self], str]] = {}
1024
1031
 
1025
1032
  def __format__(self, format_spec: str) -> str:
1026
1033
  """
1027
- Formats the object according to the specified format.
1028
- If no format_spec is provided, returns the object's string representation.
1029
- (Currently a dummy function)
1034
+ Format the object using a registered format specification.
1035
+
1036
+ Parameters
1037
+ ----------
1038
+ format_spec : str
1039
+ The name of a previously registered format spec. If empty or not
1040
+ found, the object's ``__str__`` representation is returned.
1041
+
1042
+ Returns
1043
+ -------
1044
+ str
1045
+ The formatted string according to the given format spec.
1046
+
1030
1047
 
1031
1048
  Usage
1032
1049
  -----
@@ -1035,7 +1052,60 @@ class BaseClass(GetClassMembersMixin, AutoREPRMixin):
1035
1052
  >>> print(format(<object>, <format_spec>))
1036
1053
  """
1037
1054
 
1038
- return self.__str__()
1055
+ func = self._format_specs.get(format_spec, None)
1056
+
1057
+ if func is None:
1058
+ return self.__str__()
1059
+ else:
1060
+ return func(self)
1061
+
1062
+ def add_format_spec(self, name: str, format_func: Callable[[Self], str]) -> None:
1063
+ """
1064
+ Register a custom format specification.
1065
+
1066
+ Parameters
1067
+ ----------
1068
+ name : str
1069
+ The format specifier string to register.
1070
+
1071
+ format_func : Callable[[Self], str]
1072
+ A function that receives the object instance and returns a formatted string.
1073
+ """
1074
+ if getattr(self, "_format_specs", None) is None:
1075
+ self._format_specs: dict[str, Callable[[Self], str]] = {}
1076
+ self._format_specs[name] = format_func
1077
+
1078
+ @property
1079
+ def available_format_spec(self) -> list[str]:
1080
+ """
1081
+ List all registered format specification names.
1082
+
1083
+ Returns
1084
+ -------
1085
+ list[str]
1086
+ A list containing the names of all format specs that have been
1087
+ registered via :meth:`add_format_spec`. If no format specs exist,
1088
+ an empty list is returned.
1089
+
1090
+
1091
+ Notes
1092
+ -----
1093
+ - This is a convenience property to inspect which formatting presets
1094
+ are currently available for the object.
1095
+ - The list contains only the names (keys), not the formatting functions.
1096
+ """
1097
+ if getattr(self, "_format_specs", None) is None:
1098
+ return []
1099
+ return list(self._format_specs)
1100
+
1101
+
1102
+ # Class
1103
+ # ---------------------------------------------------------------------------
1104
+ class BaseClass(GetClassMembersMixin, AutoREPRMixin):
1105
+ """Base class"""
1106
+
1107
+ def __str__(self) -> str:
1108
+ return repr(self)
1039
1109
 
1040
1110
 
1041
1111
  # Metaclass
@@ -3,8 +3,8 @@ Absfuyu: Core
3
3
  -------------
4
4
  Bases for other features (with library)
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
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):
absfuyu/core/decorator.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Core
3
3
  -------------
4
4
  Decorator
5
5
 
6
- Version: 5.6.1
7
- Date updated: 12/09/2025 (dd/mm/yyyy)
6
+ Version: 6.1.2
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package