nested-mapping 0.12.0__tar.gz → 0.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.
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/CHANGELOG.md +5 -0
- {nested_mapping-0.12.0/src/nested_mapping.egg-info → nested_mapping-0.12.1}/PKG-INFO +1 -1
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/__version__.py +3 -3
- nested_mapping-0.12.1/src/nested_mapping/tools/match.py +274 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1/src/nested_mapping.egg-info}/PKG-INFO +1 -1
- nested_mapping-0.12.1/src/nested_mapping.egg-info/scm_version.json +8 -0
- nested_mapping-0.12.1/tests/test_match.py +757 -0
- nested_mapping-0.12.0/src/nested_mapping/tools/match.py +0 -128
- nested_mapping-0.12.0/src/nested_mapping.egg-info/scm_version.json +0 -8
- nested_mapping-0.12.0/tests/test_match.py +0 -73
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.flake8 +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.gitignore +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.gitlab-ci.yml +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/CONTRIBUTING.md +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/LICENSE +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/README.md +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/conftest.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/pyproject.toml +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/pytest.ini +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/setup.cfg +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/__init__.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/class_wrapper.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/ipython.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/nested_mapping.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/nested_mapping_access.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/__init__.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/filter.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/map.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/reorder_key.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/typing.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/visitor.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/SOURCES.txt +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/dependency_links.txt +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/requires.txt +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/scm_file_list.json +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/top_level.txt +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_filter.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_map.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_nested_mapping.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_nested_mapping_update.py +0 -0
- {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_visitor.py +0 -0
|
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
6
|
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
7
7
|
|
|
8
|
+
## [0.12.1] - 2026-10-29
|
|
9
|
+
|
|
10
|
+
- fix: update check between of left-handed keys and indices in right-handed keys in `match_keys_to_key`
|
|
11
|
+
- refactor: update code of `match_keys_to_key`
|
|
12
|
+
|
|
8
13
|
## [0.12.0] - 2026-10-01
|
|
9
14
|
|
|
10
15
|
- add `skip_fcn` argument for `walk*` methods to skip items from iteration.
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.12.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 12,
|
|
21
|
+
__version__ = version = '0.12.1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 12, 1)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g70b286d13'
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from contextlib import suppress
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
from ordered_set import OrderedSet
|
|
7
|
+
|
|
8
|
+
from ..typing import Key, KeyLike, TupleKey, properkey, setkey
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_keys_consistent_symmetric(
|
|
15
|
+
set_key_left: set | OrderedSet, set_key_right: set | OrderedSet
|
|
16
|
+
) -> bool:
|
|
17
|
+
return set_key_left.issubset(set_key_right) or set_key_right.issubset(set_key_left)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def set_keys_consistent_left_in_right(
|
|
21
|
+
set_key_left: set | OrderedSet, set_key_right: set | OrderedSet
|
|
22
|
+
) -> bool:
|
|
23
|
+
return set_key_left.issubset(set_key_right)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def set_keys_consistent_right_in_left(
|
|
27
|
+
set_key_left: set | OrderedSet, set_key_right: set | OrderedSet
|
|
28
|
+
) -> bool:
|
|
29
|
+
return set_key_right.issubset(set_key_left)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def match_keys_to_key(
|
|
33
|
+
keys_left: Sequence[KeyLike],
|
|
34
|
+
key_right_tuple: TupleKey,
|
|
35
|
+
key_right_set: set | OrderedSet,
|
|
36
|
+
keys_consistent: Callable[[set | OrderedSet, set | OrderedSet], bool],
|
|
37
|
+
fcn: Callable[[TupleKey, TupleKey], Any],
|
|
38
|
+
*,
|
|
39
|
+
fcn_on_reject: Callable[[TupleKey, TupleKey], Any] | None = None,
|
|
40
|
+
collect_skipped_right_keys: bool = False,
|
|
41
|
+
collect_skipped_left_keys: bool = False,
|
|
42
|
+
skipped_left_keys: set | None = None,
|
|
43
|
+
processed_left_keys: set | None = None,
|
|
44
|
+
processed_right_keys: set | None = None,
|
|
45
|
+
skipped_right_keys: set | None = None,
|
|
46
|
+
require_no_duplicate_right_matches: bool = False,
|
|
47
|
+
) -> bool:
|
|
48
|
+
assert not collect_skipped_right_keys or (
|
|
49
|
+
processed_right_keys is not None and skipped_right_keys is not None
|
|
50
|
+
)
|
|
51
|
+
assert not collect_skipped_left_keys or (
|
|
52
|
+
processed_left_keys is not None and skipped_left_keys is not None
|
|
53
|
+
)
|
|
54
|
+
right_key_processed = False
|
|
55
|
+
key_left_match = None
|
|
56
|
+
for key_left in keys_left:
|
|
57
|
+
key_left_tuple = properkey(key_left)
|
|
58
|
+
key_left_set = OrderedSet(key_left_tuple)
|
|
59
|
+
|
|
60
|
+
if not keys_consistent(key_left_set, key_right_set):
|
|
61
|
+
if (
|
|
62
|
+
collect_skipped_left_keys
|
|
63
|
+
and key_left_tuple
|
|
64
|
+
not in processed_left_keys # pyright: ignore [reportOperatorIssue]
|
|
65
|
+
):
|
|
66
|
+
skipped_left_keys.add( # pyright: ignore [reportOptionalMemberAccess]
|
|
67
|
+
key_left_tuple
|
|
68
|
+
)
|
|
69
|
+
if (
|
|
70
|
+
collect_skipped_right_keys
|
|
71
|
+
and key_right_tuple
|
|
72
|
+
not in processed_right_keys # pyright: ignore [reportOperatorIssue]
|
|
73
|
+
):
|
|
74
|
+
skipped_right_keys.add( # pyright: ignore [reportOptionalMemberAccess]
|
|
75
|
+
key_right_tuple
|
|
76
|
+
)
|
|
77
|
+
if fcn_on_reject:
|
|
78
|
+
fcn_on_reject(key_left_tuple, key_right_tuple)
|
|
79
|
+
continue
|
|
80
|
+
|
|
81
|
+
fcn(key_left_tuple, key_right_tuple)
|
|
82
|
+
if require_no_duplicate_right_matches and right_key_processed:
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"match_keys: right key {key_right_tuple!s} was matched more than once to "
|
|
85
|
+
f"{key_left_match!s}; {key_left_tuple!s}..."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
right_key_processed = True
|
|
89
|
+
key_left_match = key_left_tuple
|
|
90
|
+
|
|
91
|
+
if collect_skipped_left_keys:
|
|
92
|
+
with suppress(KeyError):
|
|
93
|
+
skipped_left_keys.remove(
|
|
94
|
+
key_left_tuple
|
|
95
|
+
) # pyright: ignore [reportOptionalMemberAccess]
|
|
96
|
+
processed_left_keys.add(key_left_tuple) # pyright: ignore [reportOptionalMemberAccess]
|
|
97
|
+
|
|
98
|
+
if collect_skipped_right_keys:
|
|
99
|
+
with suppress(KeyError):
|
|
100
|
+
skipped_right_keys.remove(
|
|
101
|
+
key_right_tuple
|
|
102
|
+
) # pyright: ignore [reportOptionalMemberAccess]
|
|
103
|
+
processed_right_keys.add(key_right_tuple) # pyright: ignore [reportOptionalMemberAccess]
|
|
104
|
+
|
|
105
|
+
return right_key_processed
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def zip_match_keys_to_key(
|
|
109
|
+
keys_left: Sequence[KeyLike],
|
|
110
|
+
key_right_tuple: TupleKey,
|
|
111
|
+
key_right_set: set | OrderedSet,
|
|
112
|
+
keys_consistent_fcn: Callable[[set | OrderedSet, set | OrderedSet], bool],
|
|
113
|
+
):
|
|
114
|
+
for key_left in keys_left:
|
|
115
|
+
key_left_tuple = properkey(key_left)
|
|
116
|
+
key_left_set = OrderedSet(key_left_tuple)
|
|
117
|
+
if keys_consistent_fcn(key_left_set, key_right_set):
|
|
118
|
+
yield key_left_tuple, key_right_tuple
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def zip_match_keys_to_keys(
|
|
122
|
+
keys_left: Sequence[KeyLike],
|
|
123
|
+
keys_right: Sequence[KeyLike],
|
|
124
|
+
keys_consistent_fcn: Callable[[set | OrderedSet, set | OrderedSet], bool],
|
|
125
|
+
):
|
|
126
|
+
for key_right in keys_right:
|
|
127
|
+
key_right_tuple = properkey(key_right)
|
|
128
|
+
key_right_set = OrderedSet(key_right_tuple)
|
|
129
|
+
|
|
130
|
+
yield from zip_match_keys_to_key(
|
|
131
|
+
keys_left, key_right_tuple, key_right_set, keys_consistent_fcn
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def match_keys(
|
|
136
|
+
keys_left_seq: Sequence[Sequence[KeyLike]],
|
|
137
|
+
keys_right: Iterable[KeyLike],
|
|
138
|
+
fcn: Callable[[int, TupleKey, TupleKey], Any],
|
|
139
|
+
*,
|
|
140
|
+
fcn_outer_before: Callable[[TupleKey], Any] | None = None,
|
|
141
|
+
fcn_outer_after: Callable[[TupleKey], Any] | None = None,
|
|
142
|
+
fcn_on_reject: Callable[[int, TupleKey, TupleKey], Any] | None = None,
|
|
143
|
+
left_in_right: bool = True,
|
|
144
|
+
right_in_left: bool = True,
|
|
145
|
+
require_all_right_keys_processed: bool = True,
|
|
146
|
+
require_all_left_keys_processed: bool = True,
|
|
147
|
+
require_all_left_keys_match_right: bool = True,
|
|
148
|
+
require_no_duplicate_right_matches: bool = False,
|
|
149
|
+
unmatched_right_keys_should_contain: Sequence[KeyLike] | None = None,
|
|
150
|
+
skippable_left_keys_should_contain: Sequence[KeyLike] | None = None,
|
|
151
|
+
) -> None:
|
|
152
|
+
if isinstance(keys_right, str):
|
|
153
|
+
raise RuntimeError("`keys_right` should be iterable, but not string")
|
|
154
|
+
if not keys_left_seq:
|
|
155
|
+
raise RuntimeError("Left sequence is empty")
|
|
156
|
+
|
|
157
|
+
if left_in_right and right_in_left:
|
|
158
|
+
keys_consistent_fcn = set_keys_consistent_symmetric
|
|
159
|
+
elif left_in_right:
|
|
160
|
+
keys_consistent_fcn = set_keys_consistent_left_in_right
|
|
161
|
+
elif right_in_left:
|
|
162
|
+
keys_consistent_fcn = set_keys_consistent_right_in_left
|
|
163
|
+
else:
|
|
164
|
+
raise RuntimeError(
|
|
165
|
+
"At least on of the flags should be true: right_in_left or left_in_right"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
skipped_right_keys = []
|
|
169
|
+
processed_right_keys = set()
|
|
170
|
+
processed_left_keys = set()
|
|
171
|
+
skipped_left_keys = set()
|
|
172
|
+
skipped_right_keys_for_left = []
|
|
173
|
+
processed_right_keys_for_left = []
|
|
174
|
+
|
|
175
|
+
collect_skipped_left_keys = bool(
|
|
176
|
+
require_all_left_keys_processed or skippable_left_keys_should_contain is not None
|
|
177
|
+
)
|
|
178
|
+
collect_skipped_right_keys = bool(
|
|
179
|
+
require_all_left_keys_match_right or unmatched_right_keys_should_contain is not None
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
i_left = -1
|
|
183
|
+
for key_right in keys_right:
|
|
184
|
+
key_right_tuple = properkey(key_right)
|
|
185
|
+
key_right_set = OrderedSet(key_right_tuple)
|
|
186
|
+
if fcn_outer_before is not None:
|
|
187
|
+
fcn_outer_before(key_right_tuple)
|
|
188
|
+
|
|
189
|
+
right_key_processed = False
|
|
190
|
+
for i_left, keys_left in enumerate(keys_left_seq):
|
|
191
|
+
if i_left >= len(skipped_right_keys_for_left):
|
|
192
|
+
processed_right_keys_for_left.append(processed_right_keys_i := set())
|
|
193
|
+
skipped_right_keys_for_left.append(skipped_right_keys_i := set())
|
|
194
|
+
else:
|
|
195
|
+
processed_right_keys_i = processed_right_keys_for_left[i_left]
|
|
196
|
+
skipped_right_keys_i = skipped_right_keys_for_left[i_left]
|
|
197
|
+
|
|
198
|
+
right_key_processed = match_keys_to_key(
|
|
199
|
+
keys_left,
|
|
200
|
+
key_right_tuple,
|
|
201
|
+
key_right_set,
|
|
202
|
+
keys_consistent_fcn,
|
|
203
|
+
lambda *args: fcn(i_left, *args),
|
|
204
|
+
fcn_on_reject=(
|
|
205
|
+
(lambda *args: fcn_on_reject(i_left, *args))
|
|
206
|
+
if fcn_on_reject is not None
|
|
207
|
+
else None
|
|
208
|
+
),
|
|
209
|
+
collect_skipped_left_keys=collect_skipped_left_keys,
|
|
210
|
+
collect_skipped_right_keys=collect_skipped_right_keys,
|
|
211
|
+
skipped_left_keys=skipped_left_keys,
|
|
212
|
+
processed_left_keys=processed_left_keys,
|
|
213
|
+
skipped_right_keys=skipped_right_keys_i,
|
|
214
|
+
processed_right_keys=processed_right_keys_i,
|
|
215
|
+
require_no_duplicate_right_matches=require_no_duplicate_right_matches,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
if fcn_outer_after is not None:
|
|
219
|
+
fcn_outer_after(key_right_tuple)
|
|
220
|
+
|
|
221
|
+
if right_key_processed:
|
|
222
|
+
if require_all_left_keys_match_right:
|
|
223
|
+
processed_right_keys.add(key_right_tuple)
|
|
224
|
+
elif require_all_right_keys_processed:
|
|
225
|
+
skipped_right_keys.append(key_right_tuple)
|
|
226
|
+
|
|
227
|
+
for i, (skipped_left_keys_i, processed_left_keys_i) in enumerate(
|
|
228
|
+
zip(skipped_right_keys_for_left, processed_right_keys_for_left)
|
|
229
|
+
):
|
|
230
|
+
if processed_left_keys_i != processed_right_keys:
|
|
231
|
+
if require_all_left_keys_match_right:
|
|
232
|
+
raise ValueError(
|
|
233
|
+
f"match_keys: left sequence {i} did not provide keys {skipped_left_keys_i!s}"
|
|
234
|
+
)
|
|
235
|
+
_check_for_unexpectedly_skipped_keys(
|
|
236
|
+
skipped_left_keys_i,
|
|
237
|
+
unmatched_right_keys_should_contain,
|
|
238
|
+
kind=f"missing left ({i})",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
if skipped_left_keys:
|
|
242
|
+
if require_all_left_keys_processed:
|
|
243
|
+
raise ValueError(f"match_keys: there were unprocessed left keys {skipped_left_keys!s}")
|
|
244
|
+
_check_for_unexpectedly_skipped_keys(
|
|
245
|
+
skipped_left_keys, unmatched_right_keys_should_contain, kind="skipped left"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
if require_all_right_keys_processed and skipped_right_keys:
|
|
249
|
+
raise ValueError(f"match_keys: there were unprocessed right keys {skipped_right_keys!s}")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def _check_for_unexpectedly_skipped_keys(
|
|
253
|
+
skipped_keys: Sequence[Key] | set[Key], should_contain: Sequence[KeyLike] | None, kind: str
|
|
254
|
+
):
|
|
255
|
+
if unexpectedly_skipped_keys := _get_unexpectedly_skipped_keys(skipped_keys, should_contain):
|
|
256
|
+
raise ValueError(
|
|
257
|
+
f"match_keys: there were {kind} keys {skipped_keys!s}. "
|
|
258
|
+
f"{unexpectedly_skipped_keys} should not have been skipped."
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _get_unexpectedly_skipped_keys(
|
|
263
|
+
skipped_keys: Sequence[Key] | set[Key], should_contain: Sequence[KeyLike] | None
|
|
264
|
+
) -> Sequence[Key]:
|
|
265
|
+
if not should_contain:
|
|
266
|
+
return []
|
|
267
|
+
|
|
268
|
+
setkeys = tuple(setkey(keypart) for keypart in should_contain)
|
|
269
|
+
incorrectly_skipped_keys = []
|
|
270
|
+
for skipped_key in skipped_keys:
|
|
271
|
+
if not any(setkey.issubset(skipped_key) for setkey in setkeys):
|
|
272
|
+
incorrectly_skipped_keys.append(skipped_key)
|
|
273
|
+
|
|
274
|
+
return incorrectly_skipped_keys
|