From 46e55cf1c9ca287803286f4f9314af5aff3da8a2 Mon Sep 17 00:00:00 2001 From: Fabio Caccamo Date: Thu, 6 Feb 2020 15:16:11 +0100 Subject: [PATCH] Added nest utility method. --- benedict/core/__init__.py | 1 + benedict/core/nest.py | 22 ++++++++++++ benedict/dicts/__init__.py | 8 +++++ tests/core/test_nest.py | 70 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 benedict/core/nest.py create mode 100644 tests/core/test_nest.py diff --git a/benedict/core/__init__.py b/benedict/core/__init__.py index 363e492..74128d6 100644 --- a/benedict/core/__init__.py +++ b/benedict/core/__init__.py @@ -13,6 +13,7 @@ from benedict.core.keylists import keylists from benedict.core.keypaths import keypaths from benedict.core.merge import merge from benedict.core.move import move +from benedict.core.nest import nest from benedict.core.remove import remove from benedict.core.rename import rename from benedict.core.search import search diff --git a/benedict/core/nest.py b/benedict/core/nest.py new file mode 100644 index 0000000..d0bd5bb --- /dev/null +++ b/benedict/core/nest.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from benedict.core.groupby import groupby + + +def _nest_items(nested_items, item, id_key, children_key): + children_items = nested_items.pop(item[id_key], []) + item[children_key] = children_items + for child_item in children_items: + _nest_items(nested_items, child_item, id_key, children_key) + + +def nest(items, id_key, parent_id_key, children_key): + if any([id_key == parent_id_key, + id_key == children_key, + parent_id_key == children_key]): + raise ValueError('keys should be different.') + nested_items = groupby(items, parent_id_key) + root_items = nested_items.get(None, []) + for item in root_items: + _nest_items(nested_items, item, id_key, children_key) + return nested_items.get(None) diff --git a/benedict/dicts/__init__.py b/benedict/dicts/__init__.py index bf59bd9..13abebf 100644 --- a/benedict/dicts/__init__.py +++ b/benedict/dicts/__init__.py @@ -12,6 +12,7 @@ from benedict.core import items_sorted_by_values as _items_sorted_by_values from benedict.core import keypaths as _keypaths from benedict.core import merge as _merge from benedict.core import move as _move +from benedict.core import nest as _nest from benedict.core import remove as _remove from benedict.core import rename as _rename from benedict.core import search as _search @@ -141,6 +142,13 @@ class benedict(IODict, KeypathDict, ParseDict): """ _move(self, key_src, key_dest) + def nest(self, key, id_key='id', parent_id_key='parent_id', children_key='children'): + """ + 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. + """ + return _nest(self[key], id_key, parent_id_key, children_key) + def remove(self, keys, *args): """ Remove multiple keys from the current dict instance. diff --git a/tests/core/test_nest.py b/tests/core/test_nest.py new file mode 100644 index 0000000..3bf32dc --- /dev/null +++ b/tests/core/test_nest.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +from benedict.core import clone as _clone +from benedict.core import nest as _nest + +import unittest + + +class nest_test_case(unittest.TestCase): + + def test_nest(self): + l = [ + {'id':1, 'parent_id':None, 'name':'John'}, + {'id':2, 'parent_id':1, 'name':'Frank'}, + {'id':3, 'parent_id':2, 'name':'Tony'}, + {'id':4, 'parent_id':3, 'name':'Jimmy'}, + {'id':5, 'parent_id':1, 'name':'Sam'}, + {'id':6, 'parent_id':3, 'name':'Charles'}, + {'id':7, 'parent_id':2, 'name':'Bob'}, + {'id':8, 'parent_id':3, 'name':'Paul'}, + {'id':9, 'parent_id':None, 'name':'Michael'}, + ] + l_clone = _clone(l) + n = _nest(l, 'id', 'parent_id', 'children') + r = [ + {'id':1, 'parent_id':None, 'name':'John', 'children':[ + {'id':2, 'parent_id':1, 'name':'Frank', 'children':[ + {'id':3, 'parent_id':2, 'name':'Tony', 'children':[ + {'id':4, 'parent_id':3, 'name':'Jimmy', 'children':[], }, + {'id':6, 'parent_id':3, 'name':'Charles', 'children':[], }, + {'id':8, 'parent_id':3, 'name':'Paul', 'children':[], }, + ], }, + {'id':7, 'parent_id':2, 'name':'Bob', 'children':[], }, + ], }, + {'id':5, 'parent_id':1, 'name':'Sam', 'children':[], }, + ]}, + {'id':9, 'parent_id':None, 'name':'Michael', 'children':[], }, + ] + self.assertEqual(l, l_clone) + self.assertEqual(n, r) + + def test_nest_with_wrong_keys(self): + l = [ + {'id':1, 'parent_id':None, 'name':'John'}, + {'id':2, 'parent_id':1, 'name':'Frank'}, + {'id':3, 'parent_id':2, 'name':'Tony'}, + {'id':4, 'parent_id':3, 'name':'Jimmy'}, + {'id':5, 'parent_id':1, 'name':'Sam'}, + {'id':6, 'parent_id':3, 'name':'Charles'}, + {'id':7, 'parent_id':2, 'name':'Bob'}, + {'id':8, 'parent_id':3, 'name':'Paul'}, + {'id':9, 'parent_id':None, 'name':'Michael'}, + ] + with self.assertRaises(ValueError): + n = _nest(l, 'id', 'id', 'children') + with self.assertRaises(ValueError): + n = _nest(l, 'id', 'parent_id', 'id') + with self.assertRaises(ValueError): + n = _nest(l, 'id', 'parent_id', 'parent_id') + + def test_nest_with_wrong_input(self): + l = {'id':1, 'parent_id':None, 'name':'John'} + with self.assertRaises(ValueError): + d = _nest(l, 'id', 'parent_id', 'children') + l = [ + [{'id':1, 'parent_id':None, 'name':'John'},], + [{'id':2, 'parent_id':1, 'name':'Frank'},], + ] + with self.assertRaises(ValueError): + d = _nest(l, 'id', 'parent_id', 'children')