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/dxt/dictext.py CHANGED
@@ -3,10 +3,12 @@ Absfuyu: Data Extension
3
3
  -----------------------
4
4
  dict extension
5
5
 
6
- Version: 5.0.0
7
- Date updated: 14/02/2025 (dd/mm/yyyy)
6
+ Version: 6.1.1
7
+ Date updated: 30/12/2025 (dd/mm/yyyy)
8
8
  """
9
9
 
10
+ from __future__ import annotations
11
+
10
12
  # Module Package
11
13
  # ---------------------------------------------------------------------------
12
14
  __all__ = ["DictExt", "DictAnalyzeResult"]
@@ -16,9 +18,20 @@ __all__ = ["DictExt", "DictAnalyzeResult"]
16
18
  # ---------------------------------------------------------------------------
17
19
  import operator
18
20
  from collections.abc import Callable
19
- from typing import Any, NamedTuple, Self
21
+ from typing import Any, Literal, NamedTuple, Self, TypeVar, overload
22
+
23
+ from absfuyu.core import GetClassMembersMixin, versionadded, versionchanged
24
+
25
+ # from absfuyu.typings import KT as _KT
26
+ # from absfuyu.typings import VT as _VT
20
27
 
21
- from absfuyu.core import ShowAllMethodsMixin, versionadded, versionchanged
28
+
29
+ # Type
30
+ # ---------------------------------------------------------------------------
31
+ KT = TypeVar("KT")
32
+ VT = TypeVar("VT")
33
+ VT2 = TypeVar("VT2")
34
+ R = TypeVar("R") # Return type - Can be anything
22
35
 
23
36
 
24
37
  # Class
@@ -34,9 +47,12 @@ class DictAnalyzeResult(NamedTuple):
34
47
  min_list: list
35
48
 
36
49
 
37
- class DictExt(ShowAllMethodsMixin, dict):
50
+ class DictExtLegacy(GetClassMembersMixin, dict[KT, VT]):
38
51
  """
39
- ``dict`` extension
52
+ ``dict`` extension (with no generic type-hints)
53
+
54
+ >>> # For a list of new methods
55
+ >>> DictExt.show_all_methods()
40
56
  """
41
57
 
42
58
  @versionchanged("3.3.0", reason="Updated return type")
@@ -53,13 +69,7 @@ class DictExt(ShowAllMethodsMixin, dict):
53
69
 
54
70
  Example:
55
71
  --------
56
- >>> test = DictExt({
57
- ... "abc": 9,
58
- ... "def": 9,
59
- ... "ghi": 8,
60
- ... "jkl": 1,
61
- ... "mno": 1
62
- ... })
72
+ >>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
63
73
  >>> test.analyze()
64
74
  DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
65
75
  """
@@ -101,9 +111,9 @@ class DictExt(ShowAllMethodsMixin, dict):
101
111
  {9: 'abc'}
102
112
  """
103
113
  # return self.__class__(zip(self.values(), self.keys()))
104
- return self.__class__({v: k for k, v in self.items()})
114
+ return self.__class__({v: k for k, v in self.items()}) # type: ignore
105
115
 
106
- def apply(self, func: Callable, apply_to_value: bool = True) -> Self:
116
+ def apply(self, func: Callable[[Any], Any], apply_to_value: bool = True) -> Self:
107
117
  """
108
118
  Apply function to ``DictExt.keys()`` or ``DictExt.values()``
109
119
 
@@ -138,7 +148,7 @@ class DictExt(ShowAllMethodsMixin, dict):
138
148
  @versionadded("3.4.0")
139
149
  def aggregate(
140
150
  self,
141
- other_dict: dict,
151
+ other_dict: dict[KT, VT],
142
152
  default_value: Any = 0,
143
153
  operator_func: Callable[[Any, Any], Any] = operator.add, # operator add
144
154
  ) -> Self:
