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.
@@ -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