absfuyu 5.11.0__tar.gz → 5.12.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- {absfuyu-5.11.0 → absfuyu-5.12.1}/PKG-INFO +1 -1
- {absfuyu-5.11.0 → absfuyu-5.12.1}/pyproject.toml +19 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/__init__.py +1 -1
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/__main__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/color.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/config_group.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/do_group.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/game_group.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/cli/tool_group.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/config/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/baseclass.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/baseclass2.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/decorator.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/docstring.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/dummy_cli.py +3 -3
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/core/dummy_func.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/dxt/__init__.py +2 -2
- absfuyu-5.12.1/src/absfuyu/dxt/dictext.py +387 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/dxt/dxt_support.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/dxt/intext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/dxt/listext.py +320 -7
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/dxt/strext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/beautiful.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/da/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/da/dadf.py +73 -3
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/da/dadf_base.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/da/df_func.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/da/mplt.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/data_analysis.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/pdf.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/rclone.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/extra/xml.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/fun/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/fun/rubik.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/fun/tarot.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/game_stat.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/schulte.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/sudoku.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/tictactoe.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/game/wordle.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/general/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/general/content.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/general/human.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/general/shape.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/general/tax.py +22 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/logger.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/deprecated.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/logo.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/sort.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/checksum.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/converter.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/generator.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/inspector.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/keygen.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/obfuscator.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/passwordlib.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/shutdownizer.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/sw.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/tools/web.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/typings.py +5 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/__init__.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/api.py +2 -2
- absfuyu-5.12.1/src/absfuyu/util/cli.py +118 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/gui.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/json_method.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/lunar.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/path.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/performance.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/shorten_number.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/text_table.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/util/zipped.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/version.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/conftest.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_api.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_beautiful.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_config.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_converter.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_core.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_data_analysis.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_dictext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_everything.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_extra.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_fun.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_game.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_generator.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_inspector.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_intext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_listext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_logger.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_obfuscator.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_passwordlib.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_performance.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_pkg_data.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_shape.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_shorten_number.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_strext.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_text_table.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_tools.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_util.py +2 -2
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/test_version.py +2 -2
- absfuyu-5.11.0/src/absfuyu/dxt/dictext.py +0 -207
- {absfuyu-5.11.0 → absfuyu-5.12.1}/.gitignore +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/LICENSE +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/README.md +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/dev_requirements.txt +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/Makefile +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/_template/versioning.html +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/conf.py +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/index.rst +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/info.md +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/make.bat +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/docs/modules.rst +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/Logo Full.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/Logo Square.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/Logo White.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/repository-image-crop.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/repository-image-white.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/images/repository-image.png +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/config/config.json +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/chemistry.pkl +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/passwordlib_lzma.pkl +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/pkg_data/tarot.pkl +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/src/absfuyu/py.typed +0 -0
- {absfuyu-5.11.0 → absfuyu-5.12.1}/tests/__init__.py +0 -0
|
@@ -57,6 +57,25 @@ Repository = "https://github.com/AbsoluteWinter/absfuyu-public"
|
|
|
57
57
|
Issues = "https://github.com/AbsoluteWinter/absfuyu-public/issues"
|
|
58
58
|
|
|
59
59
|
[project.optional-dependencies]
|
|
60
|
+
# docs = ["numpy", "pandas", "rich", "spire-pdf"]
|
|
61
|
+
# dadf = ["numpy", "pandas", "openpyxl", "xlsxwriter"]
|
|
62
|
+
# beautiful = ["rich"]
|
|
63
|
+
# pdf = ["spire-pdf"]
|
|
64
|
+
# xml = ["xmltodict"]
|
|
65
|
+
# rclone = ["rclone-crypt"]
|
|
66
|
+
# extra = [
|
|
67
|
+
# "absfuyu[dadf]", # numpy, pandas, openpyxl, xlsxwriter
|
|
68
|
+
# "absfuyu[pdf]", # spire-pdf
|
|
69
|
+
# "absfuyu[xml]", # xmltodict
|
|
70
|
+
# ]
|
|
71
|
+
# full = [
|
|
72
|
+
# "absfuyu[beautiful]", # rich
|
|
73
|
+
# "absfuyu[rclone]", # rclone-crypt
|
|
74
|
+
# "absfuyu[extra]",
|
|
75
|
+
# "tqdm",
|
|
76
|
+
# "unidecode",
|
|
77
|
+
# # "jinja2"
|
|
78
|
+
# ]
|
|
60
79
|
full = [
|
|
61
80
|
"numpy",
|
|
62
81
|
"pandas",
|
|
@@ -68,7 +87,6 @@ full = [
|
|
|
68
87
|
"spire-pdf",
|
|
69
88
|
"xmltodict",
|
|
70
89
|
"rclone-crypt",
|
|
71
|
-
# "jinja2"
|
|
72
90
|
]
|
|
73
91
|
docs = ["numpy", "pandas", "rich", "spire-pdf"]
|
|
74
92
|
beautiful = ["rich"]
|
|
@@ -78,7 +96,6 @@ dadf = ["numpy", "pandas", "openpyxl", "xlsxwriter"]
|
|
|
78
96
|
xml = ["xmltodict"]
|
|
79
97
|
rclone = ["rclone-crypt"]
|
|
80
98
|
|
|
81
|
-
|
|
82
99
|
[project.scripts]
|
|
83
100
|
absfuyu = "absfuyu.__main__:main"
|
|
84
101
|
fuyu = "absfuyu.__main__:main" # alias
|
|
@@ -3,8 +3,8 @@ Absfuyu: Core
|
|
|
3
3
|
-------------
|
|
4
4
|
Dummy cli
|
|
5
5
|
|
|
6
|
-
Version: 5.
|
|
7
|
-
Date updated:
|
|
6
|
+
Version: 5.12.0
|
|
7
|
+
Date updated: 17/10/2025 (dd/mm/yyyy)
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
10
|
# Module Package
|
|
@@ -57,7 +57,7 @@ def get_parser(
|
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def cli() -> None:
|
|
60
|
-
desc = "This is a dummy cli,
|
|
60
|
+
desc = "This is a dummy cli, install <click> and <colorama> package to use this feature"
|
|
61
61
|
arg_parser = get_parser(
|
|
62
62
|
name=__title__,
|
|
63
63
|
description=desc,
|
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Data Extension
|
|
3
|
+
-----------------------
|
|
4
|
+
dict extension
|
|
5
|
+
|
|
6
|
+
Version: 5.12.0
|
|
7
|
+
Date updated: 17/10/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module Package
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["DictExt", "DictAnalyzeResult"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
import operator
|
|
18
|
+
from collections.abc import Callable
|
|
19
|
+
from typing import Any, Literal, NamedTuple, Self, TypeVar, overload
|
|
20
|
+
|
|
21
|
+
from absfuyu.core import GetClassMembersMixin, versionadded, versionchanged
|
|
22
|
+
|
|
23
|
+
# from absfuyu.typings import KT as _KT
|
|
24
|
+
# from absfuyu.typings import VT as _VT
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Type
|
|
28
|
+
# ---------------------------------------------------------------------------
|
|
29
|
+
KT = TypeVar("KT")
|
|
30
|
+
VT = TypeVar("VT")
|
|
31
|
+
VT2 = TypeVar("VT2")
|
|
32
|
+
R = TypeVar("R") # Return type - Can be anything
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Class
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
class DictAnalyzeResult(NamedTuple):
|
|
38
|
+
"""
|
|
39
|
+
Result for ``DictExt.analyze()``
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
max_value: int | float
|
|
43
|
+
min_value: int | float
|
|
44
|
+
max_list: list
|
|
45
|
+
min_list: list
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DictExtLegacy(GetClassMembersMixin, dict[KT, VT]):
|
|
49
|
+
"""
|
|
50
|
+
``dict`` extension (with no generic type-hints)
|
|
51
|
+
|
|
52
|
+
>>> # For a list of new methods
|
|
53
|
+
>>> DictExt.show_all_methods()
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
@versionchanged("3.3.0", reason="Updated return type")
|
|
57
|
+
def analyze(self) -> DictAnalyzeResult:
|
|
58
|
+
"""
|
|
59
|
+
Analyze all the key values (``int``, ``float``)
|
|
60
|
+
in ``dict`` then return highest/lowest index
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
dict
|
|
65
|
+
Analyzed data
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
Example:
|
|
69
|
+
--------
|
|
70
|
+
>>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
|
|
71
|
+
>>> test.analyze()
|
|
72
|
+
DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
|
|
73
|
+
"""
|
|
74
|
+
try:
|
|
75
|
+
dct: dict = self.copy()
|
|
76
|
+
|
|
77
|
+
max_val: int | float = max(list(dct.values()))
|
|
78
|
+
min_val: int | float = min(list(dct.values()))
|
|
79
|
+
max_list = []
|
|
80
|
+
min_list = []
|
|
81
|
+
|
|
82
|
+
for k, v in dct.items():
|
|
83
|
+
if v == max_val:
|
|
84
|
+
max_list.append((k, v))
|
|
85
|
+
if v == min_val:
|
|
86
|
+
min_list.append((k, v))
|
|
87
|
+
|
|
88
|
+
return DictAnalyzeResult(max_val, min_val, max_list, min_list)
|
|
89
|
+
|
|
90
|
+
except TypeError:
|
|
91
|
+
err_msg = "Value must be int or float"
|
|
92
|
+
# logger.error(err_msg)
|
|
93
|
+
raise ValueError(err_msg) # noqa: B904
|
|
94
|
+
|
|
95
|
+
def swap_items(self) -> Self:
|
|
96
|
+
"""
|
|
97
|
+
Swap ``dict.keys()`` with ``dict.values()``
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
DictExt
|
|
102
|
+
Swapped dict
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
Example:
|
|
106
|
+
--------
|
|
107
|
+
>>> test = DictExt({"abc": 9})
|
|
108
|
+
>>> test.swap_items()
|
|
109
|
+
{9: 'abc'}
|
|
110
|
+
"""
|
|
111
|
+
# return self.__class__(zip(self.values(), self.keys()))
|
|
112
|
+
return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
113
|
+
|
|
114
|
+
def apply(self, func: Callable[[Any], Any], apply_to_value: bool = True) -> Self:
|
|
115
|
+
"""
|
|
116
|
+
Apply function to ``DictExt.keys()`` or ``DictExt.values()``
|
|
117
|
+
|
|
118
|
+
Parameters
|
|
119
|
+
----------
|
|
120
|
+
func : Callable
|
|
121
|
+
Callable function
|
|
122
|
+
|
|
123
|
+
apply_to_value : bool
|
|
124
|
+
| ``True``: Apply ``func`` to ``DictExt.values()``
|
|
125
|
+
| ``False``: Apply ``func`` to ``DictExt.keys()``
|
|
126
|
+
|
|
127
|
+
Returns
|
|
128
|
+
-------
|
|
129
|
+
DictExt
|
|
130
|
+
DictExt
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
Example:
|
|
134
|
+
--------
|
|
135
|
+
>>> test = DictExt({"abc": 9})
|
|
136
|
+
>>> test.apply(str)
|
|
137
|
+
{'abc': '9'}
|
|
138
|
+
"""
|
|
139
|
+
if apply_to_value:
|
|
140
|
+
new_dict = {k: func(v) for k, v in self.items()}
|
|
141
|
+
else:
|
|
142
|
+
new_dict = {func(k): v for k, v in self.items()}
|
|
143
|
+
return self.__class__(new_dict)
|
|
144
|
+
|
|
145
|
+
@versionchanged("5.0.0", reason="Updated to handle more types and operator")
|
|
146
|
+
@versionadded("3.4.0")
|
|
147
|
+
def aggregate(
|
|
148
|
+
self,
|
|
149
|
+
other_dict: dict[KT, VT],
|
|
150
|
+
default_value: Any = 0,
|
|
151
|
+
operator_func: Callable[[Any, Any], Any] = operator.add, # operator add
|
|
152
|
+
) -> Self:
|
|
153
|
+
"""
|
|
154
|
+
Aggregates the values of the current dictionary with another dictionary.
|
|
155
|
+
|
|
156
|
+
For each unique key, this method applies the specified operator to the values
|
|
157
|
+
from both dictionaries. If a key exists in only one dictionary, its value is used.
|
|
158
|
+
If an error occurs during aggregation (e.g., incompatible types), the values
|
|
159
|
+
from both dictionaries are returned as a list.
|
|
160
|
+
|
|
161
|
+
Parameters
|
|
162
|
+
----------
|
|
163
|
+
other_dict : dict
|
|
164
|
+
The dictionary to aggregate with.
|
|
165
|
+
|
|
166
|
+
default_value : Any, optional
|
|
167
|
+
The value to use for missing keys, by default ``0``
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
operator_func : Callable[[Any, Any], Any], optional
|
|
171
|
+
A function that takes two arguments and returns a single value,
|
|
172
|
+
by default ``operator.add``
|
|
173
|
+
|
|
174
|
+
Returns
|
|
175
|
+
-------
|
|
176
|
+
Self
|
|
177
|
+
A new instance of the aggregated dictionary.
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
Example:
|
|
181
|
+
--------
|
|
182
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
183
|
+
>>> agg = {"test1": 10, "test2": 1}
|
|
184
|
+
>>> print(test.aggregate(agg))
|
|
185
|
+
{'test1': 10, 'test': 5, 'test2': 10}
|
|
186
|
+
|
|
187
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
188
|
+
>>> agg = {"test1": 10, "test2": "1"}
|
|
189
|
+
>>> print(test.aggregate(agg))
|
|
190
|
+
{'test1': 10, 'test': 5, 'test2': [9, '1']}
|
|
191
|
+
"""
|
|
192
|
+
merged_output = {}
|
|
193
|
+
|
|
194
|
+
# Create a set of all unique keys from both dictionaries
|
|
195
|
+
all_keys = set(self) | set(other_dict)
|
|
196
|
+
|
|
197
|
+
for k in all_keys:
|
|
198
|
+
# Retrieve values with default fallback
|
|
199
|
+
value_self = self.get(k, default_value)
|
|
200
|
+
value_other = other_dict.get(k, default_value)
|
|
201
|
+
|
|
202
|
+
try:
|
|
203
|
+
# Attempt to apply the operator for existing keys
|
|
204
|
+
merged_output[k] = operator_func(value_self, value_other)
|
|
205
|
+
except TypeError:
|
|
206
|
+
# If a TypeError occurs (e.g., if values are not compatible), store values as a list
|
|
207
|
+
merged_output[k] = [value_self, value_other]
|
|
208
|
+
|
|
209
|
+
return self.__class__(merged_output)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
# Type-hints are hard coded, no work around Self[KT, VT] yet
|
|
213
|
+
class DictExt(GetClassMembersMixin, dict[KT, VT]):
|
|
214
|
+
"""
|
|
215
|
+
``dict`` extension
|
|
216
|
+
|
|
217
|
+
>>> # For a list of new methods
|
|
218
|
+
>>> DictExt.show_all_methods()
|
|
219
|
+
"""
|
|
220
|
+
|
|
221
|
+
# Analyze
|
|
222
|
+
@versionchanged("3.3.0", reason="Updated return type")
|
|
223
|
+
def analyze(self) -> DictAnalyzeResult:
|
|
224
|
+
"""
|
|
225
|
+
Analyze all the key values (``int``, ``float``)
|
|
226
|
+
in ``dict`` then return highest/lowest index
|
|
227
|
+
|
|
228
|
+
Returns
|
|
229
|
+
-------
|
|
230
|
+
dict
|
|
231
|
+
Analyzed data
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
Example:
|
|
235
|
+
--------
|
|
236
|
+
>>> test = DictExt({"abc": 9, "def": 9, "ghi": 8, "jkl": 1, "mno": 1})
|
|
237
|
+
>>> test.analyze()
|
|
238
|
+
DictAnalyzeResult(max_value=9, min_value=1, max_list=[('abc', 9), ('def', 9)], min_list=[('jkl', 1), ('mno', 1)])
|
|
239
|
+
"""
|
|
240
|
+
try:
|
|
241
|
+
dct: dict = self.copy()
|
|
242
|
+
|
|
243
|
+
max_val: int | float = max(list(dct.values()))
|
|
244
|
+
min_val: int | float = min(list(dct.values()))
|
|
245
|
+
max_list = []
|
|
246
|
+
min_list = []
|
|
247
|
+
|
|
248
|
+
for k, v in dct.items():
|
|
249
|
+
if v == max_val:
|
|
250
|
+
max_list.append((k, v))
|
|
251
|
+
if v == min_val:
|
|
252
|
+
min_list.append((k, v))
|
|
253
|
+
|
|
254
|
+
return DictAnalyzeResult(max_val, min_val, max_list, min_list)
|
|
255
|
+
|
|
256
|
+
except TypeError:
|
|
257
|
+
err_msg = "Value must be int or float"
|
|
258
|
+
# logger.error(err_msg)
|
|
259
|
+
raise ValueError(err_msg) # noqa: B904
|
|
260
|
+
|
|
261
|
+
# Swap
|
|
262
|
+
def swap_items(self) -> "DictExt[VT, KT]":
|
|
263
|
+
"""
|
|
264
|
+
Swap ``dict.keys()`` with ``dict.values()``
|
|
265
|
+
|
|
266
|
+
Returns
|
|
267
|
+
-------
|
|
268
|
+
DictExt
|
|
269
|
+
Swapped dict
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
Example:
|
|
273
|
+
--------
|
|
274
|
+
>>> test = DictExt({"abc": 9})
|
|
275
|
+
>>> test.swap_items()
|
|
276
|
+
{9: 'abc'}
|
|
277
|
+
"""
|
|
278
|
+
# return self.__class__(zip(self.values(), self.keys()))
|
|
279
|
+
out: dict[VT, KT] = {v: k for k, v in self.items()}
|
|
280
|
+
# return self.__class__({v: k for k, v in self.items()}) # type: ignore
|
|
281
|
+
return self.__class__(out) # type: ignore
|
|
282
|
+
|
|
283
|
+
# Apply
|
|
284
|
+
@overload
|
|
285
|
+
def apply(self, func: Callable[[VT], R]) -> "DictExt[KT, R]": ...
|
|
286
|
+
|
|
287
|
+
@overload
|
|
288
|
+
def apply(self, func: Callable[[KT], R], apply_to_value: Literal[False] = ...) -> "DictExt[R, VT]": ...
|
|
289
|
+
|
|
290
|
+
def apply(self, func: Callable[[KT | VT], R], apply_to_value: bool = True): # type: ignore
|
|
291
|
+
"""
|
|
292
|
+
Apply function to ``DictExt.keys()`` or ``DictExt.values()``
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
func : Callable
|
|
297
|
+
Callable function
|
|
298
|
+
|
|
299
|
+
apply_to_value : bool
|
|
300
|
+
| ``True``: Apply ``func`` to ``DictExt.values()``
|
|
301
|
+
| ``False``: Apply ``func`` to ``DictExt.keys()``
|
|
302
|
+
|
|
303
|
+
Returns
|
|
304
|
+
-------
|
|
305
|
+
DictExt
|
|
306
|
+
DictExt
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
Example:
|
|
310
|
+
--------
|
|
311
|
+
>>> test = DictExt({"abc": 9})
|
|
312
|
+
>>> test.apply(str)
|
|
313
|
+
{'abc': '9'}
|
|
314
|
+
"""
|
|
315
|
+
if apply_to_value:
|
|
316
|
+
new_dict_v: dict[KT, R] = {k: func(v) for k, v in self.items()}
|
|
317
|
+
return self.__class__(new_dict_v) # type: ignore
|
|
318
|
+
else:
|
|
319
|
+
new_dict_k: dict[R, VT] = {func(k): v for k, v in self.items()}
|
|
320
|
+
return self.__class__(new_dict_k) # type: ignore
|
|
321
|
+
|
|
322
|
+
# Aggregate
|
|
323
|
+
@versionchanged("5.0.0", reason="Updated to handle more types and operator")
|
|
324
|
+
@versionadded("3.4.0")
|
|
325
|
+
def aggregate(
|
|
326
|
+
self,
|
|
327
|
+
other_dict: dict[KT, VT | VT2],
|
|
328
|
+
default_value: Any = 0,
|
|
329
|
+
operator_func: Callable[[Any, Any], R] = operator.add, # operator add
|
|
330
|
+
):
|
|
331
|
+
"""
|
|
332
|
+
Aggregates the values of the current dictionary with another dictionary.
|
|
333
|
+
|
|
334
|
+
For each unique key, this method applies the specified operator to the values
|
|
335
|
+
from both dictionaries. If a key exists in only one dictionary, its value is used.
|
|
336
|
+
If an error occurs during aggregation (e.g., incompatible types), the values
|
|
337
|
+
from both dictionaries are returned as a list.
|
|
338
|
+
|
|
339
|
+
Parameters
|
|
340
|
+
----------
|
|
341
|
+
other_dict : dict
|
|
342
|
+
The dictionary to aggregate with.
|
|
343
|
+
|
|
344
|
+
default_value : Any, optional
|
|
345
|
+
The value to use for missing keys, by default ``0``
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
operator_func : Callable[[Any, Any], Any], optional
|
|
349
|
+
A function that takes two arguments and returns a single value,
|
|
350
|
+
by default ``operator.add``
|
|
351
|
+
|
|
352
|
+
Returns
|
|
353
|
+
-------
|
|
354
|
+
Self
|
|
355
|
+
A new instance of the aggregated dictionary.
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
Example:
|
|
359
|
+
--------
|
|
360
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
361
|
+
>>> agg = {"test1": 10, "test2": 1}
|
|
362
|
+
>>> print(test.aggregate(agg))
|
|
363
|
+
{'test1': 10, 'test': 5, 'test2': 10}
|
|
364
|
+
|
|
365
|
+
>>> test = DictExt({"test": 5, "test2": 9})
|
|
366
|
+
>>> agg = {"test1": 10, "test2": "1"}
|
|
367
|
+
>>> print(test.aggregate(agg))
|
|
368
|
+
{'test1': 10, 'test': 5, 'test2': [9, '1']}
|
|
369
|
+
"""
|
|
370
|
+
merged_output: dict[KT, VT | R | list[VT | VT2]] = {}
|
|
371
|
+
|
|
372
|
+
# Create a set of all unique keys from both dictionaries
|
|
373
|
+
all_keys = set(self) | set(other_dict)
|
|
374
|
+
|
|
375
|
+
for k in all_keys:
|
|
376
|
+
# Retrieve values with default fallback
|
|
377
|
+
value_self = self.get(k, default_value)
|
|
378
|
+
value_other = other_dict.get(k, default_value)
|
|
379
|
+
|
|
380
|
+
try:
|
|
381
|
+
# Attempt to apply the operator for existing keys
|
|
382
|
+
merged_output[k] = operator_func(value_self, value_other)
|
|
383
|
+
except TypeError:
|
|
384
|
+
# If a TypeError occurs (e.g., if values are not compatible), store values as a list
|
|
385
|
+
merged_output[k] = [value_self, value_other] # type: ignore
|
|
386
|
+
|
|
387
|
+
return self.__class__(merged_output) # type: ignore
|