@@ -199,3 +209,181 @@ class DictExt(ShowAllMethodsMixin, dict):
199
209
  merged_output[k] = [value_self, value_other]
200
210
 
201
211
  return self.__class__(merged_output)
212
+
213
+
214
+ # Type-hints are hard coded, no work around Self[KT, VT] yet
215
+ class DictExt(GetClassMembersMixin, dict[KT, VT]):
216
+ """
217
+ ``dict`` extension
218
+
219
+ >>> # For a list of new methods
220
+ >>> DictExt.show_all_methods()
221
+ """
222
+
223
+ # Analyze
224
+ @versionchanged("3.3.0", reason="Updated return type")
225
+ def analyze(self) -> DictAnalyzeResult:
226
+ """
227
+ Analyze all the key values (``int``, ``float``)
228
+ in ``dict`` then return highest/lowest index
229
+
230
+ Returns
231
+ -------
232
+ dict
233
+ Analyzed data
234
+
235
+
236
+ Example:
237
+ --------
238
+ >>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
239
+ >>> test.analyze()
240
+ DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
241
+ """
242
+ try:
243
+ dct: dict = self.copy()
244
+
245
+ max_val: int | float = max(list(dct.values()))
246
+ min_val: int | float = min(list(dct.values()))
247
+ max_list = []
248
+ min_list = []
249
+
250
+ for k, v in dct.items():
251
+ if v == max_val:
252
+ max_list.append((k, v))
253
+ if v == min_val:
254
+ min_list.append((k, v))
255
+
256
+ return DictAnalyzeResult(max_val, min_val, max_list, min_list)
257
+
258
+ except TypeError:
259
+ err_msg = "Value must be int or float"
260
+ # logger.error(err_msg)
261
+ raise ValueError(err_msg) # noqa: B904
262
+
263
+ # Swap
264
+ def swap_items(self) -> DictExt[VT, KT]:
265
+ """
266
+ Swap ``dict.keys()`` with ``dict.values()``
267
+
268
+ Returns
269
+ -------
270
+ DictExt
271
+ Swapped dict
272
+
273
+
274
+ Example:
275
+ --------
276
+ >>> test = DictExt({"abc": 9})
277
+ >>> test.swap_items()
278
+ {9: 'abc'}
279
+ """
280
+ # return self.__class__(zip(self.values(), self.keys()))
281
+ out: dict[VT, KT] = {v: k for k, v in self.items()}
282
+ # return self.__class__({v: k for k, v in self.items()}) # type: ignore
283
+ return self.__class__(out) # type: ignore
284
+
285
+ # Apply
286
+ @overload
287
+ def apply(self, func: Callable[[VT], R]) -> DictExt[KT, R]: ...
288
+
289
+ @overload
290
+ def apply(self, func: Callable[[KT], R], apply_to_value: Literal[False] = ...) -> DictExt[R, VT]: ...
291
+
292
+ def apply(self, func: Callable[[KT | VT], R], apply_to_value: bool = True) -> DictExt[KT | R, VT | R]: # type: ignore
293
+ """
294
+ Apply function to ``DictExt.keys()`` or ``DictExt.values()``
295
+
296
+ Parameters
297
+ ----------
298
+ func : Callable
299
+ Callable function
300
+
301
+ apply_to_value : bool
302
+ | ``True``: Apply ``func`` to ``DictExt.values()``
303
+ | ``False``: Apply ``func`` to ``DictExt.keys()``
304
+
305
+ Returns
306
+ -------
307
+ DictExt
308
+ DictExt
309
+
310
+
311
+ Example:
312
+ --------
313
+ >>> test = DictExt({"abc": 9})
314
+ >>> test.apply(str)
315
+ {'abc': '9'}
316
+ """
317
+ if apply_to_value:
318
+ new_dict_v: dict[KT, R] = {k: func(v) for k, v in self.items()}
319
+ return self.__class__(new_dict_v) # type: ignore
320
+ else:
321
+ new_dict_k: dict[R, VT] = {func(k): v for k, v in self.items()}
322
+ return self.__class__(new_dict_k) # type: ignore
323
+
324
+ # Aggregate
325
+ @versionchanged("5.0.0", reason="Updated to handle more types and operator")
326
+ @versionadded("3.4.0")
327
+ def aggregate(
328
+ self,
329
+ other_dict: dict[KT, VT | VT2],
330
+ default_value: Any = 0,
331
+ operator_func: Callable[[Any, Any], R] = operator.add, # operator add
332
+ ):
333
+ """
334
+ Aggregates the values of the current dictionary with another dictionary.
335
+
336
+ For each unique key, this method applies the specified operator to the values
337
+ from both dictionaries. If a key exists in only one dictionary, its value is used.
338
+ If an error occurs during aggregation (e.g., incompatible types), the values
339
+ from both dictionaries are returned as a list.
340
+
341
+ Parameters
342
+ ----------
343
+ other_dict : dict
344
+ The dictionary to aggregate with.
345
+
346
+ default_value : Any, optional
347
+ The value to use for missing keys, by default ``0``
348
+
349
+
350
+ operator_func : Callable[[Any, Any], Any], optional
351
+ A function that takes two arguments and returns a single value,
352
+ by default ``operator.add``
353
+
354
+ Returns
355
+ -------
356
+ Self
357
+ A new instance of the aggregated dictionary.
358
+
359
+
360
+ Example:
361
+ --------
362
+ >>> test = DictExt({"test": 5, "test2": 9})
363
+ >>> agg = {"test1": 10, "test2": 1}
364
+ >>> print(test.aggregate(agg))
365
+ {'test1': 10, 'test': 5, 'test2': 10}
366
+
367
+ >>> test = DictExt({"test": 5, "test2": 9})
368
+ >>> agg = {"test1": 10, "test2": "1"}
369
+ >>> print(test.aggregate(agg))
370
+ {'test1': 10, 'test': 5, 'test2': [9, '1']}
371
+ """
372
+ merged_output: dict[KT, VT | R | list[VT | VT2]] = {}
373
+
374
+ # Create a set of all unique keys from both dictionaries
375
+ all_keys = set(self) | set(other_dict)
376
+
377
+ for k in all_keys:
378
+ # Retrieve values with default fallback
379
+ value_self = self.get(k, default_value)
380
+ value_other = other_dict.get(k, default_value)
381
+
382
+ try:
383
+ # Attempt to apply the operator for existing keys
384
+ merged_output[k] = operator_func(value_self, value_other)
385
+ except TypeError:
386
+ # If a TypeError occurs (e.g., if values are not compatible), store values as a list
387
+ merged_output[k] = [value_self, value_other] # type: ignore
388
+
389
+ return self.__class__(merged_output) # type: ignore
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
3
3
  -----------------------
