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.

Files changed (103) hide show
  1. absfuyu/__init__.py +5 -3
  2. absfuyu/__main__.py +3 -3
  3. absfuyu/cli/__init__.py +13 -2
  4. absfuyu/cli/audio_group.py +98 -0
  5. absfuyu/cli/color.py +30 -14
  6. absfuyu/cli/config_group.py +9 -2
  7. absfuyu/cli/do_group.py +23 -6
  8. absfuyu/cli/game_group.py +27 -2
  9. absfuyu/cli/tool_group.py +81 -11
  10. absfuyu/config/__init__.py +3 -3
  11. absfuyu/core/__init__.py +12 -8
  12. absfuyu/core/baseclass.py +929 -96
  13. absfuyu/core/baseclass2.py +44 -3
  14. absfuyu/core/decorator.py +70 -4
  15. absfuyu/core/docstring.py +64 -41
  16. absfuyu/core/dummy_cli.py +3 -3
  17. absfuyu/core/dummy_func.py +19 -6
  18. absfuyu/dxt/__init__.py +2 -2
  19. absfuyu/dxt/base_type.py +93 -0
  20. absfuyu/dxt/dictext.py +204 -16
  21. absfuyu/dxt/dxt_support.py +2 -2
  22. absfuyu/dxt/intext.py +151 -34
  23. absfuyu/dxt/listext.py +969 -127
  24. absfuyu/dxt/strext.py +77 -17
  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 +3 -2
  31. absfuyu/extra/da/__init__.py +72 -0
  32. absfuyu/extra/da/dadf.py +1600 -0
  33. absfuyu/extra/da/dadf_base.py +186 -0
  34. absfuyu/extra/da/df_func.py +181 -0
  35. absfuyu/extra/da/mplt.py +219 -0
  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 +87 -0
  45. absfuyu/extra/rclone.py +253 -0
  46. absfuyu/extra/xml.py +90 -0
  47. absfuyu/fun/__init__.py +7 -20
  48. absfuyu/fun/rubik.py +442 -0
  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 -3
  55. absfuyu/game/wordle.py +6 -4
  56. absfuyu/general/__init__.py +4 -4
  57. absfuyu/general/content.py +4 -4
  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 +28 -2
  72. absfuyu/tools/checksum.py +144 -9
  73. absfuyu/tools/converter.py +120 -34
  74. absfuyu/tools/generator.py +461 -0
  75. absfuyu/tools/inspector.py +752 -0
  76. absfuyu/tools/keygen.py +2 -2
  77. absfuyu/tools/obfuscator.py +47 -9
  78. absfuyu/tools/passwordlib.py +89 -25
  79. absfuyu/tools/shutdownizer.py +3 -8
  80. absfuyu/tools/sw.py +718 -0
  81. absfuyu/tools/web.py +10 -13
  82. absfuyu/typings.py +138 -0
  83. absfuyu/util/__init__.py +114 -6
  84. absfuyu/util/api.py +41 -18
  85. absfuyu/util/cli.py +119 -0
  86. absfuyu/util/gui.py +91 -0
  87. absfuyu/util/json_method.py +43 -14
  88. absfuyu/util/lunar.py +2 -2
  89. absfuyu/util/package.py +124 -0
  90. absfuyu/util/path.py +702 -82
  91. absfuyu/util/performance.py +122 -7
  92. absfuyu/util/shorten_number.py +244 -21
  93. absfuyu/util/text_table.py +481 -0
  94. absfuyu/util/zipped.py +8 -7
  95. absfuyu/version.py +79 -59
  96. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/METADATA +52 -11
  97. absfuyu-6.1.2.dist-info/RECORD +105 -0
  98. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
  99. absfuyu/extra/data_analysis.py +0 -1078
  100. absfuyu/general/generator.py +0 -303
  101. absfuyu-5.0.0.dist-info/RECORD +0 -68
  102. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
  103. {absfuyu-5.0.0.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
absfuyu/core/baseclass.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Core
3
3
  -------------
4
4
  Bases for other features
5
5
 
6
- Version: 5.0.0
7
- Date updated: 12/02/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
10
  # Module Package
@@ -12,15 +12,26 @@ Date updated: 12/02/2025 (dd/mm/yyyy)
12
12
  __all__ = [
13
13
  # Color
14
14
  "CLITextColor",
15
+ # Support
16
+ "ClassMembers",
17
+ "ClassMembersResult",
15
18
  # Mixins
16
- "ShowAllMethodsMixin",
19
+ "GetClassMembersMixin",
17
20
  "AutoREPRMixin",
21
+ "AddFormatMixin",
18
22
  # Class
19
23
  "BaseClass",
24
+ "BaseDataclass",
20
25
  # Metaclass
21
26
  "PositiveInitArgsMeta",
22
27
  ]
23
28
 
29
+ # Library
30
+ # ---------------------------------------------------------------------------
31
+ from collections.abc import Callable
32
+ from dataclasses import dataclass, field
33
+ from typing import Any, ClassVar, Literal, Self
34
+
24
35
 
25
36
  # Color
26
37
  # ---------------------------------------------------------------------------
@@ -39,31 +50,427 @@ class CLITextColor:
39
50
  RESET = "\x1b[39m"
40
51
 
41
52
 
42
- # Mixins
53
+ # Dataclass
43
54
  # ---------------------------------------------------------------------------
44
- class ShowAllMethodsMixin:
55
+ # @versionadded("5.5.0")
56
+ @dataclass
57
+ class BaseDataclass:
45
58
  """
46
- Show all methods of the class and its parent class minus ``object`` class
59
+ Base dataclass.
47
60
 
48
- *This class is meant to be used with other class*
61
+ Contains util methods:
62
+ - _get_fields
63
+ - to_dict
49
64
  """
50
65
 
51
66
  @classmethod
52
- def show_all_methods(
53
- cls,
67
+ def _get_fields(cls) -> tuple[str, ...]:
68
+ """
69
+ Get dataclass's fields.
70
+
71
+ Returns
72
+ -------
73
+ tuple[str, ...]
74
+ Dataclass's fields.
75
+ """
76
+ _fields = getattr(cls, "__dataclass_fields__", ())
77
+ return tuple(_fields)
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
+
95
+
96
+ # Support
97
+ # ---------------------------------------------------------------------------
98
+ # @versionadded("5.5.0")
99
+ @dataclass
100
+ class ClassMembers(BaseDataclass):
101
+ """
102
+ Contains lists of methods, classmethods, staticmethods,
103
+ properties, and attributes of a class.
104
+
105
+ Parameters
106
+ ----------
107
+ methods : list[str], optional
108
+ List contains method names of a class.
109
+ By default inits an empty list.
110
+
111
+ classmethods : list[str], optional
112
+ List contains classmethod names of a class.
113
+ By default inits an empty list.
114
+
115
+ staticmethods : list[str], optional
116
+ List contains staticmethod names of a class.
117
+ By default inits an empty list.
118
+
119
+ properties : list[str], optional
120
+ List contains property names of a class.
121
+ By default inits an empty list.
122
+
123
+ attributes : list[str], optional
124
+ List contains attributes of a class instance.
125
+ By default inits an empty list.
126
+
127
+ classattributes : list[str], optional
128
+ List contains attributes of a class.
129
+ By default inits an empty list.
130
+
131
+
132
+ Available formats
133
+ -----------------
134
+ Alternative format to use with: ``format(<obj>, <format_spec>)``
135
+ - ``s``/``short`` (This hides attributes with empty value)
136
+ """
137
+
138
+ methods: list[str] = field(default_factory=list)
139
+ classmethods: list[str] = field(default_factory=list)
140
+ staticmethods: list[str] = field(default_factory=list)
141
+ properties: list[str] = field(default_factory=list)
142
+ attributes: list[str] = field(default_factory=list)
143
+ classattributes: list[str] = field(default_factory=list)
144
+
145
+ def __format__(self, format_spec: str) -> str:
146
+ """
147
+ Available format:
148
+ - ``s``/``short`` (This hides attributes with empty value)
149
+
150
+ Parameters
151
+ ----------
152
+ format_spec : str
153
+ Format spec
154
+
155
+ Returns
156
+ -------
157
+ str
158
+ Alternative text representation of this class.
159
+ """
160
+
161
+ fmt = format_spec.lower().strip()
162
+ cls_name = self.__class__.__name__
163
+
164
+ if fmt == "short" or fmt == "s":
165
+ out = []
166
+ sep = ", "
167
+ for x in self._get_fields():
168
+ if len(getattr(self, x)) > 0:
169
+ out.append(f"{x}={repr(getattr(self, x))}")
170
+ return f"{cls_name}({sep.join(out)})"
171
+
172
+ return self.__repr__()
173
+
174
+ def is_empty(self) -> bool:
175
+ """
176
+ Checks if all fields are empty.
177
+
178
+
179
+ Example:
180
+ --------
181
+ >>> ClassMembers().is_empty()
182
+ True
183
+
184
+ >>> ClassMembers(["a"]).is_empty()
185
+ False
186
+ """
187
+
188
+ # return all(len(getattr(self, x)) == 0 for x in self._get_fields())
189
+ for x in self._get_fields():
190
+ if len(getattr(self, x)) > 0:
191
+ return False
192
+ return True
193
+
194
+ def pack(
195
+ self,
196
+ include_method: bool = True,
197
+ include_classmethod: bool = True,
198
+ classmethod_indicator: str = "<cls>",
199
+ include_staticmethod: bool = True,
200
+ staticmethod_indicator: str = "<stc>",
201
+ include_attribute: bool = True,
202
+ include_classattribute: bool = True,
203
+ classattribute_indicator: str = "<cls>",
204
+ ) -> Self:
205
+ """
206
+ Combines the following into one list:
207
+ - methods, classmethods, and staticmethods
208
+ - attributes and class attributes
209
+
210
+ Parameters
211
+ ----------
212
+ include_method : bool, optional
213
+ Whether to include methods in the output, by default ``True``
214
+
215
+ include_classmethod : bool, optional
216
+ Whether to include classmethods in the output, by default ``True``
217
+
218
+ classmethod_indicator : str, optional
219
+ A string used to mark classmethod in the output. This string is appended
220
+ to the name of each classmethod to visually differentiate it from regular
221
+ instance methods, by default ``"<cls>"``
222
+
223
+ include_staticmethod : bool, optional
224
+ Whether to include staticmethods in the output, by default ``True``
225
+
226
+ staticmethod_indicator : str, optional
227
+ A string used to mark staticmethod in the output. This string is appended
228
+ to the name of each staticmethod to visually differentiate it from regular
229
+ instance methods, by default ``"<stc>"``
230
+
231
+ include_attribute : bool, optional
232
+ Whether to include attributes in the output, by default ``True``
233
+
234
+ include_classattribute : bool, optional
235
+ Whether to include class attributes in the output, by default ``True``
236
+
237
+ classattribute_indicator : str, optional
238
+ A string used to mark class attribute in the output. This string is appended
239
+ to the name of each class attribute to visually differentiate it from regular
240
+ instance attributes, by default ``"<cls>"``
241
+
242
+ Returns
243
+ -------
244
+ Self
245
+ ClassMembers (combined methods)
246
+
247
+
248
+ Example:
249
+ --------
250
+ >>> ClassMembers(["a"], ["b"], ["c"], ["d"], ["e"], ["f"]).pack().__format__("short")
251
+ ClassMembers(methods=['a', 'b <cls>', 'c <stc>'], properties=['d'], attributes=['e', 'f <cls>'])
252
+ """
253
+
254
+ new_methods_list: list[str] = []
255
+
256
+ # Method
257
+ if include_method:
258
+ new_methods_list.extend(self.methods)
259
+
260
+ # Classmethod
261
+ if include_classmethod:
262
+ new_methods_list.extend(
263
+ [f"{x} {classmethod_indicator}".strip() for x in self.classmethods]
264
+ )
265
+
266
+ # Staticmethod
267
+ if include_staticmethod:
268
+ new_methods_list.extend(
269
+ [f"{x} {staticmethod_indicator}".strip() for x in self.staticmethods]
270
+ )
271
+
272
+ new_attributes_list: list[str] = []
273
+
274
+ # Attribute
275
+ if include_attribute:
276
+ new_attributes_list.extend(self.attributes)
277
+
278
+ # Class attribute
279
+ if include_classattribute:
280
+ new_attributes_list.extend(
281
+ [
282
+ f"{x} {classattribute_indicator}".strip()
283
+ for x in self.classattributes
284
+ ]
285
+ )
286
+
287
+ # return self.__class__(new_methods_list, [], [], self.properties, [])
288
+ return self.__class__(
289
+ methods=new_methods_list,
290
+ properties=self.properties,
291
+ attributes=new_attributes_list,
292
+ )
293
+
294
+ def sort(self, reverse: bool = False) -> Self:
295
+ """
296
+ Sorts every element in each method list.
297
+
298
+ Parameters
299
+ ----------
300
+ reverse : bool, optional
301
+ Descending order, by default ``False``
302
+
303
+ Returns
304
+ -------
305
+ Self
306
+ Self with sorted values.
307
+
308
+
309
+ Example:
310
+ --------
311
+ >>> ClassMembers(["b", "a"], ["d", "c"], ["f", "e"]).sort().__format__("short")
312
+ ClassMembers(methods=['a', 'b'], classmethods=['c', 'd'], staticmethods=['e', 'f'])
313
+
314
+ >>> ClassMembers(["b", "a"], ["d", "c"], ["f", "e"]).pack().sort().__format__("short")
315
+ ClassMembers(methods=['a', 'b', 'c <cls>', 'd <cls>', 'e <stc>', 'f <stc>'])
316
+ """
317
+
318
+ sorted_vals: list[list[str]] = [
319
+ sorted(getattr(self, field), reverse=reverse)
320
+ for field in self._get_fields()
321
+ ]
322
+
323
+ return self.__class__(*sorted_vals)
324
+
325
+
326
+ # @versionadded("5.5.0")
327
+ class ClassMembersResult(dict[str, ClassMembers]):
328
+ """
329
+ All members of a class and its parent classes.
330
+ """
331
+
332
+ _LINELENGTH: ClassVar[int] = 88
333
+
334
+ def __format__(self, format_spec: str) -> str:
335
+ """
336
+ Available format:
337
+ - ``s``/``short`` (This hides attributes with empty value)
338
+
339
+ Parameters
340
+ ----------
341
+ format_spec : str
342
+ Format spec
343
+
344
+ Returns
345
+ -------
346
+ str
347
+ Alternative text representation of this class.
348
+ """
349
+
350
+ fmt = format_spec.lower().strip()
351
+
352
+ if fmt == "short" or fmt == "s":
353
+ # out = {}
354
+ # for name, member in self.items():
355
+ # out[name] = format(member, fmt)
356
+ # return repr(out)
357
+ out = []
358
+ sep = ", "
359
+ for idx, (name, member) in enumerate(self.items()):
360
+ mem = format(member, fmt)
361
+ base = f"{repr(name)}: {mem}"
362
+
363
+ # if idx == 0:
364
+ # out.append("{" + base)
365
+ # elif idx + 1 == len(self.items()):
366
+ # out.append(base + "}")
367
+ # else:
368
+ # out.append(base)
369
+ out.append(
370
+ ("{" if idx == 0 else "")
371
+ + base
372
+ + ("}" if idx + 1 == len(self.items()) else "")
373
+ )
374
+
375
+ return sep.join(out)
376
+
377
+ return self.__repr__()
378
+
379
+ def _merge_value(
380
+ self,
381
+ value_name: Literal[
382
+ "methods",
383
+ "classmethods",
384
+ "staticmethods",
385
+ "properties",
386
+ "attributes",
387
+ "classattributes",
388
+ ],
389
+ ) -> list[str]:
390
+ """
391
+ Merge all specified values from the dictionary.
392
+
393
+ Parameters
394
+ ----------
395
+ value_name : Literal["methods", "classmethods", "staticmethods", "properties", "attributes", "classattributes"]
396
+ The type of value to merge.
397
+
398
+ Returns
399
+ -------
400
+ list[str]
401
+ A list of merged values.
402
+ """
403
+
404
+ merged = []
405
+ for _, member in self.items():
406
+ if value_name in member._get_fields():
407
+ merged.extend(getattr(member, value_name))
408
+ return merged
409
+
410
+ def flatten_value(self, sort: bool = True) -> ClassMembers:
411
+ """
412
+ Merge all attributes of ``dict``'s values into one ``ClassMembers``.
413
+
414
+ Parameters
415
+ ----------
416
+ sort : bool
417
+ Sort value in ascending order after flatten, by default ``True``
418
+
419
+ Returns
420
+ -------
421
+ ClassMembers
422
+ Flattened value.
423
+
424
+
425
+ Example:
426
+ --------
427
+ >>> test = ClassMembersResult(
428
+ ... ABC=ClassMembers(["a"], ["b"], ["c"], ["d"], ["y"], ["x"]),
429
+ ... DEF=ClassMembers(["e"], ["f"], ["g"], ["h"], ["w"], ["z"]),
430
+ ... )
431
+ >>> test.flatten_value()
432
+ ClassMembers(
433
+ methods=["a", "e"],
434
+ classmethods=["b", "f"],
435
+ staticmethods=["c", "g"],
436
+ properties=["d", "h"],
437
+ attributes=["w", "y"],
438
+ classattributes=["x", "z"],
439
+ )
440
+ """
441
+
442
+ res: list[list[str]] = []
443
+ # for x in [
444
+ # "methods",
445
+ # "classmethods",
446
+ # "staticmethods",
447
+ # "properties",
448
+ # "attributes",
449
+ # "classattributes",
450
+ # ]:
451
+ for x in ClassMembers._get_fields():
452
+ res.append(self._merge_value(x)) # type: ignore
453
+ return ClassMembers(*res).sort() if sort else ClassMembers(*res)
454
+
455
+ def pack_value(
456
+ self,
457
+ include_method: bool = True,
54
458
  include_classmethod: bool = True,
55
459
  classmethod_indicator: str = "<classmethod>",
56
460
  include_staticmethod: bool = True,
57
461
  staticmethod_indicator: str = "<staticmethod>",
58
- include_private_method: bool = False,
59
- print_result: bool = False,
60
- ) -> dict[str, list[str]]:
462
+ include_attribute: bool = True,
463
+ include_classattribute: bool = True,
464
+ classattribute_indicator: str = "<cls>",
465
+ ) -> Self:
61
466
  """
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.
467
+ Join members into one list for each value.
64
468
 
65
469
  Parameters
66
470
  ----------
471
+ include_method : bool, optional
472
+ Whether to include method in the output, by default ``True``
473
+
67
474
  include_classmethod : bool, optional
68
475
  Whether to include classmethod in the output, by default ``True``
69
476
 
@@ -80,52 +487,394 @@ class ShowAllMethodsMixin:
80
487
  to the name of each staticmethod to visually differentiate it from regular
81
488
  instance methods, by default ``"<staticmethod>"``
82
489
 
83
- include_private_method : bool, optional
84
- Whether to include private method in the output, by default ``False``
490
+ include_attribute : bool, optional
491
+ Whether to include attributes in the output, by default ``True``
85
492
 
86
- print_result : bool, optional
87
- Beautifully print the output, by default ``False``
493
+ include_classattribute : bool, optional
494
+ Whether to include class attributes in the output, by default ``True``
495
+
496
+ classattribute_indicator : str, optional
497
+ A string used to mark class attribute in the output. This string is appended
498
+ to the name of each class attribute to visually differentiate it from regular
499
+ instance attributes, by default ``"<cls>"``
500
+
501
+ Returns
502
+ -------
503
+ Self
504
+ ClassMembersResult with packed value.
505
+
506
+
507
+ Example:
508
+ --------
509
+ >>> test = ClassMembersResult(
510
+ ... ABC=ClassMembers(["a"], ["b"], ["c"], ["d"], ["y"], ["x"]),
511
+ ... DEF=ClassMembers(["e"], ["f"], ["g"], ["h"], ["w"], ["z"]),
512
+ ... )
513
+ >>> test.pack_value()
514
+ {
515
+ "ABC": ClassMembers(
516
+ methods=["a", "b <classmethod>", "c <staticmethod>"],
517
+ classmethods=[],
518
+ staticmethods=[],
519
+ properties=["d"],
520
+ attributes=["y", "x <cls>"],
521
+ classattributes=[],
522
+ ),
523
+ "DEF": ClassMembers(
524
+ methods=["e", "f <classmethod>", "g <staticmethod>"],
525
+ classmethods=[],
526
+ staticmethods=[],
527
+ properties=["h"],
528
+ attributes=["w", "z <cls>"],
529
+ classattributes=[],
530
+ ),
531
+ }
532
+ """
533
+
534
+ for class_name, members in self.items():
535
+ self[class_name] = members.pack(
536
+ include_method=include_method,
537
+ include_classmethod=include_classmethod,
538
+ classmethod_indicator=classmethod_indicator,
539
+ include_staticmethod=include_staticmethod,
540
+ staticmethod_indicator=staticmethod_indicator,
541
+ include_attribute=include_attribute,
542
+ include_classattribute=include_classattribute,
543
+ classattribute_indicator=classattribute_indicator,
544
+ )
545
+ return self
546
+
547
+ def prioritize_value(
548
+ self,
549
+ value_name: Literal[
550
+ "methods",
551
+ "classmethods",
552
+ "staticmethods",
553
+ "properties",
554
+ "attributes",
555
+ "classattributes",
556
+ ] = "methods",
557
+ ) -> dict[str, list[str]]:
558
+ """
559
+ Prioritize which field of value to show.
560
+
561
+ Parameters
562
+ ----------
563
+ value_name : Literal["methods", "classmethods", "staticmethods", "properties", "attributes", "classattributes"], optional
564
+ The type of value to prioritize, by default ``"methods"``
88
565
 
89
566
  Returns
90
567
  -------
91
568
  dict[str, list[str]]
92
- A dictionary where keys are class names and values are lists of method names.
569
+ A dictionary with prioritized values.
570
+
571
+
572
+ Example:
573
+ --------
574
+ >>> test = ClassMembersResult(
575
+ ... ABC=ClassMembers(["a"], ["b"], ["c"], ["d"]),
576
+ ... DEF=ClassMembers(["e"], ["f"], ["g"], ["h"]),
577
+ ... )
578
+ >>> test.prioritize_value("methods")
579
+ {'ABC': ['a'], 'DEF': ['e']}
580
+ >>> test.prioritize_value("classmethods")
581
+ {'ABC': ['b'], 'DEF': ['f']}
582
+ >>> test.prioritize_value("staticmethods")
583
+ {'ABC': ['c'], 'DEF': ['g']}
584
+ >>> test.prioritize_value("properties")
585
+ {'ABC': ['d'], 'DEF': ['h']}
586
+ """
587
+
588
+ result: dict[str, list[str]] = {}
589
+ for name, member in self.items():
590
+ result[name] = getattr(member, value_name, member.methods)
591
+ return result
592
+
593
+ def print_output(
594
+ self,
595
+ where_to_print: Literal["methods", "properties", "attributes"] = "methods",
596
+ print_in_one_column: bool = False,
597
+ ) -> None:
598
+ """
599
+ Beautifully print the result.
600
+
601
+ *This method is deprecated.*
602
+
603
+ Parameters
604
+ ----------
605
+ where_to_print : Literal["methods", "properties", "attributes"], optional
606
+ Whether to print ``methods`` or ``properties``, by default ``"methods"``
607
+
608
+ print_in_one_column : bool, optional
609
+ Whether to print in one column, by default ``False``
610
+ """
611
+
612
+ print_func = print # Can be extended with function parameter
613
+
614
+ # Loop through each class base
615
+ for order, (class_base, member) in enumerate(self.items(), start=1):
616
+ methods: list[str] = getattr(member, where_to_print, member.methods)
617
+ mlen = len(methods) # How many methods in that class
618
+ if mlen == 0:
619
+ continue
620
+ print_func(f"{order:02}. <{class_base}> | len: {mlen:02}")
621
+
622
+ # Modify methods list
623
+ max_method_name_len = max([len(x) for x in methods])
624
+ if mlen % 2 == 0:
625
+ p1, p2 = methods[: int(mlen / 2)], methods[int(mlen / 2) :]
626
+ else:
627
+ p1, p2 = methods[: int(mlen / 2) + 1], methods[int(mlen / 2) + 1 :]
628
+ p2.append("")
629
+ new_methods = list(zip(p1, p2))
630
+
631
+ # print
632
+ if print_in_one_column:
633
+ # This print 1 method in one line
634
+ for name in methods:
635
+ print(f" - {name.ljust(max_method_name_len)}")
636
+ else:
637
+ # This print 2 methods in 1 line
638
+ for x1, x2 in new_methods:
639
+ if x2 == "":
640
+ print_func(f" - {x1.ljust(max_method_name_len)}")
641
+ else:
642
+ print_func(
643
+ f" - {x1.ljust(max_method_name_len)} - {x2.ljust(max_method_name_len)}"
644
+ )
645
+
646
+ print_func("".ljust(self._LINELENGTH, "-"))
647
+
648
+
649
+ # Mixins
650
+ # ---------------------------------------------------------------------------
651
+ class GetClassMembersMixin:
652
+ """
653
+ Show all methods of the class and its parent class minus ``object`` class
654
+
655
+ *This class is meant to be used with other class*
656
+
657
+
658
+ Example:
659
+ --------
660
+ >>> class TestClass(GetClassMembersMixin):
661
+ ... def method1(self): ...
662
+ >>> TestClass._get_members(dunder=False)
663
+ {
664
+ 'GetClassMembersMixin': ClassMembers(
665
+ methods=[],
666
+ classmethods=['_get_members', ..., 'show_all_methods', 'show_all_properties'],
667
+ staticmethods=[],
668
+ properties=[],
669
+ attributes=[],
670
+ classattributes=[]
671
+ ),
672
+ 'TestClass': ClassMembers(
673
+ methods=['method1'],
674
+ classmethods=[],
675
+ staticmethods=[],
676
+ properties=[],
677
+ attributes=[],
678
+ classattributes=[]
679
+ )
680
+ }
681
+ """
682
+
683
+ # @versionadded("5.5.0")
684
+ @classmethod
685
+ def _get_members(
686
+ cls,
687
+ dunder: bool = True,
688
+ underscore: bool = True,
689
+ private: bool = True,
690
+ ) -> ClassMembersResult:
691
+ """
692
+ Class method to get all methods, properties, and attributes
693
+ of the class and its parent classes.
694
+
695
+ Parameters
696
+ ----------
697
+ dunder : bool, optional
698
+ Whether to include attribute with ``__`` (dunder)
699
+ in the output, by default ``True``
700
+
701
+ underscore : bool, optional
702
+ Whether to include attribute starts with ``_``
703
+ in the output (will also skip dunder), by default ``True``
704
+
705
+ private : bool, optional
706
+ Whether to include private attribute
707
+ (``_<__class__.__name__>__<attribute>``)
708
+ in the output, by default ``True``
709
+
710
+ Returns
711
+ -------
712
+ ClassMembersResult
713
+ A dictionary where keys are class names
714
+ and values are tuples of method names and properties.
93
715
  """
94
- classes = cls.__mro__[::-1][1:] # MRO in reverse order
95
- result = {}
716
+
717
+ # MRO in reverse order
718
+ classes = cls.__mro__[::-1]
719
+ result: dict[str, ClassMembers] = {}
720
+
721
+ # For each class base in classes
96
722
  for base in classes:
97
- methods = []
723
+ methods: list[str] = []
724
+ classmethods: list[str] = []
725
+ staticmethods: list[str] = []
726
+ properties: list[str] = []
727
+ classattributes: list[str] = []
728
+
729
+ # Dict items of base
98
730
  for name, attr in base.__dict__.items():
731
+ # Skip dunder
732
+ if name.startswith("__") and not dunder:
733
+ continue
734
+
99
735
  # Skip private attribute
100
- if name.startswith("__"):
736
+ if base.__name__ in name and not private:
101
737
  continue
102
738
 
103
- # Skip private Callable
104
- if base.__name__ in name and not include_private_method:
739
+ # Skip underscore
740
+ if (
741
+ name.startswith("_")
742
+ and not underscore
743
+ and base.__name__ not in name
744
+ ):
105
745
  continue
106
746
 
107
- # Function
747
+ # Methods
108
748
  if callable(attr):
109
749
  if isinstance(attr, staticmethod):
110
- if include_staticmethod:
111
- methods.append(f"{name} {staticmethod_indicator}")
750
+ staticmethods.append(name)
112
751
  else:
113
752
  methods.append(name)
114
- if isinstance(attr, classmethod) and include_classmethod:
115
- methods.append(f"{name} {classmethod_indicator}")
753
+ elif isinstance(attr, classmethod):
754
+ classmethods.append(name)
116
755
 
117
- if methods:
118
- result[base.__name__] = sorted(methods)
756
+ # Property
757
+ elif isinstance(attr, property):
758
+ properties.append(name)
119
759
 
120
- if print_result:
121
- cls.__print_show_all_result(result)
760
+ # Class attribute
761
+ else:
762
+ classattributes.append(name)
122
763
 
123
- return result
764
+ # Save to result
765
+ result[base.__name__] = ClassMembers(
766
+ methods=methods,
767
+ classmethods=classmethods,
768
+ staticmethods=staticmethods,
769
+ properties=properties,
770
+ classattributes=classattributes,
771
+ ).sort()
772
+
773
+ return ClassMembersResult(result)
774
+
775
+ # @versionadded("5.5.0")
776
+ def _get_attributes(
777
+ self,
778
+ underscore: bool = True,
779
+ private: bool = True,
780
+ ) -> list[str]:
781
+ """
782
+ Get all attributes of the class instance.
783
+
784
+ Parameters
785
+ ----------
786
+ underscore : bool, optional
787
+ Whether to include attribute starts with ``_``
788
+ in the output, by default ``True``
789
+
790
+ private : bool, optional
791
+ Whether to include private attribute
792
+ (``_<__class__.__name__>__<attribute>``)
793
+ in the output, by default ``True``
794
+
795
+ Returns
796
+ -------
797
+ list[str]
798
+ A list contains attributes.
799
+ """
800
+
801
+ # Default output
802
+ out: list[str] = []
803
+
804
+ # Get attributes
805
+ cls_dict: dict[str, Any] | None = getattr(self, "__dict__", None)
806
+ cls_slots: tuple[str, ...] | None = getattr(self, "__slots__", None)
807
+ cls_name: str = self.__class__.__name__
808
+
809
+ def _is_valid(item_name: str) -> bool:
810
+ if not item_name.startswith("_"):
811
+ return True
812
+ if cls_name in item_name and private:
813
+ return True
814
+ if underscore and cls_name not in item_name:
815
+ return True
816
+ return False
817
+
818
+ # Check if __dict__ exist and len(__dict__) > 0
819
+ if cls_dict is not None and len(cls_dict) > 0:
820
+ # out = [x for x in self.__dict__ if _is_valid(x)]
821
+ out.extend(list(cls_dict))
822
+
823
+ # Check if __slots__ exist and len(__slots__) > 0
824
+ if cls_slots is not None and len(cls_slots) > 0:
825
+ # Convert __<attribute> to _<self.__class__.__name__>__<attribute>
826
+ _slot = [f"_{cls_name}{x}" if x.startswith("__") else x for x in cls_slots]
827
+ out.extend(_slot)
828
+
829
+ return [x for x in out if _is_valid(x)]
830
+
831
+ # @versionadded("5.5.0")
832
+ def get_members(
833
+ self,
834
+ dunder: bool = False,
835
+ underscore: bool = True,
836
+ private: bool = True,
837
+ ) -> ClassMembersResult:
838
+ """
839
+ Get all members of a class instance.
840
+
841
+ Parameters
842
+ ----------
843
+ dunder : bool, optional
844
+ Whether to include attribute with ``__`` (dunder)
845
+ in the output, by default ``False``
846
+
847
+ underscore : bool, optional
848
+ Whether to include attribute starts with ``_``
849
+ in the output (will also skip dunder), by default ``True``
850
+
851
+ private : bool, optional
852
+ Whether to include private attribute
853
+ (``_<__class__.__name__>__<attribute>``)
854
+ in the output, by default ``True``
855
+
856
+ Returns
857
+ -------
858
+ ClassMembersResult
859
+ All member of a class instance.
860
+ """
861
+ mems = self._get_members(dunder=dunder, underscore=underscore, private=private)
862
+ attrs = self._get_attributes(underscore=underscore, private=private)
863
+ mems[self.__class__.__name__].attributes = attrs
864
+ return mems
124
865
 
125
866
  @classmethod
126
- def show_all_properties(cls, print_result: bool = False) -> dict[str, list[str]]:
867
+ def show_all_methods(
868
+ cls,
869
+ print_result: bool = False,
870
+ include_classmethod: bool = True,
871
+ classmethod_indicator: str = "<classmethod>",
872
+ include_staticmethod: bool = True,
873
+ staticmethod_indicator: str = "<staticmethod>",
874
+ include_private_method: bool = False,
875
+ ) -> dict[str, list[str]]:
127
876
  """
128
- Class method to display all properties of the class and its parent classes,
877
+ Class method to display all methods of the class and its parent classes,
129
878
  including the class in which they are defined in alphabetical order.
130
879
 
131
880
  Parameters
@@ -133,71 +882,77 @@ class ShowAllMethodsMixin:
133
882
  print_result : bool, optional
134
883
  Beautifully print the output, by default ``False``
135
884
 
885
+ include_classmethod : bool, optional
886
+ Whether to include classmethod in the output, by default ``True``
887
+
888
+ classmethod_indicator : str, optional
889
+ A string used to mark classmethod in the output. This string is appended
890
+ to the name of each classmethod to visually differentiate it from regular
891
+ instance methods, by default ``"<classmethod>"``
892
+
893
+ include_staticmethod : bool, optional
894
+ Whether to include staticmethod in the output, by default ``True``
895
+
896
+ staticmethod_indicator : str, optional
897
+ A string used to mark staticmethod in the output. This string is appended
898
+ to the name of each staticmethod to visually differentiate it from regular
899
+ instance methods, by default ``"<staticmethod>"``
900
+
901
+ include_private_method : bool, optional
902
+ Whether to include private method in the output, by default ``False``
903
+
136
904
  Returns
137
905
  -------
138
906
  dict[str, list[str]]
139
- A dictionary where keys are class names and values are lists of property names.
907
+ A dictionary where keys are class names and values are lists of method names.
140
908
  """
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
909
 
150
- if isinstance(attr, property):
151
- properties.append(name)
152
-
153
- if properties:
154
- result[base.__name__] = sorted(properties)
910
+ result = cls._get_members(
911
+ dunder=False, private=include_private_method
912
+ ).pack_value(
913
+ include_classmethod=include_classmethod,
914
+ classmethod_indicator=classmethod_indicator,
915
+ include_staticmethod=include_staticmethod,
916
+ staticmethod_indicator=staticmethod_indicator,
917
+ )
155
918
 
156
919
  if print_result:
157
- cls.__print_show_all_result(result)
920
+ result.print_output("methods")
158
921
 
159
- return result
922
+ return result.prioritize_value("methods")
160
923
 
161
- @staticmethod
162
- def __print_show_all_result(result: dict[str, list[str]]) -> None:
924
+ @classmethod
925
+ def show_all_properties(cls, print_result: bool = False) -> dict[str, list[str]]:
163
926
  """
164
- Pretty print the result of ``ShowAllMethodsMixin.show_all_methods()``
927
+ Class method to display all properties of the class and its parent classes,
928
+ including the class in which they are defined in alphabetical order.
165
929
 
166
930
  Parameters
167
931
  ----------
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}")
932
+ print_result : bool, optional
933
+ Beautifully print the output, by default ``False``
177
934
 
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))
935
+ Returns
936
+ -------
937
+ dict[str, list[str]]
938
+ A dictionary where keys are class names and values are lists of property names.
939
+ """
186
940
 
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
- )
941
+ # result = cls.get_methods_and_properties().prioritize_value("properties")
942
+ result = ClassMembersResult(
943
+ {
944
+ cls.__name__: ClassMembers(
945
+ properties=cls._get_members(dunder=False)
946
+ .flatten_value()
947
+ .properties,
948
+ )
949
+ }
950
+ )
195
951
 
