extended-data 8.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.
Files changed (63) hide show
  1. extended_data/__init__.py +105 -0
  2. extended_data/_version.py +16 -0
  3. extended_data/cli.py +204 -0
  4. extended_data/containers/__init__.py +17 -0
  5. extended_data/containers/factory.py +57 -0
  6. extended_data/containers/mappings.py +181 -0
  7. extended_data/containers/sequences.py +403 -0
  8. extended_data/containers/strings.py +276 -0
  9. extended_data/inputs/__init__.py +14 -0
  10. extended_data/inputs/__main__.py +412 -0
  11. extended_data/inputs/decorators.py +346 -0
  12. extended_data/inputs/py.typed +0 -0
  13. extended_data/io/__init__.py +50 -0
  14. extended_data/io/base64.py +65 -0
  15. extended_data/io/exporters.py +151 -0
  16. extended_data/io/files.py +619 -0
  17. extended_data/io/importers.py +53 -0
  18. extended_data/logging/__init__.py +13 -0
  19. extended_data/logging/const.py +15 -0
  20. extended_data/logging/handlers.py +61 -0
  21. extended_data/logging/log_types.py +18 -0
  22. extended_data/logging/logging.py +663 -0
  23. extended_data/logging/py.typed +0 -0
  24. extended_data/logging/utils.py +167 -0
  25. extended_data/primitives/__init__.py +172 -0
  26. extended_data/primitives/formats/__init__.py +47 -0
  27. extended_data/primitives/formats/_normalization.py +15 -0
  28. extended_data/primitives/formats/errors.py +72 -0
  29. extended_data/primitives/formats/hcl.py +264 -0
  30. extended_data/primitives/formats/json.py +100 -0
  31. extended_data/primitives/formats/toml.py +48 -0
  32. extended_data/primitives/formats/yaml/__init__.py +43 -0
  33. extended_data/primitives/formats/yaml/constructors.py +58 -0
  34. extended_data/primitives/formats/yaml/dumpers.py +75 -0
  35. extended_data/primitives/formats/yaml/loaders.py +31 -0
  36. extended_data/primitives/formats/yaml/representers.py +82 -0
  37. extended_data/primitives/formats/yaml/tag_classes.py +79 -0
  38. extended_data/primitives/formats/yaml/utils.py +75 -0
  39. extended_data/primitives/introspection.py +168 -0
  40. extended_data/primitives/mappings.py +399 -0
  41. extended_data/primitives/matching.py +82 -0
  42. extended_data/primitives/numbers.py +236 -0
  43. extended_data/primitives/redaction.py +126 -0
  44. extended_data/primitives/sequences.py +85 -0
  45. extended_data/primitives/serialization.py +37 -0
  46. extended_data/primitives/splitting.py +56 -0
  47. extended_data/primitives/state.py +162 -0
  48. extended_data/primitives/string_transforms.py +74 -0
  49. extended_data/primitives/strings.py +127 -0
  50. extended_data/primitives/transformations/__init__.py +13 -0
  51. extended_data/primitives/transformations/numbers/__init__.py +40 -0
  52. extended_data/primitives/transformations/numbers/notation.py +181 -0
  53. extended_data/primitives/transformations/numbers/words.py +403 -0
  54. extended_data/primitives/transformations/strings/__init__.py +26 -0
  55. extended_data/primitives/transformations/strings/inflection.py +95 -0
  56. extended_data/primitives/types.py +532 -0
  57. extended_data/py.typed +0 -0
  58. extended_data/workflows/__init__.py +394 -0
  59. extended_data-8.0.0.dist-info/METADATA +189 -0
  60. extended_data-8.0.0.dist-info/RECORD +63 -0
  61. extended_data-8.0.0.dist-info/WHEEL +4 -0
  62. extended_data-8.0.0.dist-info/entry_points.txt +2 -0
  63. extended_data-8.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,105 @@