4
4
  Support classes
5
5
 
6
- Version: 5.0.0
7
- Date updated: 11/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
absfuyu/dxt/intext.py CHANGED
@@ -3,8 +3,8 @@ Absfuyu: Data Extension
3
3
  -----------------------
4
4
  int extension
5
5
 
6
- Version: 5.0.0
7
- Date updated: 11/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
@@ -15,10 +15,12 @@ __all__ = ["IntExt", "Pow"]
15
15
  # Library
16
16
  # ---------------------------------------------------------------------------
17
17
  import math
18
+ from abc import ABC
18
19
  from collections import Counter
19
- from typing import Any, Self
20
+ from typing import Any, Literal, Self, overload, override
20
21
 
21
- from absfuyu.core import ShowAllMethodsMixin, versionchanged
22
+ from absfuyu.core.baseclass import GetClassMembersMixin
23
+ from absfuyu.core.docstring import deprecated, versionadded, versionchanged
22
24
  from absfuyu.dxt.dxt_support import DictBoolTrue
23
25
 
24
26
 
@@ -32,20 +34,23 @@ class Pow:
32
34
  self.power_by = power_by
33
35
 
34
36
  def __str__(self) -> str:
37
+ return self.__repr__()
38
+
39
+ def __repr__(self) -> str:
35
40
  if self.power_by == 1:
