nested-mapping 0.12.0__tar.gz → 0.13.0__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.
Files changed (41) hide show
  1. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/CHANGELOG.md +9 -0
  2. {nested_mapping-0.12.0/src/nested_mapping.egg-info → nested_mapping-0.13.0}/PKG-INFO +1 -1
  3. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/__version__.py +3 -3
  4. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/tools/map.py +54 -20
  5. nested_mapping-0.13.0/src/nested_mapping/tools/match.py +282 -0
  6. {nested_mapping-0.12.0 → nested_mapping-0.13.0/src/nested_mapping.egg-info}/PKG-INFO +1 -1
  7. nested_mapping-0.13.0/src/nested_mapping.egg-info/scm_version.json +8 -0
  8. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/tests/test_map.py +45 -0
  9. nested_mapping-0.13.0/tests/test_match.py +908 -0
  10. nested_mapping-0.12.0/src/nested_mapping/tools/match.py +0 -128
  11. nested_mapping-0.12.0/src/nested_mapping.egg-info/scm_version.json +0 -8
  12. nested_mapping-0.12.0/tests/test_match.py +0 -73
  13. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/.flake8 +0 -0
  14. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/.gitignore +0 -0
  15. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/.gitlab-ci.yml +0 -0
  16. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/CONTRIBUTING.md +0 -0
  17. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/LICENSE +0 -0
  18. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/README.md +0 -0
  19. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/conftest.py +0 -0
  20. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/pyproject.toml +0 -0
  21. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/pytest.ini +0 -0
  22. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/setup.cfg +0 -0
  23. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/__init__.py +0 -0
  24. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/class_wrapper.py +0 -0
  25. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/ipython.py +0 -0
  26. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/nested_mapping.py +0 -0
  27. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/nested_mapping_access.py +0 -0
  28. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/tools/__init__.py +0 -0
  29. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/tools/filter.py +0 -0
  30. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/tools/reorder_key.py +0 -0
  31. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/typing.py +0 -0
  32. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping/visitor.py +0 -0
  33. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping.egg-info/SOURCES.txt +0 -0
  34. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping.egg-info/dependency_links.txt +0 -0
  35. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping.egg-info/requires.txt +0 -0
  36. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping.egg-info/scm_file_list.json +0 -0
  37. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/src/nested_mapping.egg-info/top_level.txt +0 -0
  38. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/tests/test_filter.py +0 -0
  39. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/tests/test_nested_mapping.py +0 -0
  40. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/tests/test_nested_mapping_update.py +0 -0
  41. {nested_mapping-0.12.0 → nested_mapping-0.13.0}/tests/test_visitor.py +0 -0
@@ -5,6 +5,15 @@ 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.13.0] - 2026-11-03
9
+
10
+ - feature: enable `remap_keys` and `make_reorder_fcn` to insert extra levels of nesting.
11
+ Should be enabled explicitly via `allow_insert_items`.
12
+
13
+ ## [0.12.1] - 2026-10-29
14
+
15
+ - feature: extra protection for `match_keys` to avoid keys being skipped. Can be disabled.
16
+
8
17
  ## [0.12.0] - 2026-10-01
9
18
 
10
19
  - add `skip_fcn` argument for `walk*` methods to skip items from iteration.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nested-mapping
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: A wrapper and a toolkit for working with nested mappings in Python
5
5
  Author-email: DAGModelling Team <dagflow@jinr.ru>
6
6
  Maintainer-email: DAGModelling Team <dagflow@jinr.ru>
@@ -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.0'
22
- __version_tuple__ = version_tuple = (0, 12, 0)
21
+ __version__ = version = '0.13.0'
22
+ __version_tuple__ = version_tuple = (0, 13, 0)
23
23
 
24
- __commit_id__ = commit_id = 'gd3a44dafb'
24
+ __commit_id__ = commit_id = 'g4a18ec946'
@@ -52,11 +52,12 @@ def remap_items(
52
52
  | tuple[tuple[str, ...], tuple[str, ...]]
53
53
  | list[int]
54
54
  | list[list[str]]
55
- | Mapping[str, list[str] | tuple[str]]
55
+ | Mapping[str, list[str] | tuple[str, ...]]
56
56
  | None
57
57
  ) = None,
