extended-data-types 1.0.0__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.
- extended_data_types/__init__.py +90 -0
- extended_data_types/base64_utils.py +53 -0
- extended_data_types/export_utils.py +98 -0
- extended_data_types/hcl2_utils.py +52 -0
- extended_data_types/import_utils.py +26 -0
- extended_data_types/json_utils.py +35 -0
- extended_data_types/list_data_type.py +78 -0
- extended_data_types/map_data_type.py +226 -0
- extended_data_types/matcher_utils.py +68 -0
- extended_data_types/py.typed +0 -0
- extended_data_types/splitter_utils.py +54 -0
- extended_data_types/stack_utils.py +87 -0
- extended_data_types/state_utils.py +169 -0
- extended_data_types/string_data_type.py +161 -0
- extended_data_types/type_utils.py +67 -0
- extended_data_types/yaml_utils/__init__.py +31 -0
- extended_data_types/yaml_utils/constructors.py +58 -0
- extended_data_types/yaml_utils/dumpers.py +69 -0
- extended_data_types/yaml_utils/loaders.py +28 -0
- extended_data_types/yaml_utils/representers.py +65 -0
- extended_data_types/yaml_utils/tag_classes.py +53 -0
- extended_data_types/yaml_utils/utils.py +61 -0
- extended_data_types-1.0.0.dist-info/METADATA +123 -0
- extended_data_types-1.0.0.dist-info/RECORD +26 -0
- extended_data_types-1.0.0.dist-info/WHEEL +4 -0
- extended_data_types-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"""Extended Data Types Library.
|
|
2
|
+
|
|
3
|
+
This library provides extended functionality for handling various data types in Python.
|
|
4
|
+
It includes utilities for YAML, JSON, Base64, file paths, strings, lists, maps, and more.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from .base64_utils import base64_encode
|
|
10
|
+
from .export_utils import wrap_raw_data_for_export
|
|
11
|
+
from .hcl2_utils import decode_hcl2
|
|
12
|
+
from .json_utils import decode_json, encode_json
|
|
13
|
+
from .list_data_type import filter_list, flatten_list
|
|
14
|
+
from .map_data_type import (
|
|
15
|
+
all_values_from_map,
|
|
16
|
+
deduplicate_map,
|
|
17
|
+
filter_map,
|
|
18
|
+
first_non_empty_value_from_map,
|
|
19
|
+
flatten_map,
|
|
20
|
+
get_default_dict,
|
|
21
|
+
unhump_map,
|
|
22
|
+
zipmap,
|
|
23
|
+
)
|
|
24
|
+
from .matcher_utils import is_non_empty_match, is_partial_match
|
|
25
|
+
from .splitter_utils import split_dict_by_type, split_list_by_type
|
|
26
|
+
from .stack_utils import filter_methods, get_available_methods, get_caller
|
|
27
|
+
from .state_utils import (
|
|
28
|
+
all_non_empty,
|
|
29
|
+
all_non_empty_in_dict,
|
|
30
|
+
all_non_empty_in_list,
|
|
31
|
+
any_non_empty,
|
|
32
|
+
are_nothing,
|
|
33
|
+
first_non_empty,
|
|
34
|
+
is_nothing,
|
|
35
|
+
yield_non_empty,
|
|
36
|
+
)
|
|
37
|
+
from .string_data_type import (
|
|
38
|
+
is_url,
|
|
39
|
+
lower_first_char,
|
|
40
|
+
sanitize_key,
|
|
41
|
+
strtobool,
|
|
42
|
+
titleize_name,
|
|
43
|
+
truncate,
|
|
44
|
+
upper_first_char,
|
|
45
|
+
)
|
|
46
|
+
from .yaml_utils import decode_yaml, encode_yaml, is_yaml_data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
__all__ = [
|
|
50
|
+
"base64_encode",
|
|
51
|
+
"wrap_raw_data_for_export",
|
|
52
|
+
"decode_hcl2",
|
|
53
|
+
"decode_json",
|
|
54
|
+
"encode_json",
|
|
55
|
+
"flatten_list",
|
|
56
|
+
"filter_list",
|
|
57
|
+
"first_non_empty_value_from_map",
|
|
58
|
+
"deduplicate_map",
|
|
59
|
+
"all_values_from_map",
|
|
60
|
+
"flatten_map",
|
|
61
|
+
"zipmap",
|
|
62
|
+
"get_default_dict",
|
|
63
|
+
"unhump_map",
|
|
64
|
+
"filter_map",
|
|
65
|
+
"is_partial_match",
|
|
66
|
+
"is_non_empty_match",
|
|
67
|
+
"is_nothing",
|
|
68
|
+
"all_non_empty",
|
|
69
|
+
"all_non_empty_in_list",
|
|
70
|
+
"all_non_empty_in_dict",
|
|
71
|
+
"are_nothing",
|
|
72
|
+
"first_non_empty",
|
|
73
|
+
"any_non_empty",
|
|
74
|
+
"yield_non_empty",
|
|
75
|
+
"split_list_by_type",
|
|
76
|
+
"split_dict_by_type",
|
|
77
|
+
"get_caller",
|
|
78
|
+
"filter_methods",
|
|
79
|
+
"get_available_methods",
|
|
80
|
+
"sanitize_key",
|
|
81
|
+
"truncate",
|
|
82
|
+
"lower_first_char",
|
|
83
|
+
"upper_first_char",
|
|
84
|
+
"is_url",
|
|
85
|
+
"titleize_name",
|
|
86
|
+
"strtobool",
|
|
87
|
+
"decode_yaml",
|
|
88
|
+
"encode_yaml",
|
|
89
|
+
"is_yaml_data",
|
|
90
|
+
]
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""This module provides utilities for encoding and decoding data to and from Base64 format.
|
|
2
|
+
|
|
3
|
+
It includes functions to encode data to Base64 strings, with optional data
|
|
4
|
+
wrapping for export, and to decode Base64 strings back to their original data.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from base64 import b64decode, b64encode
|
|
10
|
+
|
|
11
|
+
from .export_utils import wrap_raw_data_for_export
|
|
12
|
+
from .import_utils import unwrap_raw_data_from_import
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def base64_encode(raw_data: str | bytes, wrap_raw_data: bool = True) -> str:
|
|
16
|
+
"""Encodes data to base64 format.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
raw_data (str | bytes): The data to encode.
|
|
20
|
+
wrap_raw_data (bool): Whether to wrap the raw data for export.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
str: The base64 encoded string.
|
|
24
|
+
"""
|
|
25
|
+
if wrap_raw_data:
|
|
26
|
+
if isinstance(raw_data, bytes):
|
|
27
|
+
raw_data = raw_data.decode("utf-8")
|
|
28
|
+
raw_data = wrap_raw_data_for_export(raw_data).encode("utf-8")
|
|
29
|
+
elif isinstance(raw_data, str):
|
|
30
|
+
raw_data = raw_data.encode("utf-8")
|
|
31
|
+
|
|
32
|
+
return b64encode(raw_data).decode("utf-8")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def base64_decode(
|
|
36
|
+
encoded_data: str,
|
|
37
|
+
unwrap_raw_data: bool = True,
|
|
38
|
+
encoding: str = "yaml",
|
|
39
|
+
) -> str | bytes:
|
|
40
|
+
"""Decodes data from base64 format.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
encoded_data (str): The base64 encoded string to decode.
|
|
44
|
+
unwrap_raw_data (bool): Whether to unwrap the raw data after decoding.
|
|
45
|
+
encoding (str): The encoding format used for wrapping (default is 'yaml').
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str | bytes: The decoded data, as a string if unwrapped, otherwise as bytes.
|
|
49
|
+
"""
|
|
50
|
+
decoded_data = b64decode(encoded_data).decode("utf-8")
|
|
51
|
+
if unwrap_raw_data:
|
|
52
|
+
decoded_data = unwrap_raw_data_from_import(decoded_data, encoding=encoding)
|
|
53
|
+
return decoded_data
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"""This module provides utilities for exporting raw data in various formats.
|
|
2
|
+
|
|
3
|
+
It includes functions to make raw data export-safe and to wrap raw data for export
|
|
4
|
+
with optional encoding formats such as YAML or JSON.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import datetime
|
|
10
|
+
import pathlib
|
|
11
|
+
|
|
12
|
+
from typing import Any, Mapping
|
|
13
|
+
|
|
14
|
+
from .json_utils import encode_json
|
|
15
|
+
from .string_data_type import strtobool
|
|
16
|
+
from .yaml_utils import YamlPairs, YamlTagged, encode_yaml, is_yaml_data
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def make_raw_data_export_safe(raw_data: Any, export_to_yaml: bool = False) -> Any:
|
|
20
|
+
"""Makes raw data export safe by converting certain types to strings.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
raw_data (Any): The raw data to process.
|
|
24
|
+
export_to_yaml (bool): Flag to indicate if the data is for YAML export.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Any: The processed data.
|
|
28
|
+
"""
|
|
29
|
+
if isinstance(raw_data, Mapping):
|
|
30
|
+
return {
|
|
31
|
+
k: make_raw_data_export_safe(v, export_to_yaml=export_to_yaml)
|
|
32
|
+
for k, v in raw_data.items()
|
|
33
|
+
}
|
|
34
|
+
if isinstance(raw_data, (set, list)):
|
|
35
|
+
return [
|
|
36
|
+
make_raw_data_export_safe(v, export_to_yaml=export_to_yaml)
|
|
37
|
+
for v in raw_data
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
if isinstance(raw_data, YamlTagged):
|
|
41
|
+
raw_data = raw_data.__wrapped__
|
|
42
|
+
elif isinstance(raw_data, YamlPairs):
|
|
43
|
+
raw_data = list(raw_data)
|
|
44
|
+
|
|
45
|
+
if isinstance(raw_data, (datetime.date, datetime.datetime)):
|
|
46
|
+
return raw_data.isoformat()
|
|
47
|
+
if isinstance(raw_data, pathlib.Path):
|
|
48
|
+
return str(raw_data)
|
|
49
|
+
if isinstance(raw_data, (int, float, str, bool, type(None))):
|
|
50
|
+
return raw_data
|
|
51
|
+
|
|
52
|
+
# For all other types, convert to string representation
|
|
53
|
+
return str(raw_data)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def wrap_raw_data_for_export(
|
|
57
|
+
raw_data: Mapping[str, Any] | Any,
|
|
58
|
+
allow_encoding: bool | str = True,
|
|
59
|
+
**format_opts: Any,
|
|
60
|
+
) -> str:
|
|
61
|
+
"""Wraps raw data for export, optionally encoding it.
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
raw_data (Mapping[str, Any] | Any): The raw data to wrap.
|
|
65
|
+
allow_encoding (bool | str): The encoding format or flag (default is 'yaml').
|
|
66
|
+
format_opts (Any): Additional options for formatting the output.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
str: The wrapped and encoded data.
|
|
70
|
+
"""
|
|
71
|
+
raw_data = make_raw_data_export_safe(raw_data)
|
|
72
|
+
|
|
73
|
+
if isinstance(allow_encoding, str):
|
|
74
|
+
allow_encoding_lower = allow_encoding.casefold()
|
|
75
|
+
if allow_encoding_lower == "yaml":
|
|
76
|
+
return encode_yaml(raw_data)
|
|
77
|
+
if allow_encoding_lower == "json":
|
|
78
|
+
return encode_json(raw_data, **format_opts)
|
|
79
|
+
if allow_encoding_lower == "raw":
|
|
80
|
+
return str(raw_data)
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
allow_encoding_bool = strtobool(allow_encoding, raise_on_error=True)
|
|
84
|
+
allow_encoding = (
|
|
85
|
+
allow_encoding_bool
|
|
86
|
+
if isinstance(allow_encoding_bool, bool)
|
|
87
|
+
else allow_encoding
|
|
88
|
+
)
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
error_message = f"Invalid allow_encoding value: {allow_encoding}"
|
|
91
|
+
raise ValueError(error_message) from e
|
|
92
|
+
|
|
93
|
+
if allow_encoding:
|
|
94
|
+
if is_yaml_data(raw_data):
|
|
95
|
+
return encode_yaml(raw_data)
|
|
96
|
+
return encode_json(raw_data, **format_opts)
|
|
97
|
+
|
|
98
|
+
return str(raw_data)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""This module provides utilities for decoding and encoding HCL2 data.
|
|
2
|
+
|
|
3
|
+
It includes functions to decode HCL2 strings into Python objects with appropriate
|
|
4
|
+
error handling, and to encode Python objects into HCL2 strings.
|
|
5
|
+
|
|
6
|
+
Credits:
|
|
7
|
+
The approach for encoding HCL2 data is inspired by Nicolai Antiferov's article:
|
|
8
|
+
https://nklya.medium.com/how-to-write-hcl2-from-python-53ac12e45874
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
|
|
15
|
+
from io import StringIO
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import hcl2
|
|
19
|
+
|
|
20
|
+
from lark.exceptions import UnexpectedToken
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def decode_hcl2(hcl2_data: str) -> Any:
|
|
24
|
+
"""Decodes HCL2 data into a Python object.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
hcl2_data (str): The HCL2 data to decode.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
Any: The decoded Python object.
|
|
31
|
+
|
|
32
|
+
Raises:
|
|
33
|
+
ValueError: If the HCL2 data cannot be parsed.
|
|
34
|
+
"""
|
|
35
|
+
hcl2_data_stream = StringIO(hcl2_data)
|
|
36
|
+
try:
|
|
37
|
+
return hcl2.load(hcl2_data_stream) # type: ignore[attr-defined]
|
|
38
|
+
except UnexpectedToken as e:
|
|
39
|
+
error_message = f"Invalid HCL2 data: {e}"
|
|
40
|
+
raise ValueError(error_message) from e
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def encode_hcl2(data: Any) -> str:
|
|
44
|
+
"""Encodes a Python object into an HCL2 string.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
data (Any): The Python object to encode.
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
str: The encoded HCL2 string.
|
|
51
|
+
"""
|
|
52
|
+
return json.dumps(data, indent=2, separators=(",", " = "))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""This module provides utilities for unwrapping data after import."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from .json_utils import decode_json
|
|
6
|
+
from .yaml_utils import decode_yaml
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def unwrap_raw_data_from_import(wrapped_data: str, encoding: str = "yaml") -> Any:
|
|
10
|
+
"""Unwraps the data that was wrapped for import.
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
wrapped_data (str): The wrapped data.
|
|
14
|
+
encoding (str): The encoding format (default is 'yaml').
|
|
15
|
+
|
|
16
|
+
Returns:
|
|
17
|
+
Any: The unwrapped data.
|
|
18
|
+
"""
|
|
19
|
+
encoding_lower = encoding.casefold()
|
|
20
|
+
if encoding_lower == "yaml":
|
|
21
|
+
return decode_yaml(wrapped_data)
|
|
22
|
+
if encoding_lower == "json":
|
|
23
|
+
return decode_json(wrapped_data)
|
|
24
|
+
|
|
25
|
+
error_message = f"Unsupported encoding format: {encoding}"
|
|
26
|
+
raise ValueError(error_message)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""JSON Utilities Module.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for encoding and decoding JSON using the standard `json` library.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def decode_json(json_data: str) -> Any:
|
|
14
|
+
"""Decodes a JSON string into a Python object using json.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
json_data (str): The JSON string to decode.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
Any: The decoded Python object.
|
|
21
|
+
"""
|
|
22
|
+
return json.loads(json_data)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def encode_json(raw_data: Any, **format_opts: Any) -> str:
|
|
26
|
+
"""Encodes a Python object into a JSON string using json.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
raw_data (Any): The Python object to encode.
|
|
30
|
+
format_opts (Any): Options for formatting the JSON output.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
str: The encoded JSON string.
|
|
34
|
+
"""
|
|
35
|
+
return json.dumps(raw_data, **format_opts)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""This module provides utilities for handling lists.
|
|
2
|
+
|
|
3
|
+
It includes functions to flatten lists and to filter lists based on allowlists
|
|
4
|
+
and denylists.
|
|
5
|
+
|
|
6
|
+
Functions:
|
|
7
|
+
- flatten_list: Flattens a list of lists into a single list.
|
|
8
|
+
- filter_list: Filters a list based on allowlist and denylist.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from typing import Any
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def flatten_list(matrix: list[Any]) -> list[Any]:
|
|
17
|
+
"""Flattens a list of lists into a single list.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
matrix (list[Any]): The list of lists to flatten.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
list[Any]: The flattened list.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
def _flatten(lst: list[Any]) -> list[Any]:
|
|
27
|
+
"""Recursively flattens a nested list.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
lst (list[Any]): The list to flatten.
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
list[Any]: The flattened list.
|
|
34
|
+
"""
|
|
35
|
+
flattened = []
|
|
36
|
+
for item in lst:
|
|
37
|
+
if isinstance(item, list):
|
|
38
|
+
flattened.extend(_flatten(item))
|
|
39
|
+
else:
|
|
40
|
+
flattened.append(item)
|
|
41
|
+
return flattened
|
|
42
|
+
|
|
43
|
+
return _flatten(matrix)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def filter_list(
|
|
47
|
+
items: list[str] | None,
|
|
48
|
+
allowlist: list[str] | None = None,
|
|
49
|
+
denylist: list[str] | None = None,
|
|
50
|
+
) -> list[str]:
|
|
51
|
+
"""Filters a list based on allowlist and denylist.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
items (list[str] | None): The list to filter.
|
|
55
|
+
allowlist (list[str] | None): The list of allowed items.
|
|
56
|
+
denylist (list[str] | None): The list of denied items.
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
list[str]: The filtered list.
|
|
60
|
+
"""
|
|
61
|
+
if items is None:
|
|
62
|
+
items = []
|
|
63
|
+
|
|
64
|
+
if allowlist is None:
|
|
65
|
+
allowlist = []
|
|
66
|
+
|
|
67
|
+
if denylist is None:
|
|
68
|
+
denylist = []
|
|
69
|
+
|
|
70
|
+
filtered = []
|
|
71
|
+
|
|
72
|
+
for elem in items:
|
|
73
|
+
if (len(allowlist) > 0 and elem not in allowlist) or elem in denylist:
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
filtered.append(elem)
|
|
77
|
+
|
|
78
|
+
return filtered
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
"""This module provides utilities for handling maps (dictionaries).
|
|
2
|
+
|
|
3
|
+
It includes functions to manipulate, flatten, and filter dictionaries, and to
|
|
4
|
+
convert keys from camelCase to snake_case.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
from typing import Any, Mapping, MutableMapping
|
|
11
|
+
|
|
12
|
+
import inflection
|
|
13
|
+
|
|
14
|
+
from sortedcontainers import SortedDict
|
|
15
|
+
|
|
16
|
+
from .export_utils import make_raw_data_export_safe
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def first_non_empty_value_from_map(m: Mapping[str, Any], *keys: str) -> Any:
|
|
20
|
+
"""Returns the first non-empty value from a map for the given keys.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
m (Mapping[str, Any]): The map to search.
|
|
24
|
+
keys: The keys to search for.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
Any: The first non-empty value.
|
|
28
|
+
"""
|
|
29
|
+
for key in keys:
|
|
30
|
+
if m.get(key):
|
|
31
|
+
return m[key]
|
|
32
|
+
return None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def deduplicate_map(m: Mapping[str, Any]) -> dict[str, Any]:
|
|
36
|
+
"""Removes duplicate values from a map.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
m (Mapping[str, Any]): The map to deduplicate.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
dict[str, Any]: The deduplicated map.
|
|
43
|
+
"""
|
|
44
|
+
deduplicated_map: dict[str, Any] = make_raw_data_export_safe(m)
|
|
45
|
+
|
|
46
|
+
for k, v in m.items():
|
|
47
|
+
if isinstance(v, list):
|
|
48
|
+
deduplicated_map[k] = []
|
|
49
|
+
|
|
50
|
+
for elem in v:
|
|
51
|
+
if elem in deduplicated_map[k]:
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
deduplicated_map[k].append(elem)
|
|
55
|
+
|
|
56
|
+
continue
|
|
57
|
+
|
|
58
|
+
if isinstance(v, Mapping):
|
|
59
|
+
deduplicated_map[k] = deduplicate_map(v)
|
|
60
|
+
continue
|
|
61
|
+
|
|
62
|
+
if k not in deduplicated_map:
|
|
63
|
+
deduplicated_map[k] = v
|
|
64
|
+
|
|
65
|
+
return deduplicated_map
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def all_values_from_map(m: Mapping[str, Any]) -> list[Any]:
|
|
69
|
+
"""Returns all values from a nested map.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
m (Mapping[str, Any]): The map to retrieve values from.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
List[Any]: A list of all values.
|
|
76
|
+
"""
|
|
77
|
+
values = []
|
|
78
|
+
|
|
79
|
+
for v in m.values():
|
|
80
|
+
if isinstance(v, Mapping):
|
|
81
|
+
values.extend(all_values_from_map(v))
|
|
82
|
+
elif isinstance(v, list):
|
|
83
|
+
for item in v:
|
|
84
|
+
if isinstance(item, Mapping):
|
|
85
|
+
values.extend(all_values_from_map(item))
|
|
86
|
+
else:
|
|
87
|
+
values.append(item)
|
|
88
|
+
else:
|
|
89
|
+
values.append(v)
|
|
90
|
+
|
|
91
|
+
return values
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def flatten_map(
|
|
95
|
+
dictionary: Mapping[str, Any],
|
|
96
|
+
parent_key: str | None = "",
|
|
97
|
+
separator: str = ".",
|
|
98
|
+
) -> dict[str, Any]:
|
|
99
|
+
"""Flattens a nested dictionary into a flat dictionary.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
dictionary (Mapping[str, Any]): The dictionary to flatten.
|
|
103
|
+
parent_key (Optional[str]): The string to prepend to dictionary's keys.
|
|
104
|
+
separator (str): The string used to separate flattened keys.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Dict[str, Any]: The flattened dictionary.
|
|
108
|
+
"""
|
|
109
|
+
items: list[tuple[str, Any]] = []
|
|
110
|
+
for key, value in dictionary.items():
|
|
111
|
+
new_key = f"{parent_key}{separator}{key}" if parent_key else key
|
|
112
|
+
if isinstance(value, MutableMapping):
|
|
113
|
+
items.extend(flatten_map(value, new_key, separator).items())
|
|
114
|
+
elif isinstance(value, list):
|
|
115
|
+
for k, v in enumerate(value):
|
|
116
|
+
items.extend(flatten_map({str(k): v}, new_key).items())
|
|
117
|
+
else:
|
|
118
|
+
items.append((new_key, value))
|
|
119
|
+
return dict(items)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def zipmap(a: list[str], b: list[str]) -> dict[str, str]:
|
|
123
|
+
"""Creates a dictionary from two lists by zipping them together.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
a (List[str]): The first list.
|
|
127
|
+
b (List[str]): The second list.
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Dict[str, str]: The resulting dictionary.
|
|
131
|
+
"""
|
|
132
|
+
zipped = {}
|
|
133
|
+
|
|
134
|
+
for idx, val in enumerate(a):
|
|
135
|
+
if idx >= len(b):
|
|
136
|
+
break
|
|
137
|
+
|
|
138
|
+
zipped[val] = b[idx]
|
|
139
|
+
|
|
140
|
+
return zipped
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def get_default_dict(
|
|
144
|
+
use_sorted_dict: bool = False,
|
|
145
|
+
default_type: type[dict[str, Any]] = dict,
|
|
146
|
+
) -> Any:
|
|
147
|
+
"""Returns a default dictionary with nested default dictionaries.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
use_sorted_dict (bool): Whether to use SortedDict for sorting keys.
|
|
151
|
+
default_type (Type[Dict[str, Any]]): The type of the default dictionary.
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Any: The default dictionary.
|
|
155
|
+
"""
|
|
156
|
+
|
|
157
|
+
def default_factory() -> Any:
|
|
158
|
+
return SortedDict() if use_sorted_dict else default_type()
|
|
159
|
+
|
|
160
|
+
return defaultdict(default_factory)
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
def unhump_map(
|
|
164
|
+
m: Mapping[str, Any],
|
|
165
|
+
drop_without_prefix: str | None = None,
|
|
166
|
+
) -> dict[str, Any]:
|
|
167
|
+
"""Converts keys in a dictionary from camelCase to snake_case.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
m (Mapping[str, Any]): The dictionary to convert.
|
|
171
|
+
drop_without_prefix (Optional[str]): Drop keys without this prefix.
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Dict[str, Any]: The converted dictionary.
|
|
175
|
+
"""
|
|
176
|
+
unhumped = {}
|
|
177
|
+
|
|
178
|
+
for k, v in m.items():
|
|
179
|
+
if drop_without_prefix is not None and not k.startswith(drop_without_prefix):
|
|
180
|
+
continue
|
|
181
|
+
|
|
182
|
+
unhumped_key = inflection.underscore(k)
|
|
183
|
+
|
|
184
|
+
if isinstance(v, Mapping):
|
|
185
|
+
unhumped[unhumped_key] = unhump_map(v)
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
unhumped[unhumped_key] = v
|
|
189
|
+
|
|
190
|
+
return unhumped
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def filter_map(
|
|
194
|
+
m: Mapping[str, Any] | None,
|
|
195
|
+
allowlist: list[str] | None = None,
|
|
196
|
+
denylist: list[str] | None = None,
|
|
197
|
+
) -> tuple[dict[str, Any], dict[str, Any]]:
|
|
198
|
+
"""Filters a map based on allowlist and denylist.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
m (Optional[Mapping[str, Any]]): The map to filter.
|
|
202
|
+
allowlist (List[str]): The list of allowed keys.
|
|
203
|
+
denylist (List[str]): The list of denied keys.
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
tuple[Dict[str, Any], Dict[str, Any]]: The filtered and remaining maps.
|
|
207
|
+
"""
|
|
208
|
+
if m is None:
|
|
209
|
+
m = {}
|
|
210
|
+
|
|
211
|
+
if allowlist is None:
|
|
212
|
+
allowlist = []
|
|
213
|
+
|
|
214
|
+
if denylist is None:
|
|
215
|
+
denylist = []
|
|
216
|
+
|
|
217
|
+
fm = {}
|
|
218
|
+
rm = {}
|
|
219
|
+
|
|
220
|
+
for k, v in m.items():
|
|
221
|
+
if (len(allowlist) > 0 and k not in allowlist) or k in denylist:
|
|
222
|
+
rm[k] = v
|
|
223
|
+
else:
|
|
224
|
+
fm[k] = v
|
|
225
|
+
|
|
226
|
+
return fm, rm
|