36
41
  return str(self.number)
37
42
  else:
38
43
  return f"{self.number}^{self.power_by}"
39
44
  # return f"{self.__class__.__name__}({self.number}, {self.power_by})"
40
45
 
41
- def __repr__(self) -> str:
42
- return self.__str__()
43
-
44
46
  def to_list(self) -> list[int]:
45
47
  """
46
48
  Convert into list
47
49
 
48
- :rtype: list[int | float]
50
+ Returns
51
+ -------
52
+ list[int | float]
53
+ list
49
54
  """
50
55
  return [self.number] * self.power_by # type: ignore
51
56
 
@@ -53,18 +58,25 @@ class Pow:
53
58
  """
54
59
  Calculate the ``self.number`` to the power of ``self.power_by``
55
60
 
56
- :rtype: float
61
+ Returns
62
+ -------
63
+ float
64
+ Result
57
65
  """
58
66
  # return self.number**self.power_by
59
67
  return math.pow(self.number, self.power_by)
60
68
 
61
69
 
62
- class IntExt(ShowAllMethodsMixin, int):
70
+ class IntExt(GetClassMembersMixin, int):
63
71
  """
64
72
  ``int`` extension
73
+
74
+ >>> # For a list of new methods
75
+ >>> IntExt.show_all_methods()
65
76
  """
66
77
 
67
- # convert stuff
78
+ # convert
79
+ @deprecated("5.4.0", reason="Use format(...) instead")
68
80
  def to_binary(self) -> str:
69
81
  """
70
82
  Convert to binary number
@@ -77,7 +89,7 @@ class IntExt(ShowAllMethodsMixin, int):
77
89
 
78
90
  Example:
79
91
  --------
80
- >>> test = IntNumber(10)
92
+ >>> test = IntExt(10)
81
93
  >>> test.to_binary()
82
94
  '1010'
83
95
  """
@@ -95,7 +107,7 @@ class IntExt(ShowAllMethodsMixin, int):
95
107
 
96
108
  Example:
97
109
  --------
98
- >>> test = IntNumber(10)
110
+ >>> test = IntExt(10)
99
111
  >>> test.to_celcius_degree()
100
112
  -12.222222222222221
101
113
  """
@@ -114,7 +126,7 @@ class IntExt(ShowAllMethodsMixin, int):
114
126
 
115
127
  Example:
116
128
  --------
117
- >>> test = IntNumber(10)
129
+ >>> test = IntExt(10)
118
130
  >>> test.to_fahrenheit_degree()
119
131
  50.0
120
132
  """
@@ -127,13 +139,13 @@ class IntExt(ShowAllMethodsMixin, int):
127
139
 
128
140
  Returns
129
141
  -------
130
- IntNumber
131
- Reversed number
142
+ Self
143
+ Reversed number.
132
144
 
133
145
 
134
146
  Example:
135
147
  --------
136
- >>> test = IntNumber(102)
148
+ >>> test = IntExt(102)
137
149
  >>> test.reverse()
138
150
  201
139
151
  """
@@ -381,13 +393,13 @@ class IntExt(ShowAllMethodsMixin, int):
381
393
 
382
394
  Returns
383
395
  -------
384
- IntNumber
385
- Least common multiple
396
+ Self
397
+ Least common multiple.
386
398
 
387
399
 
388
400
  Example:
389
401
  --------
390
- >>> test = IntNumber(102)
402
+ >>> test = IntExt(102)
391
403
  >>> test.lcm(5)
392
404
  510
393
405
  """