1
+ """Extended Data.
2
+
3
+ This package provides Python utilities for structured data primitives, inputs,
4
+ logging, file processing, and workflow-oriented data operations.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from extended_data._version import __version__
10
+ from extended_data.containers import (
11
+ ExtendedDict,
12
+ ExtendedList,
13
+ ExtendedSet,
14
+ ExtendedString,
15
+ ExtendedTuple,
16
+ extend_data,
17
+ to_builtin,
18
+ )
19
+ from extended_data.inputs import InputProvider, directed_inputs, input_config
20
+ from extended_data.io.base64 import base64_decode, base64_encode
21
+ from extended_data.io.exporters import (
22
+ make_raw_data_export_safe,
23
+ wrap_raw_data_for_export,
24
+ )
25
+ from extended_data.io.files import (
26
+ DataFile,
27
+ FilePath,
28
+ clone_repository_to_temp,
29
+ decode_file,
30
+ delete_file,
31
+ file_path_depth,
32
+ file_path_rel_to_root,
33
+ get_encoding_for_file_path,
34
+ get_parent_repository,
35
+ get_repository_name,
36
+ get_tld,
37
+ is_url,
38
+ match_file_extensions,
39
+ read_data_file,
40
+ read_file,
41
+ resolve_local_path,
42
+ write_file,
43
+ )
44
+ from extended_data.io.importers import unwrap_raw_data_from_import
45
+ from extended_data.logging import ExitRunError, KeyTransform, Logging
46
+ from extended_data.primitives.formats.errors import DataDecodeError
47
+ from extended_data.workflows import (
48
+ DATA_TRANSFORM_STEPS,
49
+ DataWorkflow,
50
+ StepLike,
51
+ WorkflowAction,
52
+ WorkflowResult,
53
+ WorkflowStep,
54
+ data_transform_action,
55
+ list_data_transform_steps,
56
+ )
57
+
58
+
59
+ __all__ = [
60
+ "DATA_TRANSFORM_STEPS",
61
+ "DataDecodeError",
62
+ "DataFile",
63
+ "DataWorkflow",
64
+ "ExitRunError",
65
+ "ExtendedDict",
66
+ "ExtendedList",
67
+ "ExtendedSet",
68
+ "ExtendedString",
69
+ "ExtendedTuple",
70
+ "FilePath",
71
+ "InputProvider",
72
+ "KeyTransform",
73
+ "Logging",
74
+ "StepLike",
75
+ "WorkflowAction",
76
+ "WorkflowResult",
77
+ "WorkflowStep",
78
+ "__version__",
79
+ "base64_decode",
80
+ "base64_encode",
81
+ "clone_repository_to_temp",
82
+ "data_transform_action",
83
+ "decode_file",
84
+ "delete_file",
85
+ "directed_inputs",
86
+ "extend_data",
87
+ "file_path_depth",
88
+ "file_path_rel_to_root",
89
+ "get_encoding_for_file_path",
90
+ "get_parent_repository",
91
+ "get_repository_name",
92
+ "get_tld",
93
+ "input_config",
94
+ "is_url",
95
+ "list_data_transform_steps",
96
+ "make_raw_data_export_safe",
97
+ "match_file_extensions",
98
+ "read_data_file",
99
+ "read_file",
100
+ "resolve_local_path",
101
+ "to_builtin",
102
+ "unwrap_raw_data_from_import",
103
+ "wrap_raw_data_for_export",
104
+ "write_file",
105
+ ]
@@ -0,0 +1,16 @@
1
+ """Package version helpers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from importlib.metadata import PackageNotFoundError, version
6
+
7
+
8
+ def get_version() -> str:
9
+ """Return the installed extended-data distribution version."""
10
+ try:
11
+ return version("extended-data")
12
+ except PackageNotFoundError:
13
+ return "0+unknown"
14
+
15
+
16
+ __version__ = get_version()
extended_data/cli.py ADDED
@@ -0,0 +1,204 @@
1
+ """Top-level command line interface for Extended Data."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+
8
+ from collections.abc import Sequence
9
+ from typing import Any, cast
10
+
11
+ from extended_data.io import DataFile
12
+ from extended_data.primitives.redaction import redact_sensitive_text
13
+ from extended_data.workflows import DataWorkflow, WorkflowResult, list_data_transform_steps
14
+
15
+
16
+ OUTPUT_ENCODINGS = ("json", "yaml", "toml", "hcl", "raw")
17
+
18
+
19
+ def _write_stdout(message: str) -> None:
20
+ """Write one CLI output line."""
21
+ sys.stdout.write(f"{message}\n")
22
+
23
+
24
+ def _write_stderr(message: str) -> None:
25
+ """Write one CLI error line."""
26
+ sys.stderr.write(f"{redact_sensitive_text(message)}\n")
27
+
28
+
29
+ def _decode_artifact(args: argparse.Namespace) -> DataFile:
30
+ """Decode an inline payload or file path into a DataFile artifact."""
31
+ value = getattr(args, "value", None)
32
+ file_path = getattr(args, "file_path", None)
33
+
34
+ if value is not None and file_path is not None:
35
+ raise ValueError("pass either VALUE or --file, not both")
36
+ if value is None and file_path is None:
37
+ raise ValueError("pass VALUE or --file")
38
+ if file_path is not None:
39
+ return DataFile.read(file_path, suffix=args.suffix)
40
+ return DataFile.decode(cast(str, value), suffix=args.suffix)
41
+
42
+
43
+ def cmd_decode(args: argparse.Namespace) -> int:
44
+ """Decode structured data and write it through the shared export boundary."""
45
+ try:
46
+ artifact = _decode_artifact(args)
47
+ _write_stdout(artifact.wrap_for_export(allow_encoding=args.output, **_json_format_opts(args)))
48
+ return 0
49
+ except Exception as e:
50
+ _write_stderr(str(e))
51
+ return 1
52
+
53
+
54
+ def cmd_inspect(args: argparse.Namespace) -> int:
55
+ """Decode structured data and write its DataFile metadata."""
56
+ try:
57
+ artifact = _decode_artifact(args)
58
+ _write_stdout(artifact.metadata.wrap_for_export(allow_encoding=args.output, **_json_format_opts(args)))
59
+ return 0
60
+ except Exception as e:
61
+ _write_stderr(str(e))
62
+ return 1
63
+
64
+
65
+ def _json_format_opts(args: argparse.Namespace) -> dict[str, Any]:
66
+ """Return common JSON formatting options for CLI export commands."""
67
+ if args.output == "json" and not args.compact:
68
+ return {"indent_2": True}
69
+ return {}
70
+
71
+
72
+ def _merge_workflow(args: argparse.Namespace) -> DataWorkflow:
73
+ """Build a layered merge workflow from CLI arguments."""
74
+ file_paths = args.file_paths
75
+ if len(file_paths) < 2:
76
+ raise ValueError("merge requires at least two files")
77
+
78
+ workflow = DataWorkflow.from_file(file_paths[0], suffix=args.suffix)
79
+ for file_path in file_paths[1:]:
80
+ workflow = workflow.merge_file(file_path, suffix=args.suffix)
81
+ return workflow
82
+
83
+
84
+ def cmd_merge(args: argparse.Namespace) -> int:
85
+ """Merge structured files through DataWorkflow and write or print the result."""
86
+ try:
87
+ workflow = _merge_workflow(args)
88
+ result: WorkflowResult
89
+ if args.write:
90
+ result = workflow.write(args.write, encoding=args.output, allow_empty=args.allow_empty)
91
+ else:
92
+ result = workflow.result()
93
+ _write_stdout(result.wrap_for_export(allow_encoding=args.output, **_json_format_opts(args)))
94
+ return 0
95
+ except Exception as e:
96
+ _write_stderr(str(e))
97
+ return 1
98
+
99
+
100
+ def _transform_workflow(args: argparse.Namespace) -> DataWorkflow:
101
+ """Build a workflow that applies named Tier 2 transforms."""
102
+ steps = args.steps or []
103
+ if not steps:
104
+ raise ValueError("transform requires at least one --step")
105
+
106
+ return _decode_artifact(args).workflow().transform(*steps)
107
+
108
+
109
+ def cmd_transform(args: argparse.Namespace) -> int:
110
+ """Apply named Tier 2 transforms through DataWorkflow."""
111
+ try:
112
+ workflow = _transform_workflow(args)
113
+ result: WorkflowResult
114
+ if args.write:
115
+ result = workflow.write(args.write, encoding=args.output, allow_empty=args.allow_empty)
116
+ else:
117
+ result = workflow.result()
118
+ _write_stdout(result.wrap_for_export(allow_encoding=args.output, **_json_format_opts(args)))
119
+ return 0
120
+ except Exception as e:
121
+ _write_stderr(str(e))
122
+ return 1
123
+
124
+
125
+ def _build_parser() -> argparse.ArgumentParser:
126
+ """Build the top-level Extended Data argument parser."""
127
+ parser = argparse.ArgumentParser(
128
+ prog="extended-data",
129
+ description="CLI for Extended Data primitives, files, and workflows",
130
+ formatter_class=argparse.RawDescriptionHelpFormatter,
131
+ epilog="""
132
+ Examples:
133
+ extended-data decode '{"service": {"name": "api"}}' --suffix json
134
+ extended-data decode --file config.yaml --output json
135
+ extended-data inspect --file config.yaml
136
+ extended-data merge base.yaml env.yaml --output yaml
137
+ extended-data transform --file payload.json --step reconstruct --step unhump
138
+ """,
139
+ )
140
+ subparsers = parser.add_subparsers(dest="command", help="Commands")
141
+
142
+ decode_parser = subparsers.add_parser("decode", help="Decode inline data or a file")
143
+ decode_parser.add_argument("value", nargs="?", help="Inline payload to decode")
144
+ decode_parser.add_argument("--file", dest="file_path", help="File path or URL to decode")
145
+ decode_parser.add_argument("--suffix", help="Input format override")
146
+ decode_parser.add_argument("--output", choices=OUTPUT_ENCODINGS, default="json", help="Output encoding")
147
+ decode_parser.add_argument("--compact", action="store_true", help="Compact JSON output")
148
+ decode_parser.set_defaults(func=cmd_decode)
149
+
150
+ inspect_parser = subparsers.add_parser("inspect", help="Decode data and print artifact metadata")
151
+ inspect_parser.add_argument("value", nargs="?", help="Inline payload to inspect")
152
+ inspect_parser.add_argument("--file", dest="file_path", help="File path or URL to inspect")
153
+ inspect_parser.add_argument("--suffix", help="Input format override")
154
+ inspect_parser.add_argument("--output", choices=OUTPUT_ENCODINGS, default="json", help="Output encoding")
155
+ inspect_parser.add_argument("--compact", action="store_true", help="Compact JSON output")
156
+ inspect_parser.set_defaults(func=cmd_inspect)
157
+
158
+ merge_parser = subparsers.add_parser("merge", help="Deep merge structured files")
159
+ merge_parser.add_argument("file_paths", nargs="+", help="Structured files to merge in order")
160
+ merge_parser.add_argument("--suffix", help="Input format override for all files")
161
+ merge_parser.add_argument("--output", choices=OUTPUT_ENCODINGS, default="json", help="Output encoding")
162
+ merge_parser.add_argument("--compact", action="store_true", help="Compact JSON output")
163
+ merge_parser.add_argument("--write", help="Write merged output to this file")
164
+ merge_parser.add_argument("--allow-empty", action="store_true", help="Allow writing empty merged output")
165
+ merge_parser.set_defaults(func=cmd_merge)
166
+
167
+ transform_parser = subparsers.add_parser("transform", help="Apply named Extended Data transforms")
168
+ transform_parser.add_argument("value", nargs="?", help="Inline payload to transform")
169
+ transform_parser.add_argument("--file", dest="file_path", help="File path or URL to transform")
170
+ transform_parser.add_argument("--suffix", help="Input format override")
171
+ transform_parser.add_argument(
172
+ "--step",
173
+ dest="steps",
174
+ action="append",
175
+ choices=list_data_transform_steps(),
176
+ help="Transform step to apply in order",
177
+ )
178
+ transform_parser.add_argument("--output", choices=OUTPUT_ENCODINGS, default="json", help="Output encoding")
179
+ transform_parser.add_argument("--compact", action="store_true", help="Compact JSON output")
180
+ transform_parser.add_argument("--write", help="Write transformed output to this file")
181
+ transform_parser.add_argument("--allow-empty", action="store_true", help="Allow writing empty transformed output")
182
+ transform_parser.set_defaults(func=cmd_transform)
183
+
184
+ return parser
185
+
186
+
187
+ def main(argv: Sequence[str] | None = None) -> int:
188
+ """Run the Extended Data CLI."""
189
+ args = list(argv) if argv is not None else sys.argv[1:]
190
+ parser = _build_parser()
191
+ parsed = parser.parse_args(args)
192
+
193
+ if not parsed.command:
194
+ parser.print_help()
195
+ return 0
196
+
197
+ try:
198
+ return parsed.func(parsed)
199
+ except KeyboardInterrupt:
200
+ return 130
201
+
202
+
203
+ if __name__ == "__main__":
204
+ sys.exit(main())
@@ -0,0 +1,17 @@
1
+ """Tier 2 extended container classes."""
2
+
3
+ from extended_data.containers.factory import extend_data, to_builtin
4
+ from extended_data.containers.mappings import ExtendedDict
5
+ from extended_data.containers.sequences import ExtendedList, ExtendedSet, ExtendedTuple
6
+ from extended_data.containers.strings import ExtendedString
7
+
8
+
9
+ __all__ = [
10
+ "ExtendedDict",
11
+ "ExtendedList",
12
+ "ExtendedSet",
13
+ "ExtendedString",
14
+ "ExtendedTuple",
15
+ "extend_data",
16
+ "to_builtin",
17
+ ]
@@ -0,0 +1,57 @@
1
+ """Factories for moving between plain data and extended containers."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Mapping
6
+ from typing import Any
7
+
8
+ from extended_data.containers.mappings import ExtendedDict
9
+ from extended_data.containers.sequences import ExtendedList, ExtendedSet, ExtendedTuple
10
+ from extended_data.containers.strings import ExtendedString
11
+ from extended_data.primitives.formats.yaml import LiteralScalarString, YamlPairs, YamlTagged
12
+
13
+
14
+ def extend_data(value: Any) -> Any:
15
+ """Recursively wrap built-in containers in Extended Data containers."""
16
+ if isinstance(value, YamlTagged | YamlPairs | LiteralScalarString):
17
+ return value
18
+ if isinstance(value, ExtendedString | ExtendedDict | ExtendedList | ExtendedSet | ExtendedTuple):
19
+ return value
20
+ if isinstance(value, str):
21
+ return ExtendedString(value)
22
+ if isinstance(value, Mapping):
23
+ return ExtendedDict({key: extend_data(item) for key, item in value.items()})
24
+ if isinstance(value, list):
25
+ return ExtendedList(extend_data(item) for item in value)
26
+ if isinstance(value, tuple):
27
+ return ExtendedTuple(extend_data(item) for item in value)
28
+ if isinstance(value, set | frozenset):
29
+ return ExtendedSet(extend_data(item) for item in value)
30
+ return value
31
+
32
+
33
+ def to_builtin(value: Any) -> Any:
34
+ """Recursively unwrap Extended Data containers to built-in Python values."""
35
+ if isinstance(value, YamlTagged | YamlPairs | LiteralScalarString):
36
+ return value
37
+ if isinstance(value, ExtendedString):
38
+ return str(value)
39
+ if isinstance(value, ExtendedDict):
40
+ return {to_builtin(key): to_builtin(item) for key, item in value.items()}
41
+ if isinstance(value, ExtendedList):
42
+ return [to_builtin(item) for item in value]
43
+ if isinstance(value, ExtendedTuple):
44
+ return tuple(to_builtin(item) for item in value)
45
+ if isinstance(value, ExtendedSet):
46
+ return {to_builtin(item) for item in value}
47
+ if isinstance(value, Mapping):
48
+ return {to_builtin(key): to_builtin(item) for key, item in value.items()}
49
+ if isinstance(value, list):
50
+ return [to_builtin(item) for item in value]
51
+ if isinstance(value, tuple):
52
+ return tuple(to_builtin(item) for item in value)
53
+ if isinstance(value, set):
54
+ return {to_builtin(item) for item in value}
55
+ if isinstance(value, frozenset):
56
+ return frozenset(to_builtin(item) for item in value)
57
+ return value
@@ -0,0 +1,181 @@
1
+ """Extended mapping container built on Tier 1 primitives."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections import UserDict
6
+ from collections.abc import Iterable, Mapping
7
+ from typing import TYPE_CHECKING, Any, Self, overload
8
+
9
+
10
+ if TYPE_CHECKING:
11
+ from _typeshed import SupportsKeysAndGetItem
12
+
13
+ from extended_data.containers.sequences import ExtendedList, ExtendedTuple
14
+
15
+ from extended_data.primitives.mappings import (
16
+ all_values_from_map,
17
+ deduplicate_map,
18
+ deep_merge,
19
+ filter_map,
20
+ first_non_empty_value_from_map,
21
+ flatten_map,
22
+ unhump_map,
23
+ )
24
+ from extended_data.primitives.splitting import split_dict_by_type
25
+ from extended_data.primitives.state import all_non_empty_in_dict, any_non_empty, yield_non_empty
26
+ from extended_data.primitives.types import reconstruct_special_types
27
+
28
+
29
+ class ExtendedDict(UserDict[str, Any]):
30
+ """Dictionary wrapper with chainable primitive operations."""
31
+
32
+ def __init__(self, initialdata: Mapping[str, Any] | None = None, **kwargs: Any) -> None:
33
+ """Initialize the extended dictionary."""
34
+ super().__init__()
35
+ self.update(initialdata or {}, **kwargs)
36
+
37
+ def __setitem__(self, key: str, item: Any) -> None:
38
+ """Set a value while preserving extended nested containers."""
39
+ from extended_data.containers.factory import extend_data
40
+
41
+ self.data[key] = extend_data(item)
42
+
43
+ @overload
44
+ def update(self, other: SupportsKeysAndGetItem[str, Any], /) -> None: ...
45
+
46
+ @overload
47
+ def update(self, other: SupportsKeysAndGetItem[str, Any], /, **kwargs: Any) -> None: ...
48
+
49
+ @overload
50
+ def update(self, other: Iterable[tuple[str, Any]], /) -> None: ...
51
+
52
+ @overload
53
+ def update(self, other: Iterable[tuple[str, Any]], /, **kwargs: Any) -> None: ...
54
+
55
+ @overload
56
+ def update(self, **kwargs: Any) -> None: ...
57
+
58
+ def update(self, *args: Any, **kwargs: Any) -> None: # type: ignore[misc]
59
+ """Update values while preserving extended nested containers."""
60
+ if len(args) > 1:
61
+ msg = f"update expected at most 1 argument, got {len(args)}"
62
+ raise TypeError(msg)
63
+
64
+ if args:
65
+ other = args[0]
66
+ if hasattr(other, "items"):
67
+ for key, value in other.items():
68
+ self[key] = value
69
+ elif hasattr(other, "keys") and hasattr(other, "__getitem__"):
70
+ keys = other.keys()
71
+ for key in keys:
72
+ self[key] = other[key]
73
+ else:
74
+ for key, value in other:
75
+ self[key] = value
76
+
77
+ for key, value in kwargs.items():
78
+ self[key] = value
79
+
80
+ def setdefault(self, key: str, default: Any = None) -> Any:
81
+ """Insert a default while returning the promoted stored value."""
82
+ if key not in self.data:
83
+ self[key] = default
84
+ return self.data[key]
85
+
86
+ def __ior__(self, other: Any) -> Self: # type: ignore[override,misc]
87
+ """Update from a mapping or item iterable while preserving extended containers."""
88
+ self.update(other)
89
+ return self
90
+
91
+ def deep_merge(self, *mappings: Mapping[str, Any]) -> ExtendedDict:
92
+ """Return a deeply merged copy."""
93
+ from extended_data.containers.factory import extend_data, to_builtin
94
+
95
+ return extend_data(deep_merge(to_builtin(self.data), *(to_builtin(mapping) for mapping in mappings)))
96
+
97
+ def flatten(self, *, separator: str = ".") -> ExtendedDict:
98
+ """Return a flattened copy."""
99
+ from extended_data.containers.factory import extend_data, to_builtin
100
+
101
+ return extend_data(flatten_map(to_builtin(self.data), separator=separator))
102
+
103
+ def filter(
104
+ self,
105
+ *,
106
+ allowlist: list[str] | None = None,
107
+ denylist: list[str] | None = None,
108
+ ) -> ExtendedTuple[ExtendedDict]:
109
+ """Return accepted and rejected mapping entries."""
110
+ from extended_data.containers.factory import extend_data, to_builtin
111
+ from extended_data.containers.sequences import ExtendedTuple
112
+
113
+ accepted, rejected = filter_map(to_builtin(self.data), allowlist=allowlist, denylist=denylist)
114
+ return ExtendedTuple((extend_data(accepted), extend_data(rejected)))
115
+
116
+ def compact(self) -> ExtendedDict:
117
+ """Return a copy without values considered empty."""
118
+ from extended_data.containers.factory import extend_data, to_builtin
119
+
120
+ return extend_data(all_non_empty_in_dict(to_builtin(self.data)))
121
+
122
+ def deduplicate(self) -> ExtendedDict:
123
+ """Return a copy with nested duplicate list values removed."""
124
+ from extended_data.containers.factory import extend_data, to_builtin
125
+
126
+ return extend_data(deduplicate_map(to_builtin(self.data)))
127
+
128
+ def unhump(self, *, drop_without_prefix: str | None = None) -> ExtendedDict:
129
+ """Return a copy with camelCase keys converted to snake_case."""
130
+ from extended_data.containers.factory import extend_data, to_builtin
131
+
132
+ return extend_data(unhump_map(to_builtin(self.data), drop_without_prefix=drop_without_prefix))
133
+
134
+ def all_values(self) -> ExtendedList[Any]:
135
+ """Return all values from the nested mapping."""
136
+ from extended_data.containers.factory import extend_data, to_builtin
137
+
138
+ return extend_data(all_values_from_map(to_builtin(self.data)))
139
+
140
+ def split_by_type(self, *, primitive_only: bool = False) -> ExtendedDict:
141
+ """Return mapping entries grouped by value type name."""
142
+ from extended_data.containers.factory import extend_data, to_builtin
143
+
144
+ grouped = split_dict_by_type(to_builtin(self.data), primitive_only=primitive_only)
145
+ return extend_data({type_key.__name__: values for type_key, values in grouped.items()})
146
+
147
+ def first_non_empty_value(self, *keys: str) -> Any:
148
+ """Return the first non-empty value for the provided keys."""
149
+ from extended_data.containers.factory import extend_data, to_builtin
150
+
151
+ return extend_data(first_non_empty_value_from_map(to_builtin(self.data), *keys))
152
+
153
+ def first_non_empty_entry(self, *keys: str) -> ExtendedDict:
154
+ """Return the first non-empty keyed entry for the provided keys."""
155
+ from extended_data.containers.factory import extend_data, to_builtin
156
+
157
+ return extend_data(any_non_empty(to_builtin(self.data), *keys))
158
+
159
+ def non_empty_entries(self, *keys: str) -> ExtendedList[ExtendedDict]:
160
+ """Return all non-empty keyed entries for the provided keys."""
161
+ from extended_data.containers.factory import extend_data, to_builtin
162
+
163
+ return extend_data(list(yield_non_empty(to_builtin(self.data), *keys)))
164
+
165
+ def reconstruct_special_types(self, *, fail_silently: bool = False) -> ExtendedDict:
166
+ """Return a copy with string-like special values reconstructed."""
167
+ from extended_data.containers.factory import extend_data, to_builtin
168
+
169
+ return extend_data(reconstruct_special_types(to_builtin(self.data), fail_silently=fail_silently))
170
+
171
+ def to_export_safe(self, *, export_to_yaml: bool = False) -> Any:
172
+ """Return this mapping converted to export-safe primitive data."""
173
+ from extended_data.io.exporters import make_raw_data_export_safe
174
+
175
+ return make_raw_data_export_safe(self.data, export_to_yaml=export_to_yaml)
176
+
177
+ def wrap_for_export(self, allow_encoding: bool | str = True, **format_opts: Any) -> str:
178
+ """Return this mapping wrapped as an encoded export string."""
179
+ from extended_data.io.exporters import wrap_raw_data_for_export
180
+
181
+ return wrap_raw_data_for_export(self.data, allow_encoding=allow_encoding, **format_opts)