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.
Files changed (41) hide show
  1. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/CHANGELOG.md +5 -0
  2. {nested_mapping-0.12.0/src/nested_mapping.egg-info → nested_mapping-0.12.1}/PKG-INFO +1 -1
  3. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/__version__.py +3 -3
  4. nested_mapping-0.12.1/src/nested_mapping/tools/match.py +274 -0
  5. {nested_mapping-0.12.0 → nested_mapping-0.12.1/src/nested_mapping.egg-info}/PKG-INFO +1 -1
  6. nested_mapping-0.12.1/src/nested_mapping.egg-info/scm_version.json +8 -0
  7. nested_mapping-0.12.1/tests/test_match.py +757 -0
  8. nested_mapping-0.12.0/src/nested_mapping/tools/match.py +0 -128
  9. nested_mapping-0.12.0/src/nested_mapping.egg-info/scm_version.json +0 -8
  10. nested_mapping-0.12.0/tests/test_match.py +0 -73
  11. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.flake8 +0 -0
  12. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.gitignore +0 -0
  13. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/.gitlab-ci.yml +0 -0
  14. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/CONTRIBUTING.md +0 -0
  15. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/LICENSE +0 -0
  16. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/README.md +0 -0
  17. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/conftest.py +0 -0
  18. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/pyproject.toml +0 -0
  19. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/pytest.ini +0 -0
  20. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/setup.cfg +0 -0
  21. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/__init__.py +0 -0
  22. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/class_wrapper.py +0 -0
  23. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/ipython.py +0 -0
  24. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/nested_mapping.py +0 -0
  25. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/nested_mapping_access.py +0 -0
  26. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/__init__.py +0 -0
  27. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/filter.py +0 -0
  28. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/map.py +0 -0
  29. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/tools/reorder_key.py +0 -0
  30. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/typing.py +0 -0
  31. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping/visitor.py +0 -0
  32. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/SOURCES.txt +0 -0
  33. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/dependency_links.txt +0 -0
  34. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/requires.txt +0 -0
  35. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/scm_file_list.json +0 -0
  36. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/src/nested_mapping.egg-info/top_level.txt +0 -0
  37. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_filter.py +0 -0
  38. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_map.py +0 -0
  39. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_nested_mapping.py +0 -0
  40. {nested_mapping-0.12.0 → nested_mapping-0.12.1}/tests/test_nested_mapping_update.py +0 -0
  41. {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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nested-mapping
3
- Version: 0.12.0
3
+ Version: 0.12.1
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.12.1'
22
+ __version_tuple__ = version_tuple = (0, 12, 1)
23
23
 
24
- __commit_id__ = commit_id = 'gd3a44dafb'
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nested-mapping
3
- Version: 0.12.0
3
+ Version: 0.12.1
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.12.1",
3
+ "distance": 0,
4
+ "node": "g70b286d139ee9177b2886dfbe9a73efe2e19880a",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-29"
8
+ }