@@ -405,13 +417,13 @@ class IntExt(ShowAllMethodsMixin, int):
405
417
 
406
418
  Returns
407
419
  -------
408
- IntNumber
409
- Greatest common divisor
420
+ Self
421
+ Greatest common divisor.
410
422
 
411
423
 
412
424
  Example:
413
425
  --------
414
- >>> test = IntNumber(1024)
426
+ >>> test = IntExt(1024)
415
427
  >>> test.gcd(8)
416
428
  8
417
429
  """
@@ -430,28 +442,31 @@ class IntExt(ShowAllMethodsMixin, int):
430
442
 
431
443
  Returns
432
444
  -------
433
- IntNumber
434
- IntNumber
445
+ Self
446
+ IntExt
435
447
 
436
448
 
437
449
  Example:
438
450
  --------
439
- >>> test = IntNumber(119)
451
+ >>> test = IntExt(119)
440
452
  >>> test.add_to_one_digit()
441
453
  2
442
454
 
443
- >>> test = IntNumber(119)
455
+ >>> test = IntExt(119)
444
456
  >>> test.add_to_one_digit(master_number=True)
445
457
  11
446
458
  """
459
+
447
460
  number = int(self)
448
- if number < 0:
461
+ if number < 0: # Convert positive
449
462
  number *= -1
463
+
450
464
  while len(str(number)) != 1:
451
465
  number = sum(map(int, str(number)))
452
466
  if master_number:
453
467
  if number == 22 or number == 11:
454
468
  break # Master number
469
+
455
470
  return self.__class__(number)
456
471
 
457
472
  @versionchanged("5.0.0", reason="Removed ``short_form`` parameter")
@@ -467,7 +482,7 @@ class IntExt(ShowAllMethodsMixin, int):
467
482
 
468
483
  Example:
469
484
  --------
470
- >>> test = IntNumber(1024)
485
+ >>> test = IntExt(1024)
471
486
  >>> test.divisible_list()
472
487
  [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]
473
488
  """
@@ -478,6 +493,12 @@ class IntExt(ShowAllMethodsMixin, int):
478
493
 
479
494
  return divi_list
480
495
 
496
+ @overload
497
+ def prime_factor(self) -> list[Pow]: ... # type: ignore
498
+
499
+ @overload
500
+ def prime_factor(self, short_form: Literal[False] = ...) -> list[int]: ...
501
+
481
502
  def prime_factor(self, short_form: bool = True) -> list[int] | list[Pow]:
482
503
  """
483
504
  Prime factor
@@ -500,14 +521,15 @@ class IntExt(ShowAllMethodsMixin, int):
500
521
 
501
522
  Example:
502
523
  --------
503
- >>> test = IntNumber(1024)
524
+ >>> test = IntExt(1024)
504
525
  >>> test.prime_factor()
505
526
  [2^10]
506
527
 
507
- >>> test = IntNumber(1024)
528
+ >>> test = IntExt(1024)
508
529
  >>> test.prime_factor(short_form=False)
509
530
  [2, 2, 2, 2, 2, 2, 2, 2, 2, 2]
510
531
  """
532
+
511
533
  # Generate list
512
534
  factors = []
513
535
  divisor = 2
@@ -530,7 +552,7 @@ class IntExt(ShowAllMethodsMixin, int):
530
552
  # analyze
531
553
  def analyze(self, short_form: bool = True) -> dict[str, dict[str, Any]]:
532
554
  """
533
- Analyze the number with almost all ``IntNumber`` method
555
+ Analyze the number with almost all ``IntExt`` method
534
556
 
535
557
  Parameters
536
558
  ----------
@@ -546,7 +568,7 @@ class IntExt(ShowAllMethodsMixin, int):
546
568
 
547
569
  Example:
548
570
  --------
549
- >>> test = IntNumber(1024)
571
+ >>> test = IntExt(1024)
550
572
  >>> test.analyze()
