From dd3deffd84ecdbf2538c0ec10a44fd6da067c500 Mon Sep 17 00:00:00 2001 From: marcsello Date: Wed, 26 Feb 2020 18:19:37 +0100 Subject: [PATCH 1/3] added separator option --- morph/__init__.py | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/morph/__init__.py b/morph/__init__.py index af2e9b9..b9b9661 100644 --- a/morph/__init__.py +++ b/morph/__init__.py @@ -111,56 +111,62 @@ def tolist(obj, flat=True, split=True): return [obj] #------------------------------------------------------------------------------ -def flatten(obj): +def flatten(obj, separator='.'): ''' TODO: add docs ''' + if len(separator) != 1: + raise ValueError('Separator must be a single character') + if isseq(obj): ret = [] for item in obj: if isseq(item): - ret.extend(flatten(item)) + ret.extend(flatten(item, separator)) else: ret.append(item) return ret if isdict(obj): ret = dict() for key, value in obj.items(): - for skey, sval in _relflatten(value): + for skey, sval in _relflatten(value, separator): ret[key + skey] = sval return ret raise ValueError( 'only list- and dict-like objects can be flattened, not %r' % (obj,)) -def _relflatten(obj): +def _relflatten(obj, separator): if isseq(obj): for idx, subval in enumerate(obj): - for skey, sval in _relflatten(subval): + for skey, sval in _relflatten(subval, separator): yield '[' + str(idx) + ']' + skey, sval return if isdict(obj): for skey, sval in flatten(obj).items(): - yield '.' + skey, sval + yield separator + skey, sval return yield '', obj #------------------------------------------------------------------------------ -def unflatten(obj): +def unflatten(obj, separator='.'): ''' TODO: add docs ''' + if len(separator) != 1: + raise ValueError('Separator must be a single character') + if not isdict(obj): raise ValueError( 'only dict-like objects can be unflattened, not %r' % (obj,)) ret = dict() sub = dict() for key, value in obj.items(): - if '.' not in key and '[' not in key: + if separator not in key and '[' not in key: ret[key] = value continue - if '.' in key and '[' in key: - idx = min(key.find('.'), key.find('[')) - elif '.' in key: - idx = key.find('.') + if separator in key and '[' in key: + idx = min(key.find(separator), key.find('[')) + elif separator in key: + idx = key.find(separator) else: idx = key.find('[') prefix = key[:idx] @@ -171,9 +177,9 @@ def unflatten(obj): if pfx in ret: raise ValueError( 'conflicting scalar vs. structure for prefix: %s' % (pfx,)) - ret[pfx] = _relunflatten(pfx, values) + ret[pfx] = _relunflatten(pfx, values, separator) return ret -def _relunflatten(pfx, values): +def _relunflatten(pfx, values, separator): if len(values) == 1 and list(values.keys())[0] == '': return list(values.values())[0] typ = set([k[0] for k in values.keys()]) @@ -181,8 +187,8 @@ def _relunflatten(pfx, values): raise ValueError( 'conflicting structures (dict vs. list) for prefix: %s' % (pfx,)) typ = list(typ)[0] - if typ == '.': - return unflatten({k[1:]: v for k, v in values.items()}) + if typ == separator: + return unflatten({k[1:]: v for k, v in values.items()}, separator) tmp = dict() for skey, sval in values.items(): if skey[0] != '[': @@ -203,7 +209,7 @@ def _relunflatten(pfx, values): if pos not in tmp: tmp[pos] = dict() tmp[pos][skey[idx + 1:]] = sval - return [_relunflatten(pfx + '[' + str(pos) + ']', tmp[pos]) + return [_relunflatten(pfx + '[' + str(pos) + ']', tmp[pos], separator) for pos in sorted(tmp.keys())] #------------------------------------------------------------------------------ From 92fd8808574d805dbfadb2c7c7ed67e9106d06b7 Mon Sep 17 00:00:00 2001 From: marcsello Date: Wed, 26 Feb 2020 18:39:38 +0100 Subject: [PATCH 2/3] Added tests for separator --- morph/test.py | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/morph/test.py b/morph/test.py index 0433f3c..8811ee8 100644 --- a/morph/test.py +++ b/morph/test.py @@ -115,6 +115,24 @@ def test_flatten(self): 'a.b[1][2]': 6, }) + #---------------------------------------------------------------------------- + def test_flatten_separator(self): + self.assertEqual( + morph.flatten({'a': {'b': 'c'}}, separator='/'), + {'a/b': 'c'}) + self.assertEqual( + morph.flatten({'a': {'b': 1, 'c': [2, {'d': 3, 'e': 4}]}}, separator='/'), + {'a/b': 1, 'a/c[0]': 2, 'a/c[1]/d': 3, 'a/c[1]/e': 4}) + self.assertEqual( + morph.flatten({'a': {'b': [[1, 2], [3, {'x': 4, 'y': 5}, 6]]}}, separator='/'), + {'a/b[0][0]': 1, + 'a/b[0][1]': 2, + 'a/b[1][0]': 3, + 'a/b[1][1]/x': 4, + 'a/b[1][1]/y': 5, + 'a/b[1][2]': 6, + }) + #---------------------------------------------------------------------------- def test_unflatten_fail(self): with self.assertRaises(ValueError) as cm: @@ -137,6 +155,33 @@ def test_unflatten_fail(self): self.assertEqual( str(cm.exception), 'invalid list syntax (bad index) in key "a[NADA]"') + with self.assertRaises(ValueError) as cm: + morph.unflatten({'a': 'b', 'a.b.c': 'c'},separator='+/') + self.assertEqual( + str(cm.exception), + 'Separator must be a single character') + + #---------------------------------------------------------------------------- + def test_unflatten_separator_ok(self): + self.assertEqual( + morph.unflatten({'a/b' : 'c', 'd' : 'e'}, separator='/'), + {'a' : {'b' : 'c'}, 'd' : 'e'}) + self.assertEqual( + morph.unflatten({'a/b': 1, 'a/c[0]': 2, 'a/c[1]': 3, 'a/c[2]': 4}, separator='/'), + {'a': {'b': 1, 'c': [2, 3, 4]}}) + self.assertEqual( + morph.unflatten({'a/b': 1, 'a/c[0]': 2, 'a/c[1]/d': 3, 'a/c[1]/e': 4}, separator='/'), + {'a': {'b': 1, 'c': [2, {'d': 3, 'e': 4}]}}) + self.assertEqual( + morph.unflatten({ + 'a/b[0][0]': 1, + 'a/b[0][1]': 2, + 'a/b[1][0]': 3, + 'a/b[1][1]/x': 4, + 'a/b[1][1]/y': 5, + 'a/b[1][2]': 6, + }, separator='/'), + {'a': {'b': [[1, 2], [3, {'x': 4, 'y': 5}, 6]]}}) #---------------------------------------------------------------------------- def test_unflatten_ok(self): From 3196ae122e79136b79ee29ed871f8c983a71d093 Mon Sep 17 00:00:00 2001 From: marcsello Date: Wed, 26 Feb 2020 18:39:55 +0100 Subject: [PATCH 3/3] Fixed a missing separator --- morph/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/morph/__init__.py b/morph/__init__.py index b9b9661..01a3161 100644 --- a/morph/__init__.py +++ b/morph/__init__.py @@ -141,7 +141,7 @@ def _relflatten(obj, separator): yield '[' + str(idx) + ']' + skey, sval return if isdict(obj): - for skey, sval in flatten(obj).items(): + for skey, sval in flatten(obj, separator).items(): yield separator + skey, sval return yield '', obj