58
58
  skip_indices_source: Sequence[KeyLike | set] | None = None,
59
59
  skip_indices_target: Sequence[KeyLike | set] | None = None,
60
+ allow_insert_items: bool = False,
60
61
  fcn: Callable[[Any], Any] | None = None,
61
62
  verbose: bool = False,
62
63
  ) -> NestedMapping:
@@ -67,7 +68,7 @@ def remap_items(
67
68
 
68
69
  skip_source = _make_skip_fcn(skip_indices_source)
69
70
  skip_target = _make_skip_fcn(skip_indices_target)
70
- reorder = make_reorder_function(reorder_indices)
71
+ reorder = make_reorder_function(reorder_indices, allow_insert_items=allow_insert_items)
71
72
 
72
73
  if fcn is None:
73
74
  fcn = lambda o: o
@@ -79,15 +80,11 @@ def remap_items(
79
80
  for key, value in source.walkitems():
80
81
  if skip_source(key):
81
82
  continue
82
- for newkey in product(
83
- *tuple(rename_indices.get(skey, (skey,)) for skey in key)
84
- ):
83
+ for newkey in product(*tuple(rename_indices.get(skey, (skey,)) for skey in key)):
85
84
  newkey_ordered = reorder(newkey)
86
85
  if skip_target(newkey_ordered):
87
86
  if verbose:
88
- print(
89
- f"remap: skip {'.'.join(key)} → {'.'.join(newkey_ordered)}"
90
- )
87
+ print(f"remap: skip {'.'.join(key)} → {'.'.join(newkey_ordered)}")
91
88
  continue
92
89
  if verbose:
93
90
  print(
@@ -96,6 +93,9 @@ def remap_items(
96
93
  target[newkey_ordered] = fcn(value)
97
94
  else:
98
95
  for key, value in source.walkitems():
96
+ if skip_source(key):
97
+ print(f"remap: skip {'.'.join(key)}")
98
+ continue
99
99
  newkey_ordered = reorder(key)
100
100
  if skip_target(newkey_ordered):
101
101
  if verbose:
@@ -128,39 +128,73 @@ def make_reorder_function(
128
128
  | tuple[tuple[str, ...], tuple[str, ...]]
129
129
  | Sequence[int]
130
130
  | Sequence[Sequence[str]]
131
- | Mapping[str, list[str] | tuple[str]]
131
+ | Mapping[str, list[str] | tuple[str, ...]]
132
132
  | None
133
133
  ),
134
134
  *,
135
135
  allow_skip_items: bool = False,
136
+ allow_insert_items: bool = False,
136
137
  ) -> Callable:
138
+ order_from_extended: list[None | str] = []
137
139
  match reorder_indices:
138
140
  case [int(), *_]:
139
- index_order = reorder_indices
141
+ index_order = list(reorder_indices)
140
142
  len_from = len(index_order)
143
+
144
+ match_length_from = not allow_skip_items
145
+
146
+ if allow_insert_items:
147
+ raise ValueError(
148
+ "make_reorder_function: inserting items is forbidden with tuple[int] arguments"
149
+ )
141
150
  case [[str(), *_] as order_from, [str(), *_] as order_to] | {
142
151
  "from": [str(), *_] as order_from,
143
152
  "to": [str(), *_] as order_to,
144
153
  }:
154
+ match_length_from = True
155
+
145
156
  len_from = len(order_from)
146
- try:
147
- index_order = tuple(order_from.index(item) for item in order_to)
148
- except ValueError:
149
- raise ValueError(
150
- f"Inconsistent order definitions {order_from} and {order_to}"
151
- )
157
+ if allow_insert_items:
158
+ order_from_extended: list[None | str] = [None] * len(order_from)
159
+ index_order: list[None | int] = [None] * len(order_to)
160
+ not_processed_indices = set(range(len(order_from)))
161
+ for i, name in enumerate(order_to):
162
+ try:
163
+ index = order_from.index(name)
164
+ not_processed_indices.remove(index)
165
+ except ValueError:
166
+ index = len(order_from_extended)
167
+ order_from_extended.append(name)
168
+ index_order[i] = index
169
+
170
+ if not_processed_indices and not allow_skip_items:
171
+ raise ValueError(
172
+ f"Inconsistent order definitions (can not skip) {order_from}→{order_to}"
173
+ )
174
+ else:
175
+ try:
176
+ index_order = list(order_from.index(item) for item in order_to)
177
+ except ValueError:
178
+ raise ValueError(
179
+ f"Inconsistent order definitions (can neigher skip no insert) {order_from}→{order_to}"
180
+ )
181
+
152
182
  case None:
153
183
  len_from = None
184
+ match_length_from = False
154
185
  allow_skip_items = True
155
186
  return lambda key: key
156
187
  case _:
157
188
  raise ValueError(f"Invalid order specification: {reorder_indices}")
158
189
 
159
190
  def reorder_indices_fcn(key: Sequence):
160
- if not allow_skip_items and len(key) != len_from:
161
- raise ValueError(
162
- f"inconsistent index length: {len(index_order)} vs required {len(key)}"
191
+ if match_length_from and len(key) != len_from:
192
+ raise ValueError(f"inconsistent key length: {len(key)} vs required {len_from}")
193
+ if allow_insert_items:
194
+ return key.__class__(
195
+ key[idx] if idx < len(key) else order_from_extended[idx] for idx in index_order
163
196
  )
164
- return key.__class__(key[idx] for idx in index_order)
197
+ else:
198
+ return key.__class__(key[idx] for idx in index_order)
165
199
 
166
200
  return reorder_indices_fcn
@@ -0,0 +1,282 @@
1
+ from __future__ import annotations
2
+
3
+ from contextlib import suppress
4
+ from typing import Any, TYPE_CHECKING, TypeVar
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
+ SetType = TypeVar("SetType", set[str], OrderedSet[str])
14
+
15
+
16
+ def set_keys_consistent_ordered(
17
+ set_key_left: OrderedSet[str], set_key_right: OrderedSet[str]
18
+ ) -> bool:
19
+ return set_key_left == set_key_right
20
+
21
+
22
+ def set_keys_consistent_unordered(set_key_left: SetType, set_key_right: SetType) -> bool:
23
+ return not set_key_left.symmetric_difference(set_key_right)
24
+
25
+
26
+ def set_keys_consistent_symmetric(set_key_left: SetType, set_key_right: SetType) -> bool:
27
+ return set_key_left.issubset(set_key_right) or set_key_right.issubset(set_key_left)
28
+
29
+
30
+ def set_keys_consistent_left_in_right(set_key_left: SetType, set_key_right: SetType) -> bool:
31
+ return set_key_left.issubset(set_key_right)
32
+
33
+
34
+ def set_keys_consistent_right_in_left(set_key_left: SetType, set_key_right: SetType) -> bool:
35
+ return set_key_right.issubset(set_key_left)
36
+
37
+
38
+ def match_keys_to_key(
39
+ keys_left: Sequence[KeyLike],
40
+ key_right_tuple: TupleKey,
41
+ key_right_set: SetType,
42
+ keys_consistent_fcn: Callable[[SetType, SetType], bool],
43
+ fcn: Callable[[TupleKey, TupleKey], Any],
44
+ *,
45
+ fcn_on_reject: Callable[[TupleKey, TupleKey], Any] | None = None,
46
+ collect_skipped_right_keys: bool = False,
47
+ collect_skipped_left_keys: bool = False,
48
+ skipped_left_keys: set | None = None,
49
+ processed_left_keys: set | None = None,
50
+ processed_right_keys: set | None = None,
51
+ skipped_right_keys: set | None = None,
52
+ require_no_duplicate_right_matches: bool = False,
53
+ ) -> bool:
54
+ assert not collect_skipped_right_keys or (
55
+ processed_right_keys is not None and skipped_right_keys is not None
56
+ )
57
+ assert not collect_skipped_left_keys or (
58
+ processed_left_keys is not None and skipped_left_keys is not None
59
+ )
60
+ right_key_processed = False
61
+ key_left_match = None
62
+ for key_left in keys_left:
63
+ key_left_tuple = properkey(key_left)
64
+ key_left_set = OrderedSet(key_left_tuple)
65
+
66
+ if not keys_consistent_fcn(key_left_set, key_right_set): # pyright: ignore [reportArgumentType]
67
+ if (
68
+ collect_skipped_left_keys
69
+ and key_left_tuple
70
+ not in processed_left_keys # pyright: ignore [reportOperatorIssue]
71
+ ):
72
+ skipped_left_keys.add( # pyright: ignore [reportOptionalMemberAccess]
73
+ key_left_tuple
74
+ )
75
+ if (
76
+ collect_skipped_right_keys
77
+ and key_right_tuple
78
+ not in processed_right_keys # pyright: ignore [reportOperatorIssue]
79
+ ):
80
+ skipped_right_keys.add( # pyright: ignore [reportOptionalMemberAccess]
81
+ key_right_tuple
82
+ )
83
+ if fcn_on_reject:
84
+ fcn_on_reject(key_left_tuple, key_right_tuple)
85
+ continue
86
+
87
+ fcn(key_left_tuple, key_right_tuple)
88
+ if require_no_duplicate_right_matches and right_key_processed:
89
+ raise ValueError(
90
+ f"match_keys: right key {key_right_tuple!s} was matched more than once to "
91
+ f"{key_left_match!s}; {key_left_tuple!s}..."
92
+ )
93
+
94
+ right_key_processed = True
95
+ key_left_match = key_left_tuple
96
+
97
+ if collect_skipped_left_keys:
98
+ with suppress(KeyError):
99
+ skipped_left_keys.remove(
100
+ key_left_tuple
101
+ ) # pyright: ignore [reportOptionalMemberAccess]
102
+ processed_left_keys.add(key_left_tuple) # pyright: ignore [reportOptionalMemberAccess]
103
+
104
+ if collect_skipped_right_keys:
105
+ with suppress(KeyError):
106
+ skipped_right_keys.remove(
107
+ key_right_tuple
108
+ ) # pyright: ignore [reportOptionalMemberAccess]
109
+ processed_right_keys.add(
110
+ key_right_tuple
111
+ ) # pyright: ignore [reportOptionalMemberAccess]
112
+
113
+ return right_key_processed
114
+
115
+
116
+ def zip_match_keys_to_key(
117
+ keys_left: Sequence[KeyLike],
118
+ key_right_tuple: TupleKey,
119
+ key_right_set: SetType,
120
+ keys_consistent_fcn: Callable[[SetType, SetType], bool],
121
+ ):
122
+ for key_left in keys_left:
123
+ key_left_tuple = properkey(key_left)
124
+ key_left_set = OrderedSet(key_left_tuple)
125
+ if keys_consistent_fcn(key_left_set, key_right_set): # pyright: ignore [reportArgumentType]
126
+ yield key_left_tuple, key_right_tuple
127
+
128
+
129
+ def zip_match_keys_to_keys(
130
+ keys_left: Sequence[KeyLike],
131
+ keys_right: Sequence[KeyLike],
132
+ keys_consistent_fcn: Callable[[SetType, SetType], bool],
133
+ ):
134
+ for key_right in keys_right:
135
+ key_right_tuple = properkey(key_right)
136
+ key_right_set = OrderedSet(key_right_tuple)
137
+
138
+ yield from zip_match_keys_to_key(
139
+ keys_left, key_right_tuple, key_right_set, keys_consistent_fcn # pyright: ignore [reportArgumentType]
140
+ )
141
+
142
+
143
+ def match_keys(
144
+ keys_left_seq: Sequence[Sequence[KeyLike]],
145
+ keys_right: Iterable[KeyLike],
146
+ fcn: Callable[[int, TupleKey, TupleKey], Any],
147
+ *,
148
+ fcn_outer_before: Callable[[TupleKey], Any] | None = None,
149
+ fcn_outer_after: Callable[[TupleKey], Any] | None = None,
150
+ fcn_on_reject: Callable[[int, TupleKey, TupleKey], Any] | None = None,
151
+ left_in_right: bool = True,
152
+ right_in_left: bool = True,
153
+ require_all_right_keys_processed: bool = True,
154
+ require_all_left_keys_processed: bool = True,
155
+ require_all_left_keys_match_right: bool = True,
156
+ require_no_duplicate_right_matches: bool = False,
157
+ unmatched_right_keys_should_contain: Sequence[KeyLike] | None = None,
158
+ skippable_left_keys_should_contain: Sequence[KeyLike] | None = None,
159
+ ) -> None:
160
+ if isinstance(keys_right, str):
161
+ raise RuntimeError("`keys_right` should be iterable, but not string")
162
+ if not keys_left_seq:
163
+ raise RuntimeError("Left sequence is empty")
164
+
165
+ if left_in_right and right_in_left:
166
+ keys_consistent_fcn = set_keys_consistent_symmetric
167
+ elif left_in_right:
168
+ keys_consistent_fcn = set_keys_consistent_left_in_right
169
+ elif right_in_left:
170
+ keys_consistent_fcn = set_keys_consistent_right_in_left
171
+ else:
172
+ raise RuntimeError(
173
+ "At least on of the flags should be true: right_in_left or left_in_right"
174
+ )
175
+
176
+ skipped_right_keys = []
177
+ processed_right_keys = set()
178
+ processed_left_keys = set()
179
+ skipped_left_keys = set()
180
+ skipped_right_keys_for_left = []
181
+ processed_right_keys_for_left = []
182
+
183
+ collect_skipped_left_keys = bool(
184
+ require_all_left_keys_processed or skippable_left_keys_should_contain is not None
185
+ )
186
+ collect_skipped_right_keys = bool(
187
+ require_all_left_keys_match_right or unmatched_right_keys_should_contain is not None
188
+ )
189
+
190
+ i_left = -1
191
+ for key_right in keys_right:
192
+ key_right_tuple = properkey(key_right)
193
+ key_right_set = OrderedSet(key_right_tuple)
194
+ if fcn_outer_before is not None:
195
+ fcn_outer_before(key_right_tuple)
196
+
197
+ right_key_processed = False
198
+ for i_left, keys_left in enumerate(keys_left_seq):
199
+ if i_left >= len(skipped_right_keys_for_left):
200
+ processed_right_keys_for_left.append(processed_right_keys_i := set())
201
+ skipped_right_keys_for_left.append(skipped_right_keys_i := set())
202
+ else:
203
+ processed_right_keys_i = processed_right_keys_for_left[i_left]
204
+ skipped_right_keys_i = skipped_right_keys_for_left[i_left]
205
+
206
+ right_key_processed = match_keys_to_key(
207
+ keys_left,
208
+ key_right_tuple,
209
+ key_right_set,
210
+ keys_consistent_fcn,
211
+ lambda *args: fcn(i_left, *args),
212
+ fcn_on_reject=(
213
+ (lambda *args: fcn_on_reject(i_left, *args))
214
+ if fcn_on_reject is not None
215
+ else None
216
+ ),
217
+ collect_skipped_left_keys=collect_skipped_left_keys,
218
+ collect_skipped_right_keys=collect_skipped_right_keys,
219
+ skipped_left_keys=skipped_left_keys,
220
+ processed_left_keys=processed_left_keys,
221
+ skipped_right_keys=skipped_right_keys_i,
222
+ processed_right_keys=processed_right_keys_i,
223
+ require_no_duplicate_right_matches=require_no_duplicate_right_matches,
224
+ )
225
+
226
+ if fcn_outer_after is not None:
227
+ fcn_outer_after(key_right_tuple)
228
+
229
+ if right_key_processed:
230
+ if require_all_left_keys_match_right:
231
+ processed_right_keys.add(key_right_tuple)
232
+ elif require_all_right_keys_processed:
233
+ skipped_right_keys.append(key_right_tuple)
234
+
235
+ for i, (skipped_left_keys_i, processed_left_keys_i) in enumerate(
236
+ zip(skipped_right_keys_for_left, processed_right_keys_for_left)
237
+ ):
238
+ if processed_left_keys_i != processed_right_keys:
239
+ if require_all_left_keys_match_right:
240
+ raise ValueError(
241
+ f"match_keys: left sequence {i} did not provide keys {skipped_left_keys_i!s}"
242
+ )
243
+ _check_for_unexpectedly_skipped_keys(
244
+ skipped_left_keys_i,
245
+ unmatched_right_keys_should_contain,
246
+ kind=f"missing left ({i})",
247
+ )
248
+
249
+ if skipped_left_keys:
250
+ if require_all_left_keys_processed:
251
+ raise ValueError(f"match_keys: there were unprocessed left keys {skipped_left_keys!s}")
252
+ _check_for_unexpectedly_skipped_keys(
253
+ skipped_left_keys, unmatched_right_keys_should_contain, kind="skipped left"
254
+ )
255
+
256
+ if require_all_right_keys_processed and skipped_right_keys:
257
+ raise ValueError(f"match_keys: there were unprocessed right keys {skipped_right_keys!s}")
258
+
259
+
260
+ def _check_for_unexpectedly_skipped_keys(
261
+ skipped_keys: Sequence[Key] | set[Key], should_contain: Sequence[KeyLike] | None, kind: str
262
+ ):
263
+ if unexpectedly_skipped_keys := _get_unexpectedly_skipped_keys(skipped_keys, should_contain):
264
+ raise ValueError(
265
+ f"match_keys: there were {kind} keys {skipped_keys!s}. "
266
+ f"{unexpectedly_skipped_keys} should not have been skipped."
267
+ )
268
+
269
+
270
+ def _get_unexpectedly_skipped_keys(
271
+ skipped_keys: Sequence[Key] | set[Key], should_contain: Sequence[KeyLike] | None
272
+ ) -> Sequence[Key]:
273
+ if not should_contain:
274
+ return []
275
+
276
+ setkeys = tuple(setkey(keypart) for keypart in should_contain)
277
+ incorrectly_skipped_keys = []
278
+ for skipped_key in skipped_keys:
279
+ if not any(setkey.issubset(skipped_key) for setkey in setkeys):
280
+ incorrectly_skipped_keys.append(skipped_key)
281
+
282
+ return incorrectly_skipped_keys
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nested-mapping
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: A wrapper and a toolkit for working with nested mappings in Python
5
5
  Author-email: DAGModelling Team <dagflow@jinr.ru>
6
6
  Maintainer-email: DAGModelling Team <dagflow@jinr.ru>
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "0.13.0",
3
+ "distance": 0,
4
+ "node": "g4a18ec94669d327766012fd5493c902427e34392",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-11-03"
8
+ }
@@ -17,6 +17,7 @@ def test_remap_items_01():
17
17
  d_renamed_reordered_skipped_source_match = NestedMapping()
18
18
  d_renamed_reordered_skipped_target_match = NestedMapping()
19
19
  d_renamed_reordered_skipped_both_match = NestedMapping()
20
+ d_renamed_reordered_skipped_both_extra_match = NestedMapping()
20
21
 
21
22
  rename_indices = {
22
23
  "z": ("z1", "z2"),
@@ -24,6 +25,8 @@ def test_remap_items_01():
24
25
  }
25
26
  reorder_indices = [2, 1, 0]
26
27
  reorder_indices_names = (("a", "b", "c"), ("c", "b", "a"))
28
+ reorder_indices_names_extra = (("a", "b", "c"), ("c", "extra", "b", "a"))
29
+ reorder_indices_dict = {"from": ("a", "b", "c"), "to": ("c", "b", "a")}
27
30
  skip_indices_source = (("1", "z"), ("b",))
28
31
  skip_indices_target = (("1", "z2"), ("a",))
29
32
 
@@ -47,6 +50,9 @@ def test_remap_items_01():
47
50
  d_renamed_reordered_skipped_both_match["z1", i2new, i1] = c
48
51
  d_renamed_reordered_skipped_both_match["z2", i2new, i1] = c
49
52
 
53
+ d_renamed_reordered_skipped_both_extra_match["z1", "extra", i2new, i1] = c
54
+ d_renamed_reordered_skipped_both_extra_match["z2", "extra", i2new, i1] = c
55
+
50
56
  d_renamed_reordered_skipped_source_match["z1", i2new, i1] = c
51
57
  d_renamed_reordered_skipped_source_match["z2", i2new, i1] = c
52
58
 
@@ -63,6 +69,7 @@ def test_remap_items_01():
63
69
  d_renamed_reordered_skipped_source_match[i3, i2new, i1] = c
64
70
  if i2new != "a":
65
71
  d_renamed_reordered_skipped_both_match[i3, i2new, i1] = c
72
+ d_renamed_reordered_skipped_both_extra_match[i3, "extra", i2new, i1] = c
66
73
 
67
74
  d_new = remap_items(d_main, reorder_indices=reorder_indices)
68
75
  assert d_reordered_match == d_new
@@ -73,6 +80,9 @@ def test_remap_items_01():
73
80
  d_new = remap_items(d_main, reorder_indices=reorder_indices_names)
74
81
  assert d_reordered_match == d_new
75
82
 
83
+ d_new = remap_items(d_main, reorder_indices=reorder_indices_dict)
84
+ assert d_reordered_match == d_new
85
+
76
86
  d_new = remap_items(
77
87
  d_main,
78
88
  reorder_indices=reorder_indices,
@@ -122,6 +132,25 @@ def test_remap_items_01():
122
132
  skip_indices_source=skip_indices_source,
123
133
  skip_indices_target=skip_indices_target,
124
134
  )
135
+ assert d_renamed_reordered_skipped_both_match == d_new
136
+
137
+ with raises(ValueError):
138
+ d_new = remap_items(
139
+ d_main,
140
+ rename_indices=rename_indices,
141
+ reorder_indices=reorder_indices_names_extra,
142
+ skip_indices_source=skip_indices_source,
143
+ skip_indices_target=skip_indices_target,
144
+ )
145
+ d_new = remap_items(
146
+ d_main,
147
+ rename_indices=rename_indices,
148
+ reorder_indices=reorder_indices_names_extra,
149
+ skip_indices_source=skip_indices_source,
150
+ skip_indices_target=skip_indices_target,
151
+ allow_insert_items=True,
152
+ )
153
+ assert d_renamed_reordered_skipped_both_extra_match == d_new
125
154
 
126
155
  with raises(ValueError):
127
156
  remap_items(d_main, reorder_indices=[["a", "b", "c"], ["c", "a", "B"]])
@@ -227,3 +256,19 @@ def test_reorder_function():
227
256
 
228
257
  with raises(ValueError):
229
258
  make_reorder_function([list("abc"), list("cad")])
259
+
260
+ with raises(ValueError):
261
+ fcn6 = make_reorder_function([list("abc"), list("cdab")])
262
+
263
+ fcn6 = make_reorder_function([list("abc"), list("cdab")], allow_insert_items=True)
264
+ data6 = [3, "d", 1, 2]
265
+ assert data6 == fcn6(data_from)
266
+
267
+ with raises(ValueError):
268
+ fcn6 = make_reorder_function([list("abc"), list("cdb")], allow_insert_items=True)
269
+
270
+ fcn6 = make_reorder_function(
271
+ [list("abc"), list("cdb")], allow_insert_items=True, allow_skip_items=True
272
+ )
273
+ data6 = [3, "d", 2]
274
+ assert data6 == fcn6(data_from)