551
573
  {
552
574
  'summary': {'number': 1024, 'length': 4, 'even': True, 'prime factor': [2^10], 'divisible': [1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024]},
@@ -559,7 +581,7 @@ class IntExt(ShowAllMethodsMixin, int):
559
581
  "number": self,
560
582
  "length": len(str(self)),
561
583
  "even": self.is_even(),
562
- "prime factor": self.prime_factor(short_form=short_form),
584
+ "prime factor": self.prime_factor(short_form=short_form), # type: ignore[call-overload]
563
585
  "divisible": self.divisible_list(),
564
586
  },
565
587
  "convert": {
@@ -584,3 +606,98 @@ class IntExt(ShowAllMethodsMixin, int):
584
606
 
585
607
  output["characteristic"] = characteristic
586
608
  return output # type: ignore
609
+
610
+ @versionadded("5.1.0")
611
+ def split(self, parts: int) -> list[int]:
612
+ """
613
+ Evenly split the number into ``parts`` parts
614
+
615
+ Parameters
616
+ ----------
617
+ parts : int
618
+ Split by how many parts
619
+
620
+ Returns
621
+ -------
622
+ list[int]
623
+ List of evenly splitted numbers
624
+
625
+
626
+ Example:
627
+ --------
628
+ >>> IntExt(10).split(4)
629
+ [2, 2, 3, 3]
630
+ """
631
+ p = max(1, parts)
632
+ if p == 1:
633
+ return [int(self)]
634
+
635
+ quotient, remainder = divmod(self, p)
636
+ return [quotient + (i >= (p - remainder)) for i in range(p)]
637
+
638
+
639
+ # Class
640
+ # ---------------------------------------------------------------------------
641
+ class IntBase(int, ABC):
642
+ """
643
+ A base class for creating custom integer-like types.
644
+ Provides a hook for validation and extension.
645
+
646
+ Usage:
647
+ ------
648
+ Use ``_validate(cls, value: int) -> None:`` classmethod to create custom validator
649
+ """
650
+
651
+ def __new__(cls, value: int = 0) -> Self:
652
+ # Ensure the value is an integer
653
+ if not isinstance(value, int):
654
+ raise TypeError(f"{cls.__name__} must be initialized with an int, got {type(value).__name__}")
655
+ # Optional validation hook
656
+ cls._validate(value)
657
+ return int.__new__(cls, value)
658
+
659
+ @classmethod
660
+ def _validate(cls, value: int) -> None:
661
+ """Override in subclasses to add custom validation."""
662
+ pass
663
+
664
+ def __repr__(self) -> str:
665
+ return f"{self.__class__.__name__}({int(self)})"
666
+
667
+
668
+ class PositiveInt(IntBase):
669
+ """Only allows positive int"""
670
+
671
+ @classmethod
672
+ @override
673
+ def _validate(cls, value: int) -> None:
674
+ if value < 0:
675
+ raise ValueError(f"{cls.__name__} must be non-negative")
676
+
677
+
678
+ class NegativeInt(IntBase):
679
+ """Only allows negative int"""
680
+
681
+ @classmethod
682
+ @override
683
+ def _validate(cls, value: int) -> None:
684
+ if value >= 0:
685
+ raise ValueError(f"{cls.__name__} must be non-positive")
686
+
687
+
688
+ class IntWithNote(IntBase):
689
+ """Int with additional note"""
690
+
691
+ def __new__(cls, value: int = 0) -> Self:
692
+ ins = super().__new__(cls, value)
693
+ ins.note = ""
694
+ return ins
695
+
696
+ def __repr__(self) -> str:
697
+ if self.note == "":
698
+ return f"{int(self)}"
699
+ # return super().__repr__()
700
+ return f"{self.__class__.__name__}({int(self)}, note={repr(self.note)})"
701
+
702
+ def add_note(self, note: str) -> None:
703
+ self.note = note