196
- # This print 1 method in one line
197
- # for name in methods:
198
- # print(f" - {name.ljust(max_method_name_len)}")
952
+ if print_result:
953
+ result.print_output("properties")
199
954
 
200
- print_func("".ljust(88, "-"))
955
+ return result.prioritize_value("properties")
201
956
 
202
957
 
203
958
  class AutoREPRMixin:
@@ -257,19 +1012,38 @@ class AutoREPRMixin:
257
1012
  return f"{self.__class__.__name__}({sep.join(out)})"
258
1013
 
259
1014
 
260
- # Class
261
- # ---------------------------------------------------------------------------
262
- class BaseClass(ShowAllMethodsMixin, AutoREPRMixin):
263
- """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.
264
1020
 
265
- def __str__(self) -> str:
266
- 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]] = {}
267
1031
 
268
1032
  def __format__(self, format_spec: str) -> str:
269
1033
  """
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)
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
+
273
1047
 
274
1048
  Usage
275
1049
  -----
@@ -278,13 +1052,72 @@ class BaseClass(ShowAllMethodsMixin, AutoREPRMixin):
278
1052
  >>> print(format(<object>, <format_spec>))
279
1053
  """
280
1054
 
281
- 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)
282
1109
 
283
1110
 
284
1111
  # Metaclass
285
1112
  # ---------------------------------------------------------------------------
286
1113
  class PositiveInitArgsMeta(type):
287
- """Make sure that every args in a class __init__ is positive"""
1114
+ """
1115
+ Make sure that every args in a class __init__ is positive
1116
+
1117
+ Usage
1118
+ -----
1119
+ >>> class Test(metaclass=PositiveInitArgsMeta): pass
1120
+ """
288
1121
 
289
1122
  def __call__(cls, *args, **kwargs):
290
1123
  # Check if all positional and keyword arguments are positive