From 98f1bf63427d4ba6178a7a8867030e251c491ce5 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Mon, 12 Sep 2022 20:54:05 +0200 Subject: [PATCH 01/26] add wildcard for rename type_util.py - add wildcard index, add only dicts check __init__.py - clean naive test run keylist_dict.py - add condition for wildcard key keylist_util.py - enhance condition for wildcard --- benedict/__init__.py | 1 + benedict/dicts/keylist/keylist_dict.py | 6 +++++ benedict/dicts/keylist/keylist_util.py | 25 ++++++++++++++++--- benedict/dicts/keypath/keypath_util.py | 5 ++-- benedict/utils/type_util.py | 8 +++++++ tests/dicts/test_benedict.py | 33 ++++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 6 deletions(-) diff --git a/benedict/__init__.py b/benedict/__init__.py index 0add85aa..285556c9 100644 --- a/benedict/__init__.py +++ b/benedict/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from copy import deepcopy from benedict.dicts import benedict from benedict.metadata import ( diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 13a7fad9..36eefea3 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -78,6 +78,12 @@ def _pop_by_keys(self, keys, *args): parent, key, _ = keylist_util.get_item(self, keys) if type_util.is_dict(parent): return parent.pop(key, *args) + elif ( + type_util.is_list(parent) + and all(type_util.is_dict(_item) for _item in parent) + and any(type_util.is_wildcard(_key) for _key in keys) + ): + return [_item.pop(key) if key in _item else None for _item in parent] elif type_util.is_list(parent): return parent.pop(key) elif type_util.is_tuple(parent): diff --git a/benedict/dicts/keylist/keylist_util.py b/benedict/dicts/keylist/keylist_util.py index 334c51a5..a22cd3bb 100644 --- a/benedict/dicts/keylist/keylist_util.py +++ b/benedict/dicts/keylist/keylist_util.py @@ -9,8 +9,17 @@ def _get_index(key): return None -def _get_item_key_and_value(item, key): - if type_util.is_list_or_tuple(item): +def _get_item_key_and_value(item, key, parent=None): + if type_util.is_list_or_tuple(item) and type_util.is_wildcard(key): + return key, item + elif ( + type_util.is_list_or_tuple(item) + and type_util.is_wildcard(parent) + and type_util.contains_only_dict(item) + and any(key in _item.keys() for _item in item) + ): + return key, [_item for _item in item if key in _item.keys()] + elif type_util.is_list_or_tuple(item): index = _get_index(key) if index is not None: return (index, item[index]) @@ -45,6 +54,10 @@ def _set_item_value(item, key, value): # insert index item += [None] * (index - len(item)) item.insert(index, value) + elif type_util.is_list(item): + for idx, _item in enumerate(value): + if _item is not None: + item[idx].update({key: _item}) else: item[key] = value @@ -59,7 +72,13 @@ def get_items(d, keys): item = d for key in keys: try: - item_key, item_value = _get_item_key_and_value(item, key) + if any(items) and type_util.is_wildcard(val=key): + parent = items[-1][1] + elif any(items) and type_util.is_wildcard(items[-1][1]): + parent = items[-1][1] + else: + parent = None + item_key, item_value = _get_item_key_and_value(item, key, parent) items.append((item, item_key, item_value)) item = item_value except (IndexError, KeyError): diff --git a/benedict/dicts/keypath/keypath_util.py b/benedict/dicts/keypath/keypath_util.py index 5a7cce86..10f0a8a5 100644 --- a/benedict/dicts/keypath/keypath_util.py +++ b/benedict/dicts/keypath/keypath_util.py @@ -6,7 +6,7 @@ import re -KEY_INDEX_RE = r"(?:\[[\'\"]*(\-?[\d]+)[\'\"]*\]){1}$" +KEY_INDEX_RE = r"(?:\[[\'\"]*(\-?[\d|\*]+)[\'\"]*\]){1}$" def check_keys(d, separator): @@ -48,9 +48,8 @@ def _split_key_indexes(key): matches = re.findall(KEY_INDEX_RE, key) if matches: key = re.sub(KEY_INDEX_RE, "", key) - index = int(matches[0]) + index = int(matches[0]) if not any(match in ['*'] for match in matches) else matches[0] keys.insert(0, index) - # keys.insert(0, { keylist_util.INDEX_KEY:index }) continue keys.insert(0, key) break diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index 076ffa73..af27148c 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -91,3 +91,11 @@ def is_tuple(val): def is_uuid(val): return is_string(val) and uuid_re.match(val) + + +def is_wildcard(val): + return is_string(val) and val in ["*"] + + +def contains_only_dict(val): + return all(is_dict(_val) for _val in val) diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index 4aeabf38..8089ff1b 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1762,6 +1762,39 @@ def test_rename(self): with self.assertRaises(KeyError): b.rename("aa", "b") + def test_rename_asterix(self): + d = { + "a": [ + { + "x": 1, + "y": 1, + }, + { + "x": 2, + "y": 2, + }, + ] + } + r = { + "a": [ + { + "m": 1, + "y": 1, + }, + { + "m": 2, + "y": 2, + }, + ] + } + b = benedict(d.copy()) + b.rename('a[0].x', 'a[0].m') + b.rename('a[1].x', 'a[1].m') + self.assertEqual(b, r) + b = benedict(d.copy()) + b.rename('a[*].x', 'a[*].m') + self.assertEqual(b, r) + def test_search(self): d = { "a": "Hello world", From d69450a5cb26a819b429d3e257a950ac7e0d769d Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Mon, 12 Sep 2022 23:39:32 +0200 Subject: [PATCH 02/26] wildcard tests - modify test --- tests/dicts/keypath/test_keypath_util.py | 1 - tests/dicts/test_benedict.py | 18 ++++++++++++------ 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/tests/dicts/keypath/test_keypath_util.py b/tests/dicts/keypath/test_keypath_util.py index 75689f03..042d2a91 100644 --- a/tests/dicts/keypath/test_keypath_util.py +++ b/tests/dicts/keypath/test_keypath_util.py @@ -30,7 +30,6 @@ def test_split_key_indexes_with_valid_indexes(self): def test_split_key_indexes_with_invalid_indexes(self): f = keypath_util._split_key_indexes self.assertEqual(f("item[]"), ["item[]"]) - self.assertEqual(f("item[*]"), ["item[*]"]) self.assertEqual(f("item[0:2]"), ["item[0:2]"]) self.assertEqual(f("item[:1]"), ["item[:1]"]) self.assertEqual(f("item[::1]"), ["item[::1]"]) diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index 8089ff1b..9e9036bd 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +from copy import deepcopy from benedict import benedict from datetime import datetime @@ -1762,7 +1763,7 @@ def test_rename(self): with self.assertRaises(KeyError): b.rename("aa", "b") - def test_rename_asterix(self): + def test_rename_wildcard(self): d = { "a": [ { @@ -1787,12 +1788,17 @@ def test_rename_asterix(self): }, ] } - b = benedict(d.copy()) - b.rename('a[0].x', 'a[0].m') - b.rename('a[1].x', 'a[1].m') + b = benedict(deepcopy(d)) + print(b) + b.rename("a[0].x", "a[0].m") + b.rename("a[1].x", "a[1].m") + print(b) self.assertEqual(b, r) - b = benedict(d.copy()) - b.rename('a[*].x', 'a[*].m') + b = benedict(deepcopy(d)) + print(b) + b.rename("a[*].x", "a[*].m") + print(b) + print(r) self.assertEqual(b, r) def test_search(self): From 4f8e893069d10b0fd8b3e487c06c8a22884183a6 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:25:18 +0200 Subject: [PATCH 03/26] wildcard remove unnecessary imports --- benedict/__init__.py | 2 -- tests/dicts/test_benedict.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/benedict/__init__.py b/benedict/__init__.py index 285556c9..bba59107 100644 --- a/benedict/__init__.py +++ b/benedict/__init__.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from copy import deepcopy - from benedict.dicts import benedict from benedict.metadata import ( __author__, diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index 9e9036bd..2460a635 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1,6 +1,4 @@ # -*- coding: utf-8 -*- -from copy import deepcopy - from benedict import benedict from datetime import datetime from decimal import Decimal From 7e26ee24c1fde3fca739ce38e0e114e2feffd9c2 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:27:13 +0200 Subject: [PATCH 04/26] wildcard simplify condition --- benedict/utils/type_util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index af27148c..9f3ae978 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -94,7 +94,7 @@ def is_uuid(val): def is_wildcard(val): - return is_string(val) and val in ["*"] + return is_string(val) and val == "*" def contains_only_dict(val): From 6987943fe9e0b578ec05620fa9071508279e4e42 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:28:22 +0200 Subject: [PATCH 05/26] wildcard rename function, rename variable --- benedict/dicts/keylist/keylist_util.py | 32 +++++++++++++------------- benedict/utils/type_util.py | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/benedict/dicts/keylist/keylist_util.py b/benedict/dicts/keylist/keylist_util.py index a22cd3bb..a6724d6c 100644 --- a/benedict/dicts/keylist/keylist_util.py +++ b/benedict/dicts/keylist/keylist_util.py @@ -9,23 +9,23 @@ def _get_index(key): return None -def _get_item_key_and_value(item, key, parent=None): - if type_util.is_list_or_tuple(item) and type_util.is_wildcard(key): - return key, item - elif ( - type_util.is_list_or_tuple(item) - and type_util.is_wildcard(parent) - and type_util.contains_only_dict(item) - and any(key in _item.keys() for _item in item) - ): - return key, [_item for _item in item if key in _item.keys()] - elif type_util.is_list_or_tuple(item): - index = _get_index(key) - if index is not None: - return (index, item[index]) +def _get_item_key_and_value(item, index, parent=None): + if type_util.is_list_or_tuple(item): + if type_util.is_wildcard(index): + return index, item + elif ( + type_util.is_wildcard(parent) + and type_util.is_list_of_dicts(item) + and any(index in _item.keys() for _item in item) + ): + return index, [_item for _item in item if index in _item.keys()] + else: + index = _get_index(index) + if index is not None: + return index, item[index] elif type_util.is_dict(item): - return (key, item[key]) - raise KeyError(f"Invalid key: '{key}'") + return index, item[index] + raise KeyError(f"Invalid key: '{index}'") def _get_or_new_item_value(item, key, subkey): diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index 9f3ae978..13caf27a 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -97,5 +97,5 @@ def is_wildcard(val): return is_string(val) and val == "*" -def contains_only_dict(val): +def is_list_of_dicts(val): return all(is_dict(_val) for _val in val) From c85c1a48919bcc13f02d642d5055d5fb0abfc1b9 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:28:38 +0200 Subject: [PATCH 06/26] wildcard use util function --- benedict/dicts/keylist/keylist_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 36eefea3..c723fa8e 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -80,7 +80,7 @@ def _pop_by_keys(self, keys, *args): return parent.pop(key, *args) elif ( type_util.is_list(parent) - and all(type_util.is_dict(_item) for _item in parent) + and type_util.is_list_of_dicts(parent) and any(type_util.is_wildcard(_key) for _key in keys) ): return [_item.pop(key) if key in _item else None for _item in parent] From baf4f703b9a6fe373f1be5badbd5537eb029098b Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:32:42 +0200 Subject: [PATCH 07/26] wildcard change conditions blocks --- benedict/dicts/keylist/keylist_util.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/benedict/dicts/keylist/keylist_util.py b/benedict/dicts/keylist/keylist_util.py index a6724d6c..41cdd70b 100644 --- a/benedict/dicts/keylist/keylist_util.py +++ b/benedict/dicts/keylist/keylist_util.py @@ -72,10 +72,13 @@ def get_items(d, keys): item = d for key in keys: try: - if any(items) and type_util.is_wildcard(val=key): - parent = items[-1][1] - elif any(items) and type_util.is_wildcard(items[-1][1]): - parent = items[-1][1] + if any(items): + if type_util.is_wildcard(val=key): + parent = items[-1][1] + elif type_util.is_wildcard(items[-1][1]): + parent = items[-1][1] + else: + parent = None else: parent = None item_key, item_value = _get_item_key_and_value(item, key, parent) From 9eba3fcc5b5dda1dba979907cca45ab787f7b5ac Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 16:33:01 +0200 Subject: [PATCH 08/26] wildcard remove print function from test --- tests/dicts/test_benedict.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index 2460a635..efa14c89 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1786,17 +1786,13 @@ def test_rename_wildcard(self): }, ] } - b = benedict(deepcopy(d)) - print(b) + d = benedict(d) + b = benedict(d.clone()) b.rename("a[0].x", "a[0].m") b.rename("a[1].x", "a[1].m") - print(b) self.assertEqual(b, r) - b = benedict(deepcopy(d)) - print(b) + b = benedict(d.clone()) b.rename("a[*].x", "a[*].m") - print(b) - print(r) self.assertEqual(b, r) def test_search(self): From 95e3ee88a7e551d7ca33f279e545c3e72b5374ff Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Tue, 13 Sep 2022 21:32:02 +0200 Subject: [PATCH 09/26] wildcard add wildcard regex, change _split_key_indexes() to use it --- benedict/dicts/keypath/keypath_util.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/benedict/dicts/keypath/keypath_util.py b/benedict/dicts/keypath/keypath_util.py index 10f0a8a5..c1892606 100644 --- a/benedict/dicts/keypath/keypath_util.py +++ b/benedict/dicts/keypath/keypath_util.py @@ -6,7 +6,8 @@ import re -KEY_INDEX_RE = r"(?:\[[\'\"]*(\-?[\d|\*]+)[\'\"]*\]){1}$" +KEY_INDEX_RE = r"(?:\[[\'\"]*(\-?[\d]+)[\'\"]*\]){1}$" +KEY_WILDCARD_RE = r"(?:\[[\'\"]*(\-?[\*]+)[\'\"]*\]){1}$" def check_keys(d, separator): @@ -45,10 +46,16 @@ def _split_key_indexes(key): if "[" in key and key.endswith("]"): keys = [] while True: - matches = re.findall(KEY_INDEX_RE, key) - if matches: + index_matches = re.findall(KEY_INDEX_RE, key) + if index_matches: key = re.sub(KEY_INDEX_RE, "", key) - index = int(matches[0]) if not any(match in ['*'] for match in matches) else matches[0] + index = int(index_matches[0]) + keys.insert(0, index) + continue + index_matches = re.findall(KEY_WILDCARD_RE, key) + if bool(re.search(KEY_WILDCARD_RE, key)): + key = re.sub(KEY_WILDCARD_RE, "", key) + index = index_matches[0] keys.insert(0, index) continue keys.insert(0, key) From 48070d3bb610e9d6598bb9b2eefbc4f3d12cf96a Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Wed, 14 Sep 2022 08:59:05 +0200 Subject: [PATCH 10/26] fix typos --- benedict/dicts/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benedict/dicts/__init__.py b/benedict/dicts/__init__.py index cf5c05a8..5614a8d3 100644 --- a/benedict/dicts/__init__.py +++ b/benedict/dicts/__init__.py @@ -194,9 +194,9 @@ def match(self, pattern, indexes=True): def merge(self, other, *args, **kwargs): """ Merge one or more dict objects into current instance (deepupdate). - Sub-dictionaries will be merged toghether. + Sub-dictionaries will be merged together. If overwrite is False, existing values will not be overwritten. - If concat is True, list values will be concatenated toghether. + If concat is True, list values will be concatenated together. """ _merge(self, other, *args, **kwargs) @@ -212,7 +212,7 @@ def nest( ): """ Nest a list of dicts at the given key and return a new nested list - using the specified keys to establish the correct items hierarchy. + using the specified keys to establish the correct item's hierarchy. """ return _nest(self[key], id_key, parent_id_key, children_key) From 1c7da031ed9da4da8ca904f797a7892de072ade2 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Wed, 14 Sep 2022 11:14:48 +0200 Subject: [PATCH 11/26] wildcard fixes TypeError benedict methods (e.g. swap()) --- benedict/dicts/keylist/keylist_dict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index c723fa8e..cb46b689 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -43,6 +43,8 @@ def __getitem__(self, key): def _getitem_by_keys(self, keys): parent, key, _ = keylist_util.get_item(self, keys) + if type_util.is_list_of_dicts(parent): + return [item.get(key) for item in parent] if type_util.is_dict_or_list_or_tuple(parent): return parent[key] raise KeyError(f"Invalid keys: '{keys}'") From 6dfff108d7fa4c7b9e9ad4339408d678023da477 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Wed, 14 Sep 2022 11:15:32 +0200 Subject: [PATCH 12/26] wildcard build few tests, move wildcard test to separate test file --- .../test_keypath_dict_list_wildcard.py | 23 ++++++++ tests/dicts/test_benedict.py | 34 ----------- tests/dicts/test_benedict_wildcard.py | 58 +++++++++++++++++++ 3 files changed, 81 insertions(+), 34 deletions(-) create mode 100644 tests/dicts/keypath/test_keypath_dict_list_wildcard.py create mode 100644 tests/dicts/test_benedict_wildcard.py diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py new file mode 100644 index 00000000..12d882ef --- /dev/null +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -0,0 +1,23 @@ +import unittest + +from benedict.dicts import KeypathDict + + +class keypath_dict_list_wildcard_test_case(unittest.TestCase): + def setUp(self): + self.blueprint = KeypathDict( + { + "a": [ + {"x": 1, "y": 1}, + {"x": 2, "y": 2}, + ], + "x": [ + {"a": 10, "b": 10}, + {"a": 11, "b": 11}, + ], + } + ) + + def test_correct_wildcard(self): + correct_wildcard_path_example = "a[*]" + self.assertEqual(self.blueprint[correct_wildcard_path_example], [1, 2]) diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index efa14c89..49c85ae3 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1761,40 +1761,6 @@ def test_rename(self): with self.assertRaises(KeyError): b.rename("aa", "b") - def test_rename_wildcard(self): - d = { - "a": [ - { - "x": 1, - "y": 1, - }, - { - "x": 2, - "y": 2, - }, - ] - } - r = { - "a": [ - { - "m": 1, - "y": 1, - }, - { - "m": 2, - "y": 2, - }, - ] - } - d = benedict(d) - b = benedict(d.clone()) - b.rename("a[0].x", "a[0].m") - b.rename("a[1].x", "a[1].m") - self.assertEqual(b, r) - b = benedict(d.clone()) - b.rename("a[*].x", "a[*].m") - self.assertEqual(b, r) - def test_search(self): d = { "a": "Hello world", diff --git a/tests/dicts/test_benedict_wildcard.py b/tests/dicts/test_benedict_wildcard.py new file mode 100644 index 00000000..6ae71b6f --- /dev/null +++ b/tests/dicts/test_benedict_wildcard.py @@ -0,0 +1,58 @@ +import unittest + +from benedict import benedict + + +class keypath_dict_list_wildcard_test_case(unittest.TestCase): + def setUp(self): + self.d = { + "a": [ + {"x": 1, "y": 1}, + {"x": 2, "y": 2}, + ], + "x": [ + {"a": 10, "b": 10}, + {"a": 11, "b": 11}, + ], + } + + def test_rename_wildcard(self): + self.d.pop("x") + d = benedict(self.d) + b = benedict(d.clone()) + b.rename("a[0].x", "a[0].m") + b.rename("a[1].x", "a[1].m") + + result = { + "a": [ + { + "m": 1, + "y": 1, + }, + { + "m": 2, + "y": 2, + }, + ] + } + + self.assertEqual(b, result) + b = benedict(d.clone()) + b.rename("a[*].x", "a[*].m") + self.assertEqual(b, result) + + def test_swap_wildcard(self): + b = benedict(self.d) + b = benedict(b.clone()) + b.swap("a[*].x", "x[*].a") + result = { + "a": [ + {"x": 10, "y": 1}, + {"x": 11, "y": 2}, + ], + "x": [ + {"a": 1, "b": 10}, + {"a": 2, "b": 11}, + ], + } + self.assertEqual(b, result) From 4de87dda4d2ddef8b1038c9da4bb8643f51a61a7 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Wed, 14 Sep 2022 14:54:28 +0200 Subject: [PATCH 13/26] wildcard add is_list to is_list_of_dicts --- benedict/dicts/keylist/keylist_dict.py | 3 +-- benedict/utils/type_util.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index cb46b689..82247dd4 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -81,8 +81,7 @@ def _pop_by_keys(self, keys, *args): if type_util.is_dict(parent): return parent.pop(key, *args) elif ( - type_util.is_list(parent) - and type_util.is_list_of_dicts(parent) + type_util.is_list_of_dicts(parent) and any(type_util.is_wildcard(_key) for _key in keys) ): return [_item.pop(key) if key in _item else None for _item in parent] diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index 13caf27a..ba90e303 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -98,4 +98,4 @@ def is_wildcard(val): def is_list_of_dicts(val): - return all(is_dict(_val) for _val in val) + return is_list(val) and all(is_dict(_val) for _val in val) From df926ca265420a29f3c57a3afaa7a2b15998c0c5 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Wed, 14 Sep 2022 15:00:23 +0200 Subject: [PATCH 14/26] wildcard change tests --- tests/dicts/test_benedict.py | 1 + tests/dicts/test_benedict_wildcard.py | 25 ++++++++++++++----------- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index 49c85ae3..4aeabf38 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- + from benedict import benedict from datetime import datetime from decimal import Decimal diff --git a/tests/dicts/test_benedict_wildcard.py b/tests/dicts/test_benedict_wildcard.py index 6ae71b6f..bfc0bbbf 100644 --- a/tests/dicts/test_benedict_wildcard.py +++ b/tests/dicts/test_benedict_wildcard.py @@ -4,21 +4,14 @@ class keypath_dict_list_wildcard_test_case(unittest.TestCase): - def setUp(self): - self.d = { + def test_rename_wildcard(self): + d = { "a": [ {"x": 1, "y": 1}, {"x": 2, "y": 2}, ], - "x": [ - {"a": 10, "b": 10}, - {"a": 11, "b": 11}, - ], } - - def test_rename_wildcard(self): - self.d.pop("x") - d = benedict(self.d) + d = benedict(d) b = benedict(d.clone()) b.rename("a[0].x", "a[0].m") b.rename("a[1].x", "a[1].m") @@ -42,7 +35,17 @@ def test_rename_wildcard(self): self.assertEqual(b, result) def test_swap_wildcard(self): - b = benedict(self.d) + self.d = { + "a": [ + {"x": 1, "y": 1}, + {"x": 2, "y": 2}, + ], + "x": [ + {"a": 10, "b": 10}, + {"a": 11, "b": 11}, + ], + } + b = benedict(d) b = benedict(b.clone()) b.swap("a[*].x", "x[*].a") result = { From 409d742adeddc3ca142ea01a48c97774f992a01d Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 11:58:59 +0200 Subject: [PATCH 15/26] wildcard fix test --- tests/dicts/keypath/test_keypath_dict_list_wildcard.py | 2 +- tests/dicts/test_benedict_wildcard.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index 12d882ef..f3c9060d 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -19,5 +19,5 @@ def setUp(self): ) def test_correct_wildcard(self): - correct_wildcard_path_example = "a[*]" + correct_wildcard_path_example = "a[*].x" self.assertEqual(self.blueprint[correct_wildcard_path_example], [1, 2]) diff --git a/tests/dicts/test_benedict_wildcard.py b/tests/dicts/test_benedict_wildcard.py index bfc0bbbf..f3ee752a 100644 --- a/tests/dicts/test_benedict_wildcard.py +++ b/tests/dicts/test_benedict_wildcard.py @@ -35,7 +35,7 @@ def test_rename_wildcard(self): self.assertEqual(b, result) def test_swap_wildcard(self): - self.d = { + d = { "a": [ {"x": 1, "y": 1}, {"x": 2, "y": 2}, From 38931d664ca7b896ce13b7b075722a183fa72f86 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 12:03:33 +0200 Subject: [PATCH 16/26] wildcard add condition for wildcard in keys --- benedict/dicts/keylist/keylist_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 82247dd4..0e152fc0 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -43,7 +43,7 @@ def __getitem__(self, key): def _getitem_by_keys(self, keys): parent, key, _ = keylist_util.get_item(self, keys) - if type_util.is_list_of_dicts(parent): + if type_util.is_list_of_dicts(parent) and any(type_util.is_wildcard(_key) for _key in keys): return [item.get(key) for item in parent] if type_util.is_dict_or_list_or_tuple(parent): return parent[key] From b34a80366addd651a57f30547fd6f0097322adb7 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 17:15:02 +0200 Subject: [PATCH 17/26] wildcard implements returning whole list if using wildcard without any other property add any_wildcard_in_list method --- benedict/dicts/keylist/keylist_dict.py | 12 ++++++++---- benedict/utils/type_util.py | 4 ++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 0e152fc0..7bd9f069 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -43,7 +43,9 @@ def __getitem__(self, key): def _getitem_by_keys(self, keys): parent, key, _ = keylist_util.get_item(self, keys) - if type_util.is_list_of_dicts(parent) and any(type_util.is_wildcard(_key) for _key in keys): + if type_util.is_list(parent) and type_util.is_wildcard(key): + return parent + if type_util.is_list_of_dicts(parent) and type_util.any_wildcard_in_list(keys): return [item.get(key) for item in parent] if type_util.is_dict_or_list_or_tuple(parent): return parent[key] @@ -80,9 +82,11 @@ def _pop_by_keys(self, keys, *args): parent, key, _ = keylist_util.get_item(self, keys) if type_util.is_dict(parent): return parent.pop(key, *args) - elif ( - type_util.is_list_of_dicts(parent) - and any(type_util.is_wildcard(_key) for _key in keys) + elif type_util.is_list(parent) and type_util.is_wildcard(key): + del self[keys[:-1]] + return parent + elif type_util.is_list_of_dicts(parent) and type_util.any_wildcard_in_list( + keys ): return [_item.pop(key) if key in _item else None for _item in parent] elif type_util.is_list(parent): diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index ba90e303..9f9de5de 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -99,3 +99,7 @@ def is_wildcard(val): def is_list_of_dicts(val): return is_list(val) and all(is_dict(_val) for _val in val) + + +def any_wildcard_in_list(val): + return is_list(val) and any(is_wildcard(_val) for _val in val) From ec6caac998a68ac1479595b92b846921746fbb67 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 17:18:39 +0200 Subject: [PATCH 18/26] wildcard delete items in list with wildcard --- benedict/dicts/keylist/keylist_dict.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 7bd9f069..58c64e21 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -28,7 +28,10 @@ def __delitem__(self, key): def _delitem_by_keys(self, keys): parent, key, _ = keylist_util.get_item(self, keys) - if type_util.is_dict_or_list(parent): + if type_util.is_wildcard(key): + self[keys[:-1]] = [] + return + elif type_util.is_dict_or_list(parent): del parent[key] return elif type_util.is_tuple(parent): From 64e355e8938b6d2bd517ac8918e44cdb1104b10d Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 17:22:10 +0200 Subject: [PATCH 19/26] wildcard get list by wildcard --- benedict/dicts/keylist/keylist_dict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 58c64e21..e78e0242 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -70,6 +70,8 @@ def get(self, key, default=None): def _get_by_keys(self, keys, default=None): parent, key, _ = keylist_util.get_item(self, keys) + if type_util.is_wildcard(key) and type_util.is_list(parent): + return parent if type_util.is_dict(parent): return parent.get(key, default) elif type_util.is_list_or_tuple(parent): From 87b87b118cbc4854016ce0dbee6afe23fe193714 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 17:22:46 +0200 Subject: [PATCH 20/26] wildcard add a bunch of tests --- .../test_keypath_dict_list_wildcard.py | 319 +++++++++++++++++- tests/dicts/test_benedict_wildcard.py | 15 +- 2 files changed, 324 insertions(+), 10 deletions(-) diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index f3c9060d..7f870b59 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -4,20 +4,321 @@ class keypath_dict_list_wildcard_test_case(unittest.TestCase): - def setUp(self): - self.blueprint = KeypathDict( + def test_correct_wildcard(self): + self.kd = KeypathDict( { "a": [ {"x": 1, "y": 1}, {"x": 2, "y": 2}, ], - "x": [ - {"a": 10, "b": 10}, - {"a": 11, "b": 11}, - ], } ) - - def test_correct_wildcard(self): correct_wildcard_path_example = "a[*].x" - self.assertEqual(self.blueprint[correct_wildcard_path_example], [1, 2]) + self.assertEqual(self.kd[correct_wildcard_path_example], [1, 2]) + + def test_wildcard_contains_with_flat_list(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + self.assertTrue("a[*]" in b) + + def test_wildcard_contains_with_wrong_property(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + self.assertFalse("b[*]" in b) + + def test_wildcard_contains_with_nested_list(self): + d = { + "a": { + "b": [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + ], + }, + } + b = KeypathDict(d) + + self.assertTrue("a.b[0].d[*]" in b) + self.assertTrue("a.b[1].d[*]" in b) + self.assertTrue("a.b[*]" in b) + + def test_wildcard_delitem_with_flat_list(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + del b["a[*]"] + self.assertEqual(b, {"a": []}) + d1 = { + "a": [1, 2, 3], + } + b1 = KeypathDict(d1) + del b1["a[-1]"] + del b1["a[1]"] + del b1["a[0]"] + self.assertEqual(b, {"a": []}) + self.assertEqual(True, b == b1) + + def test_wildcard_delitem_with_wrong_index(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + with self.assertRaises(KeyError): + del b["b[*]"] + + def test_wildcard_delitem_with_nested_list(self): + d = { + "a": { + "b": [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + ], + }, + } + b = KeypathDict(d) + + del b["a.b[0].d[*]"] + self.assertEqual(b["a.b[0].d"], []) + + del b["a.b[0].d"] + self.assertEqual(b["a.b[0]"], {"c": 1}) + + del b["a.b[*]"] + self.assertEqual(b["a.b"], []) + + def test_wildcard_getitem_with_flat_list(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + + self.assertEqual(b["a[*]"], [1, 2, 3]) + + def test_wildcard_getitem_with_wrong_index(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + with self.assertRaises(KeyError): + self.assertEqual(b["b[*]"], 1) + + def test_wildcard_getitem_with_nested_list(self): + d = { + "a": { + "b": [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + { + "c": 3, + "d": [7, 8, 9, [0]], + }, + ], + }, + } + b = KeypathDict(d) + + self.assertEqual(b["a.b[0].d[*]"], [1, 2, 3, [0]]) + self.assertEqual(b["a.b[1].d[*]"], [4, 5, 6, [0]]) + self.assertEqual(b["a.b[2].d[*]"], [7, 8, 9, [0]]) + + self.assertEqual( + b["a.b[*]"], + [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + { + "c": 3, + "d": [7, 8, 9, [0]], + }, + ], + ) + self.assertEqual(b["a.b[2].d[3][*]"], [0]) + + def test_wildcard_getitem_github_issue_feature_request(self): + d = { + "products": [ + { + "categories": [ + { + "name": "OK 1", + }, + { + "name": "OK 2", + }, + ], + }, + ], + } + b = KeypathDict(d) + self.assertEqual( + b['products[""0""].categories[*]'], + [ + { + "name": "OK 1", + }, + { + "name": "OK 2", + }, + ], + ) + + def test_wildcard_get_with_flat_list(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + + self.assertEqual(b.get("a[*]"), [1, 2, 3]) + + def test_wildcard_get_with_wrong_index(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + self.assertEqual(b.get("b[*]", 1), 1) + + def test_wildcard_get_with_nested_list(self): + d = { + "a": { + "b": [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + { + "c": 3, + "d": [7, 8, 9, [0]], + }, + ], + }, + } + b = KeypathDict(d) + + self.assertEqual(b.get("a.b[0].d[*]"), [1, 2, 3, [0]]) + self.assertEqual(b.get("a.b[1].d[*]"), [4, 5, 6, [0]]) + self.assertEqual(b.get("a.b[2].d[*]"), [7, 8, 9, [0]]) + + self.assertEqual( + b["a.b[*]"], + [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + { + "c": 3, + "d": [7, 8, 9, [0]], + }, + ], + ) + self.assertEqual(b.get("a.b[2].d[3][*]"), [0]) + + def test_wildcard_list_indexes_with_quotes(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + self.assertEqual(b.get("a['*']"), [1, 2, 3]) + self.assertEqual(b.get('a["*"]'), [1, 2, 3]) + + def test_wildcard_pop_with_flat_list(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + + self.assertEqual(b.pop("a[*]"), [1, 2, 3]) + self.assertEqual(b, {}) + + def test_wildcard_pop_with_flat_list_and_default(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + + with self.assertRaises(KeyError): + b.pop("b[*]") + self.assertEqual(b.pop("b[*]", 4), 4) + + def test_wildcard_pop_with_wrong_index(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + with self.assertRaises(KeyError): + b.pop("b[*]") + + def test_wildcard_pop_with_wrong_index_and_default(self): + d = { + "a": [1, 2, 3], + } + b = KeypathDict(d) + self.assertEqual(b.pop("b[*]", 6), 6) + + def test_wildcard_pop_with_nested_list(self): + d = { + "a": { + "b": [ + { + "c": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + ], + }, + } + b = KeypathDict(d) + + self.assertEqual(b.pop("a.b[0].d[-1][*]"), [0]) + self.assertEqual(b["a.b[0].d"], [1, 2, 3]) + + self.assertEqual(b.pop("a.b[0].d[*]"), [1, 2, 3]) + self.assertEqual(b["a.b[0]"], {"c": 1}) + + self.assertEqual(b.pop("a.b[0]"), {"c": 1}) + self.assertEqual( + b["a.b[0]"], + { + "c": 2, + "d": [4, 5, 6, [0]], + }, + ) diff --git a/tests/dicts/test_benedict_wildcard.py b/tests/dicts/test_benedict_wildcard.py index f3ee752a..89a8568a 100644 --- a/tests/dicts/test_benedict_wildcard.py +++ b/tests/dicts/test_benedict_wildcard.py @@ -46,7 +46,6 @@ def test_swap_wildcard(self): ], } b = benedict(d) - b = benedict(b.clone()) b.swap("a[*].x", "x[*].a") result = { "a": [ @@ -59,3 +58,17 @@ def test_swap_wildcard(self): ], } self.assertEqual(b, result) + + def test_swap_wildcard_whole_list(self): + d = { + "a": [ + {"x": 1, "y": 1}, + {"x": 2, "y": 2}, + ], + "x": [ + {"a": 10, "b": 10}, + {"a": 11, "b": 11}, + ], + } + b = benedict(d) + b.get("a[1]") From 07bdb1b7e4de364cc1a77ee786a4bf0ef43744e7 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 21:07:58 +0200 Subject: [PATCH 21/26] wildcard empty existing array instead of creating new one --- benedict/dicts/keylist/keylist_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index e78e0242..7a903bf3 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -29,7 +29,7 @@ def __delitem__(self, key): def _delitem_by_keys(self, keys): parent, key, _ = keylist_util.get_item(self, keys) if type_util.is_wildcard(key): - self[keys[:-1]] = [] + self[keys[:-1]].clear() return elif type_util.is_dict_or_list(parent): del parent[key] From f7401eb928bde2a93f5dbdbd9e0c66ef411fcbfa Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 21:09:52 +0200 Subject: [PATCH 22/26] wildcard change conditions order --- benedict/dicts/keylist/keylist_dict.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 7a903bf3..e11189c4 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -70,7 +70,7 @@ def get(self, key, default=None): def _get_by_keys(self, keys, default=None): parent, key, _ = keylist_util.get_item(self, keys) - if type_util.is_wildcard(key) and type_util.is_list(parent): + if type_util.is_list(parent) and type_util.is_wildcard(key): return parent if type_util.is_dict(parent): return parent.get(key, default) From a631b9a00509e8d3e37a7f0354fb9c0617c5fca2 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 21:48:14 +0200 Subject: [PATCH 23/26] wildcard add test for multiple wildcards in row --- tests/dicts/keypath/test_keypath_dict_list_wildcard.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index 7f870b59..6b155d24 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -50,6 +50,11 @@ def test_wildcard_contains_with_nested_list(self): self.assertTrue("a.b[0].d[*]" in b) self.assertTrue("a.b[1].d[*]" in b) self.assertTrue("a.b[*]" in b) + self.assertFalse("a.c[*]" in b) + self.assertFalse("a.b.c[*]" in b) + self.assertFalse("a.b[0].c[*]" in b) + self.assertTrue("a.b[0].d[*][*]" in b) + self.assertTrue("a.b[1].d[*][*]" in b) def test_wildcard_delitem_with_flat_list(self): d = { From 750b78f1160ca6baf5f77428212b4bdca1103431 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 15 Sep 2022 21:50:39 +0200 Subject: [PATCH 24/26] wildcard test for wildcard symbol as key --- .../test_keypath_dict_list_wildcard.py | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index 6b155d24..1ded3bce 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -327,3 +327,48 @@ def test_wildcard_pop_with_nested_list(self): "d": [4, 5, 6, [0]], }, ) + + def test_wildcard_asterix_as_key(self): + d = { + "*": { + "b": [ + { + "*": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "*": [4, 5, 6, [0]], + }, + ], + }, + } + b = KeypathDict(d) + self.assertTrue( + b["*"], + { + "b": [ + { + "*": 1, + "d": [1, 2, 3, [0]], + }, + { + "c": 2, + "*": [4, 5, 6, [0]], + }, + ], + }, + ) + self.assertTrue(b["*.b[0].*"], 1) + self.assertTrue(b.get("*.b[0].*", 2), 1) + + # wrong index => default value should be used + self.assertTrue(b.get("*.b[2].*", 2), 2) + self.assertTrue(b["*.b[1][*]"], [4, 5, 6, [0]]) + + with self.assertRaises(KeyError): + self.assertFalse(b["*.a"], None) + with self.assertRaises(KeyError): + self.assertFalse(b["*.*"], None) + + self.assertEqual(b.get("*.*", "not-existing"), "not-existing") From 9e136ea6b91d11b2d7eb44556a0337c65201c409 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 22 Sep 2022 09:33:35 +0200 Subject: [PATCH 25/26] wildcard implements behavior for wildcard chaining, add tests for that --- benedict/dicts/keylist/keylist_dict.py | 7 +- benedict/dicts/keylist/keylist_util.py | 22 +++-- benedict/utils/type_util.py | 7 +- .../test_keypath_dict_list_wildcard.py | 83 +++++++++++++++++++ 4 files changed, 110 insertions(+), 9 deletions(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index e11189c4..754b0748 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -72,7 +72,12 @@ def _get_by_keys(self, keys, default=None): parent, key, _ = keylist_util.get_item(self, keys) if type_util.is_list(parent) and type_util.is_wildcard(key): return parent - if type_util.is_dict(parent): + elif type_util.is_wildcard(keys[-2]): + if type_util.is_list_of_dicts(parent): + return [item.get(key) for item in parent] + if type_util.is_list_of_list(parent): + return _ + elif type_util.is_dict(parent): return parent.get(key, default) elif type_util.is_list_or_tuple(parent): return parent[key] diff --git a/benedict/dicts/keylist/keylist_util.py b/benedict/dicts/keylist/keylist_util.py index 41cdd70b..bdc43391 100644 --- a/benedict/dicts/keylist/keylist_util.py +++ b/benedict/dicts/keylist/keylist_util.py @@ -13,12 +13,22 @@ def _get_item_key_and_value(item, index, parent=None): if type_util.is_list_or_tuple(item): if type_util.is_wildcard(index): return index, item - elif ( - type_util.is_wildcard(parent) - and type_util.is_list_of_dicts(item) - and any(index in _item.keys() for _item in item) - ): - return index, [_item for _item in item if index in _item.keys()] + elif type_util.is_wildcard(parent): + if type_util.is_list_of_dicts(item) and any( + index in _item.keys() for _item in item + ): + return index, [ + _item.get(index) for _item in item if index in _item.keys() + ] + if type_util.is_list_of_list(item): + data = [] + for i_list in item: + extracted = [ + _item.get(index) for _item in i_list if index in _item.keys() + ] + data.append(extracted) + + return index, data else: index = _get_index(index) if index is not None: diff --git a/benedict/utils/type_util.py b/benedict/utils/type_util.py index 9f9de5de..49574e99 100644 --- a/benedict/utils/type_util.py +++ b/benedict/utils/type_util.py @@ -1,10 +1,9 @@ # -*- coding: utf-8 -*- +import re from datetime import datetime from decimal import Decimal -import re - regex = re.compile("").__class__ uuid_re = re.compile( "^([0-9a-f]{32}){1}$|^([0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}){1}$", @@ -103,3 +102,7 @@ def is_list_of_dicts(val): def any_wildcard_in_list(val): return is_list(val) and any(is_wildcard(_val) for _val in val) + + +def is_list_of_list(val): + return is_list(val) and all(is_list(_val) for _val in val) diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index 1ded3bce..92c6228e 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -372,3 +372,86 @@ def test_wildcard_asterix_as_key(self): self.assertFalse(b["*.*"], None) self.assertEqual(b.get("*.*", "not-existing"), "not-existing") + + def test_complex_wildcard_usage(self): + d = { + "a": { + "b": [ + { + "c": [ + {"x": 10, "y": 20}, + {"x": 11, "y": 21}, + {"x": 12, "y": 22}, + ], + }, + { + "c": [ + {"x": 20, "y": 30}, + {"x": 21, "y": 31}, + {"x": 22, "y": 32}, + ], + }, + ], + }, + } + b = KeypathDict(d) + self.assertEqual( + b.get("a.b[0]"), + { + "c": [ + {"x": 10, "y": 20}, + {"x": 11, "y": 21}, + {"x": 12, "y": 22}, + ], + }, + ) + self.assertEqual( + b.get("a.b[*]"), + [ + { + "c": [ + {"x": 10, "y": 20}, + {"x": 11, "y": 21}, + {"x": 12, "y": 22}, + ], + }, + { + "c": [ + {"x": 20, "y": 30}, + {"x": 21, "y": 31}, + {"x": 22, "y": 32}, + ], + }, + ], + ) + self.assertEqual( + b.get("a.b[0].c"), + [ + {"x": 10, "y": 20}, + {"x": 11, "y": 21}, + {"x": 12, "y": 22}, + ], + ) + self.assertEqual( + b.get("a.b[1].c"), + [ + {"x": 20, "y": 30}, + {"x": 21, "y": 31}, + {"x": 22, "y": 32}, + ], + ) + self.assertEqual(b.get("a.b[1].c[1]"), {"x": 21, "y": 31}) + self.assertEqual( + b.get("a.b[*].c"), + [ + [{"x": 10, "y": 20}, {"x": 11, "y": 21}, {"x": 12, "y": 22}], + [{"x": 20, "y": 30}, {"x": 21, "y": 31}, {"x": 22, "y": 32}], + ], + ) + self.assertEqual( + b.get("a.b[*].c[*].x"), + [ + [10, 11, 12], + [20, 21, 22], + ], + ) From 1142a4b01de17f6ef598c8b23a9f7a8da0eb3c32 Mon Sep 17 00:00:00 2001 From: milhauzindahauz Date: Thu, 22 Sep 2022 10:27:42 +0200 Subject: [PATCH 26/26] wildcard change behavior, modify test according to it, small refactor of if blocks --- benedict/dicts/keylist/keylist_dict.py | 2 +- benedict/dicts/keylist/keylist_util.py | 17 ++++++++--------- .../keypath/test_keypath_dict_list_wildcard.py | 9 +++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/benedict/dicts/keylist/keylist_dict.py b/benedict/dicts/keylist/keylist_dict.py index 754b0748..8c51c614 100644 --- a/benedict/dicts/keylist/keylist_dict.py +++ b/benedict/dicts/keylist/keylist_dict.py @@ -75,7 +75,7 @@ def _get_by_keys(self, keys, default=None): elif type_util.is_wildcard(keys[-2]): if type_util.is_list_of_dicts(parent): return [item.get(key) for item in parent] - if type_util.is_list_of_list(parent): + elif type_util.is_list_of_list(parent): return _ elif type_util.is_dict(parent): return parent.get(key, default) diff --git a/benedict/dicts/keylist/keylist_util.py b/benedict/dicts/keylist/keylist_util.py index bdc43391..dd922e5c 100644 --- a/benedict/dicts/keylist/keylist_util.py +++ b/benedict/dicts/keylist/keylist_util.py @@ -1,5 +1,7 @@ # -*- coding: utf-8 -*- +from itertools import chain + from benedict.utils import type_util @@ -20,15 +22,12 @@ def _get_item_key_and_value(item, index, parent=None): return index, [ _item.get(index) for _item in item if index in _item.keys() ] - if type_util.is_list_of_list(item): - data = [] - for i_list in item: - extracted = [ - _item.get(index) for _item in i_list if index in _item.keys() - ] - data.append(extracted) - - return index, data + elif type_util.is_list_of_list(item): + return index, [ + _item.get(index) + for _item in chain.from_iterable(item) + if index in _item.keys() + ] else: index = _get_index(index) if index is not None: diff --git a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py index 92c6228e..b7eb2b3e 100644 --- a/tests/dicts/keypath/test_keypath_dict_list_wildcard.py +++ b/tests/dicts/keypath/test_keypath_dict_list_wildcard.py @@ -450,8 +450,9 @@ def test_complex_wildcard_usage(self): ) self.assertEqual( b.get("a.b[*].c[*].x"), - [ - [10, 11, 12], - [20, 21, 22], - ], + [10, 11, 12, 20, 21, 22], + ) + self.assertEqual( + b.get("a.b[*].c[*].x[-1]"), + 22, )