flatten-dict 0.5.0__py3-none-any.whl
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.
- flatten_dict/__init__.py +7 -0
- flatten_dict/flatten_dict.py +167 -0
- flatten_dict/reducer.py +11 -0
- flatten_dict/reducers.py +46 -0
- flatten_dict/splitter.py +11 -0
- flatten_dict/splitters.py +40 -0
- flatten_dict/tests/flatten_dict_test.py +474 -0
- flatten_dict-0.5.0.dist-info/METADATA +287 -0
- flatten_dict-0.5.0.dist-info/RECORD +11 -0
- flatten_dict-0.5.0.dist-info/WHEEL +4 -0
- flatten_dict-0.5.0.dist-info/licenses/LICENSE +21 -0
flatten_dict/__init__.py
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import inspect
|
|
2
|
+
from collections.abc import Mapping
|
|
3
|
+
|
|
4
|
+
from .reducers import dot_reducer, path_reducer, tuple_reducer, underscore_reducer
|
|
5
|
+
from .splitters import dot_splitter, path_splitter, tuple_splitter, underscore_splitter
|
|
6
|
+
|
|
7
|
+
REDUCER_DICT = {
|
|
8
|
+
"tuple": tuple_reducer,
|
|
9
|
+
"path": path_reducer,
|
|
10
|
+
"dot": dot_reducer,
|
|
11
|
+
"underscore": underscore_reducer,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
SPLITTER_DICT = {
|
|
15
|
+
"tuple": tuple_splitter,
|
|
16
|
+
"path": path_splitter,
|
|
17
|
+
"dot": dot_splitter,
|
|
18
|
+
"underscore": underscore_splitter,
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def flatten(
|
|
23
|
+
d,
|
|
24
|
+
reducer="tuple",
|
|
25
|
+
inverse=False,
|
|
26
|
+
max_flatten_depth=None,
|
|
27
|
+
enumerate_types=(),
|
|
28
|
+
keep_empty_types=(),
|
|
29
|
+
):
|
|
30
|
+
"""Flatten `Mapping` object.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
d : dict-like object
|
|
35
|
+
The dict that will be flattened.
|
|
36
|
+
reducer : {'tuple', 'path', 'underscore', 'dot', Callable}
|
|
37
|
+
The key joining method. If a `Callable` is given, the `Callable` will be
|
|
38
|
+
used to reduce.
|
|
39
|
+
'tuple': The resulting key will be tuple of the original keys.
|
|
40
|
+
'path': Use `os.path.join` to join keys.
|
|
41
|
+
'underscore': Use underscores to join keys.
|
|
42
|
+
'dot': Use dots to join keys.
|
|
43
|
+
inverse : bool
|
|
44
|
+
Whether you want invert the resulting key and value.
|
|
45
|
+
max_flatten_depth : Optional[int]
|
|
46
|
+
Maximum depth to merge.
|
|
47
|
+
enumerate_types : Sequence[type]
|
|
48
|
+
Flatten these types using `enumerate`.
|
|
49
|
+
For example, if we set `enumerate_types` to ``(list,)``,
|
|
50
|
+
`list` indices become keys: ``{'a': ['b', 'c']}`` -> ``{('a', 0): 'b', ('a', 1): 'c'}``.
|
|
51
|
+
keep_empty_types : Sequence[type]
|
|
52
|
+
By default, ``flatten({1: 2, 3: {}})`` will give you ``{(1,): 2}``, that is, the key ``3``
|
|
53
|
+
will disappear.
|
|
54
|
+
This is also applied for the types in `enumerate_types`, that is,
|
|
55
|
+
``flatten({1: 2, 3: []}, enumerate_types=(list,))`` will give you ``{(1,): 2}``.
|
|
56
|
+
If you want to keep those empty values, you can specify the types in `keep_empty_types`:
|
|
57
|
+
|
|
58
|
+
>>> flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
|
|
59
|
+
{(1,): 2, (3,): {}}
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
flat_dict : dict
|
|
64
|
+
"""
|
|
65
|
+
enumerate_types = tuple(enumerate_types)
|
|
66
|
+
flattenable_types = (Mapping,) + enumerate_types
|
|
67
|
+
if not isinstance(d, flattenable_types):
|
|
68
|
+
raise ValueError(
|
|
69
|
+
"argument type %s is not in the flattenalbe types %s"
|
|
70
|
+
% (type(d), flattenable_types)
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# check max_flatten_depth
|
|
74
|
+
if max_flatten_depth is not None and max_flatten_depth < 1:
|
|
75
|
+
raise ValueError("max_flatten_depth should not be less than 1.")
|
|
76
|
+
|
|
77
|
+
if isinstance(reducer, str):
|
|
78
|
+
reducer = REDUCER_DICT[reducer]
|
|
79
|
+
reducer_accepts_parent_obj = len(inspect.signature(reducer).parameters) == 3
|
|
80
|
+
flat_dict = {}
|
|
81
|
+
|
|
82
|
+
def _flatten(_d, depth, parent=None):
|
|
83
|
+
key_value_iterable = (
|
|
84
|
+
enumerate(_d) if isinstance(_d, enumerate_types) else _d.items()
|
|
85
|
+
)
|
|
86
|
+
has_item = False
|
|
87
|
+
for key, value in key_value_iterable:
|
|
88
|
+
has_item = True
|
|
89
|
+
if reducer_accepts_parent_obj:
|
|
90
|
+
flat_key = reducer(parent, key, _d)
|
|
91
|
+
else:
|
|
92
|
+
flat_key = reducer(parent, key)
|
|
93
|
+
if isinstance(value, flattenable_types) and (
|
|
94
|
+
max_flatten_depth is None or depth < max_flatten_depth
|
|
95
|
+
):
|
|
96
|
+
# recursively build the result
|
|
97
|
+
has_child = _flatten(value, depth=depth + 1, parent=flat_key)
|
|
98
|
+
if has_child or not isinstance(value, keep_empty_types):
|
|
99
|
+
# ignore the key in this level because it already has child key
|
|
100
|
+
# or its value is empty
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
# add an item to the result
|
|
104
|
+
if inverse:
|
|
105
|
+
flat_key, value = value, flat_key
|
|
106
|
+
if flat_key in flat_dict:
|
|
107
|
+
raise ValueError("duplicated key '{}'".format(flat_key))
|
|
108
|
+
flat_dict[flat_key] = value
|
|
109
|
+
|
|
110
|
+
return has_item
|
|
111
|
+
|
|
112
|
+
_flatten(d, depth=1)
|
|
113
|
+
return flat_dict
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def nested_set_dict(d, keys, value):
|
|
117
|
+
"""Set a value to a sequence of nested keys.
|
|
118
|
+
|
|
119
|
+
Parameters
|
|
120
|
+
----------
|
|
121
|
+
d : Mapping
|
|
122
|
+
keys : Sequence[str]
|
|
123
|
+
value : Any
|
|
124
|
+
"""
|
|
125
|
+
assert keys
|
|
126
|
+
key = keys[0]
|
|
127
|
+
if len(keys) == 1:
|
|
128
|
+
if key in d:
|
|
129
|
+
raise ValueError("duplicated key '{}'".format(key))
|
|
130
|
+
d[key] = value
|
|
131
|
+
return
|
|
132
|
+
d = d.setdefault(key, {})
|
|
133
|
+
nested_set_dict(d, keys[1:], value)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def unflatten(d, splitter="tuple", inverse=False):
|
|
137
|
+
"""Unflatten dict-like object.
|
|
138
|
+
|
|
139
|
+
Parameters
|
|
140
|
+
----------
|
|
141
|
+
d : dict-like object
|
|
142
|
+
The dict that will be unflattened.
|
|
143
|
+
splitter : {'tuple', 'path', 'underscore', 'dot', Callable}
|
|
144
|
+
The key splitting method. If a Callable is given, the Callable will be
|
|
145
|
+
used to split `d`.
|
|
146
|
+
'tuple': Use each element in the tuple key as the key of the unflattened dict.
|
|
147
|
+
'path': Use `pathlib.Path.parts` to split keys.
|
|
148
|
+
'underscore': Use underscores to split keys.
|
|
149
|
+
'dot': Use dots to split keys.
|
|
150
|
+
inverse : bool
|
|
151
|
+
Whether you want to invert the key and value before flattening.
|
|
152
|
+
|
|
153
|
+
Returns
|
|
154
|
+
-------
|
|
155
|
+
unflattened_dict : dict
|
|
156
|
+
"""
|
|
157
|
+
if isinstance(splitter, str):
|
|
158
|
+
splitter = SPLITTER_DICT[splitter]
|
|
159
|
+
|
|
160
|
+
unflattened_dict = {}
|
|
161
|
+
for flat_key, value in d.items():
|
|
162
|
+
if inverse:
|
|
163
|
+
flat_key, value = value, flat_key
|
|
164
|
+
key_tuple = splitter(flat_key)
|
|
165
|
+
nested_set_dict(unflattened_dict, key_tuple, value)
|
|
166
|
+
|
|
167
|
+
return unflattened_dict
|
flatten_dict/reducer.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""`flatten_dict.reducer` is deprecated in favor of `flatten_dict.reducers`."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from .reducers import * # noqa: F401, F403
|
|
6
|
+
|
|
7
|
+
warnings.warn(
|
|
8
|
+
"`flatten_dict.reducer` is deprecated in favor of `flatten_dict.reducers`.",
|
|
9
|
+
FutureWarning,
|
|
10
|
+
stacklevel=2,
|
|
11
|
+
)
|
flatten_dict/reducers.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
def tuple_reducer(k1, k2):
|
|
2
|
+
if k1 is None:
|
|
3
|
+
return (k2,)
|
|
4
|
+
return k1 + (k2,)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def path_reducer(k1, k2):
|
|
8
|
+
import os.path
|
|
9
|
+
|
|
10
|
+
if k1 is None:
|
|
11
|
+
return k2
|
|
12
|
+
return os.path.join(k1, k2)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def dot_reducer(k1, k2):
|
|
16
|
+
if k1 is None:
|
|
17
|
+
return k2
|
|
18
|
+
return "{}.{}".format(k1, k2)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def underscore_reducer(k1, k2):
|
|
22
|
+
if k1 is None:
|
|
23
|
+
return k2
|
|
24
|
+
return "{}_{}".format(k1, k2)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def make_reducer(delimiter):
|
|
28
|
+
"""Create a reducer with a custom delimiter.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
delimiter : str
|
|
33
|
+
Delimiter to use to join keys.
|
|
34
|
+
|
|
35
|
+
Returns
|
|
36
|
+
-------
|
|
37
|
+
f : Callable
|
|
38
|
+
Callable that can be passed to `flatten()`'s `reducer` argument.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def f(k1, k2):
|
|
42
|
+
if k1 is None:
|
|
43
|
+
return k2
|
|
44
|
+
return "{}{}{}".format(k1, delimiter, k2)
|
|
45
|
+
|
|
46
|
+
return f
|
flatten_dict/splitter.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""`flatten_dict.splitter` is deprecated in favor of `flatten_dict.splitters`."""
|
|
2
|
+
|
|
3
|
+
import warnings
|
|
4
|
+
|
|
5
|
+
from .splitters import * # noqa: F401, F403
|
|
6
|
+
|
|
7
|
+
warnings.warn(
|
|
8
|
+
"`flatten_dict.splitter` is deprecated in favor of `flatten_dict.splitters`.",
|
|
9
|
+
FutureWarning,
|
|
10
|
+
stacklevel=2,
|
|
11
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
def tuple_splitter(flat_key):
|
|
2
|
+
return flat_key
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def path_splitter(flat_key):
|
|
6
|
+
from pathlib import PurePath
|
|
7
|
+
|
|
8
|
+
keys = PurePath(flat_key).parts
|
|
9
|
+
return keys
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def dot_splitter(flat_key):
|
|
13
|
+
keys = tuple(flat_key.split("."))
|
|
14
|
+
return keys
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def underscore_splitter(flat_key):
|
|
18
|
+
keys = tuple(flat_key.split("_"))
|
|
19
|
+
return keys
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def make_splitter(delimiter):
|
|
23
|
+
"""Create a reducer with a custom delimiter.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
delimiter : str
|
|
28
|
+
Delimiter to use to split keys.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
f : Callable
|
|
33
|
+
Callable that can be passed to ``unflatten``'s ``splitter`` argument.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def f(flat_key):
|
|
37
|
+
keys = tuple(flat_key.split(delimiter))
|
|
38
|
+
return keys
|
|
39
|
+
|
|
40
|
+
return f
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os.path
|
|
3
|
+
from types import GeneratorType
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from flatten_dict import flatten, unflatten
|
|
8
|
+
from flatten_dict.reducers import (
|
|
9
|
+
make_reducer,
|
|
10
|
+
path_reducer,
|
|
11
|
+
tuple_reducer,
|
|
12
|
+
underscore_reducer,
|
|
13
|
+
)
|
|
14
|
+
from flatten_dict.splitters import (
|
|
15
|
+
make_splitter,
|
|
16
|
+
path_splitter,
|
|
17
|
+
tuple_splitter,
|
|
18
|
+
underscore_splitter,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@pytest.fixture
|
|
23
|
+
def normal_dict():
|
|
24
|
+
return {
|
|
25
|
+
"a": "0",
|
|
26
|
+
"b": {
|
|
27
|
+
"a": "1.0",
|
|
28
|
+
"b": "1.1",
|
|
29
|
+
},
|
|
30
|
+
"c": {
|
|
31
|
+
"a": "2.0",
|
|
32
|
+
"b": {
|
|
33
|
+
"a": "2.1.0",
|
|
34
|
+
"b": "2.1.1",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@pytest.fixture
|
|
41
|
+
def flat_tuple_dict():
|
|
42
|
+
return {
|
|
43
|
+
("a",): "0",
|
|
44
|
+
("b", "a"): "1.0",
|
|
45
|
+
("b", "b"): "1.1",
|
|
46
|
+
("c", "a"): "2.0",
|
|
47
|
+
("c", "b", "a"): "2.1.0",
|
|
48
|
+
("c", "b", "b"): "2.1.1",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@pytest.fixture
|
|
53
|
+
def flat_tuple_dict_depth2():
|
|
54
|
+
return {
|
|
55
|
+
("a",): "0",
|
|
56
|
+
("b", "a"): "1.0",
|
|
57
|
+
("b", "b"): "1.1",
|
|
58
|
+
("c", "a"): "2.0",
|
|
59
|
+
("c", "b"): {"a": "2.1.0", "b": "2.1.1"},
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@pytest.fixture
|
|
64
|
+
def normal_dict_with_nested_lists():
|
|
65
|
+
return {
|
|
66
|
+
"aaa": "0",
|
|
67
|
+
"bbb": ["1.1", "1.2", "1.3"],
|
|
68
|
+
"ccc": [{"aaa": "2.1.1"}, {"aaa": "2.2.1"}],
|
|
69
|
+
"ddd": [["3.1.1", "3.1.2", "3.1.3"], ["3.2.1", "3.2.2", "3.2.3"]],
|
|
70
|
+
"eee": [
|
|
71
|
+
{"aaa": ["4.1.1", "4.1.2", "4.1.3"]},
|
|
72
|
+
{"bbb": ["4.2.1", "4.2.2", "4.2.3"]},
|
|
73
|
+
],
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@pytest.fixture
|
|
78
|
+
def flat_dict_with_nested_lists_with_list_syntax():
|
|
79
|
+
return {
|
|
80
|
+
"aaa": "0",
|
|
81
|
+
"bbb[0]": "1.1",
|
|
82
|
+
"bbb[1]": "1.2",
|
|
83
|
+
"bbb[2]": "1.3",
|
|
84
|
+
"ccc[0].aaa": "2.1.1",
|
|
85
|
+
"ccc[1].aaa": "2.2.1",
|
|
86
|
+
"ddd[0][0]": "3.1.1",
|
|
87
|
+
"ddd[0][1]": "3.1.2",
|
|
88
|
+
"ddd[0][2]": "3.1.3",
|
|
89
|
+
"ddd[1][0]": "3.2.1",
|
|
90
|
+
"ddd[1][1]": "3.2.2",
|
|
91
|
+
"ddd[1][2]": "3.2.3",
|
|
92
|
+
"eee[0].aaa[0]": "4.1.1",
|
|
93
|
+
"eee[0].aaa[1]": "4.1.2",
|
|
94
|
+
"eee[0].aaa[2]": "4.1.3",
|
|
95
|
+
"eee[1].bbb[0]": "4.2.1",
|
|
96
|
+
"eee[1].bbb[1]": "4.2.2",
|
|
97
|
+
"eee[1].bbb[2]": "4.2.3",
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_flat_tuple_dict(flat_tuple_dict):
|
|
102
|
+
return flat_tuple_dict
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_flat_path_dict(flat_tuple_dict):
|
|
106
|
+
return {os.path.join(*k): v for k, v in flat_tuple_dict.items()}
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def get_flat_underscore_dict(flat_tuple_dict):
|
|
110
|
+
return {"_".join(k): v for k, v in flat_tuple_dict.items()}
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@pytest.fixture
|
|
114
|
+
def inv_flat_tuple_dict(flat_tuple_dict):
|
|
115
|
+
return {v: k for k, v in flat_tuple_dict.items()}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_flatten_dict(normal_dict, flat_tuple_dict):
|
|
119
|
+
assert flatten(normal_dict) == flat_tuple_dict
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_flatten_dict_invalid_depth_limit(normal_dict):
|
|
123
|
+
with pytest.raises(ValueError):
|
|
124
|
+
flatten(normal_dict, max_flatten_depth=0)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def test_flatten_dict_depth_limit_1(normal_dict):
|
|
128
|
+
flattened = flatten(normal_dict, max_flatten_depth=1)
|
|
129
|
+
values_before = sorted(
|
|
130
|
+
flattened.values(), key=lambda x: json.dumps(x, sort_keys=True)
|
|
131
|
+
)
|
|
132
|
+
values_after = sorted(
|
|
133
|
+
normal_dict.values(), key=lambda x: json.dumps(x, sort_keys=True)
|
|
134
|
+
)
|
|
135
|
+
assert values_before == values_after
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_flatten_dict_depth_limit_2(normal_dict, flat_tuple_dict_depth2):
|
|
139
|
+
assert flatten(normal_dict, max_flatten_depth=2) == flat_tuple_dict_depth2
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_flatten_dict_irrelevant_depth_limit(normal_dict, flat_tuple_dict):
|
|
143
|
+
assert flatten(normal_dict, max_flatten_depth=3) == flat_tuple_dict
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def test_flatten_nonflattenable_type():
|
|
147
|
+
with pytest.raises(ValueError):
|
|
148
|
+
flatten([])
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
@pytest.mark.parametrize(
|
|
152
|
+
"reducer, expected_flat_dict_func",
|
|
153
|
+
[
|
|
154
|
+
("tuple", get_flat_tuple_dict),
|
|
155
|
+
("path", get_flat_path_dict),
|
|
156
|
+
("underscore", get_flat_underscore_dict),
|
|
157
|
+
(tuple_reducer, get_flat_tuple_dict),
|
|
158
|
+
(path_reducer, get_flat_path_dict),
|
|
159
|
+
(underscore_reducer, get_flat_underscore_dict),
|
|
160
|
+
],
|
|
161
|
+
)
|
|
162
|
+
def test_flatten_dict_with_reducer(
|
|
163
|
+
normal_dict, flat_tuple_dict, reducer, expected_flat_dict_func
|
|
164
|
+
):
|
|
165
|
+
expected_flat_dict = expected_flat_dict_func(flat_tuple_dict)
|
|
166
|
+
assert flatten(normal_dict, reducer=reducer) == expected_flat_dict
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def test_flatten_dict_inverse(normal_dict, inv_flat_tuple_dict):
|
|
170
|
+
assert flatten(normal_dict, inverse=True) == inv_flat_tuple_dict
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_flatten_dict_inverse_with_duplicated_value(normal_dict):
|
|
174
|
+
dup_val_dict = normal_dict.copy()
|
|
175
|
+
dup_val_dict["a"] = "2.1.1"
|
|
176
|
+
with pytest.raises(ValueError):
|
|
177
|
+
flatten(dup_val_dict, inverse=True)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_flatten_dict_with_list_syntax(
|
|
181
|
+
normal_dict_with_nested_lists, flat_dict_with_nested_lists_with_list_syntax
|
|
182
|
+
):
|
|
183
|
+
def _reducer(parent_path, key, parent_obj):
|
|
184
|
+
if parent_path is None:
|
|
185
|
+
return key
|
|
186
|
+
if isinstance(parent_obj, list):
|
|
187
|
+
return "{}[{}]".format(parent_path, key)
|
|
188
|
+
return "{}.{}".format(parent_path, key)
|
|
189
|
+
|
|
190
|
+
assert (
|
|
191
|
+
flatten(
|
|
192
|
+
normal_dict_with_nested_lists, reducer=_reducer, enumerate_types=(list,)
|
|
193
|
+
)
|
|
194
|
+
== flat_dict_with_nested_lists_with_list_syntax
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
def test_unflatten_dict(normal_dict, flat_tuple_dict):
|
|
199
|
+
assert unflatten(flat_tuple_dict) == normal_dict
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def test_unflatten_dict_inverse(normal_dict, inv_flat_tuple_dict):
|
|
203
|
+
assert unflatten(inv_flat_tuple_dict, inverse=True) == normal_dict
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
@pytest.mark.parametrize(
|
|
207
|
+
"splitter, flat_dict_func",
|
|
208
|
+
[
|
|
209
|
+
("tuple", get_flat_tuple_dict),
|
|
210
|
+
("path", get_flat_path_dict),
|
|
211
|
+
("underscore", get_flat_underscore_dict),
|
|
212
|
+
(tuple_splitter, get_flat_tuple_dict),
|
|
213
|
+
(path_splitter, get_flat_path_dict),
|
|
214
|
+
(underscore_splitter, get_flat_underscore_dict),
|
|
215
|
+
],
|
|
216
|
+
)
|
|
217
|
+
def test_unflatten_dict_with_splitter(
|
|
218
|
+
normal_dict, flat_tuple_dict, splitter, flat_dict_func
|
|
219
|
+
):
|
|
220
|
+
flat_dict = flat_dict_func(flat_tuple_dict)
|
|
221
|
+
assert unflatten(flat_dict, splitter=splitter) == normal_dict
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_unflatten_dict_inverse_with_duplicated_value(
|
|
225
|
+
flat_tuple_dict, inv_flat_tuple_dict
|
|
226
|
+
):
|
|
227
|
+
inv_flat_tuple_dict["2.1.1"] = ("c", "b", "a")
|
|
228
|
+
with pytest.raises(ValueError):
|
|
229
|
+
unflatten(inv_flat_tuple_dict, inverse=True)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@pytest.fixture
|
|
233
|
+
def dict_with_list():
|
|
234
|
+
return {
|
|
235
|
+
"a": "0",
|
|
236
|
+
"b": {
|
|
237
|
+
"a": "1.0",
|
|
238
|
+
"b": "1.1",
|
|
239
|
+
},
|
|
240
|
+
"c": {
|
|
241
|
+
"a": "2.0",
|
|
242
|
+
"b": {
|
|
243
|
+
"a": "2.1.0",
|
|
244
|
+
"b": ["2.1.1.0", "2.1.1.1"],
|
|
245
|
+
"c": [],
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
@pytest.fixture
|
|
252
|
+
def flat_tuple_dict_with_list():
|
|
253
|
+
return {
|
|
254
|
+
("a",): "0",
|
|
255
|
+
("b", "a"): "1.0",
|
|
256
|
+
("b", "b"): "1.1",
|
|
257
|
+
("c", "a"): "2.0",
|
|
258
|
+
("c", "b", "a"): "2.1.0",
|
|
259
|
+
("c", "b", "b"): ["2.1.1.0", "2.1.1.1"],
|
|
260
|
+
("c", "b", "c"): [],
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
@pytest.fixture
|
|
265
|
+
def flat_tuple_dict_with_list_depth2():
|
|
266
|
+
return {
|
|
267
|
+
("a",): "0",
|
|
268
|
+
("b", "a"): "1.0",
|
|
269
|
+
("b", "b"): "1.1",
|
|
270
|
+
("c", "a"): "2.0",
|
|
271
|
+
("c", "b"): {
|
|
272
|
+
"a": "2.1.0",
|
|
273
|
+
"b": ["2.1.1.0", "2.1.1.1"],
|
|
274
|
+
"c": [],
|
|
275
|
+
},
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def test_flatten_dict_with_list(dict_with_list, flat_tuple_dict_with_list):
|
|
280
|
+
assert flatten(dict_with_list) == flat_tuple_dict_with_list
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def test_flatten_dict_with_list_depth_limit_1(dict_with_list):
|
|
284
|
+
flattened = flatten(dict_with_list, max_flatten_depth=1)
|
|
285
|
+
before = sorted(dict_with_list.items(), key=lambda x: x[0])
|
|
286
|
+
after = sorted(flattened.items(), key=lambda x: x[0])
|
|
287
|
+
# flatten with the default reducer transforms keys to tuples
|
|
288
|
+
after = [(x[0][0], x[1]) for x in after]
|
|
289
|
+
assert before == after
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def test_flatten_dict_with_list_depth_limit_2(
|
|
293
|
+
dict_with_list, flat_tuple_dict_with_list_depth2
|
|
294
|
+
):
|
|
295
|
+
assert (
|
|
296
|
+
flatten(dict_with_list, max_flatten_depth=2) == flat_tuple_dict_with_list_depth2
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
def test_flatten_dict_with_list_irrelevant_depth_limit(
|
|
301
|
+
dict_with_list, flat_tuple_dict_with_list
|
|
302
|
+
):
|
|
303
|
+
assert flatten(dict_with_list, max_flatten_depth=3) == flat_tuple_dict_with_list
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@pytest.mark.parametrize(
|
|
307
|
+
"reducer, expected_flat_dict_func",
|
|
308
|
+
[
|
|
309
|
+
("tuple", get_flat_tuple_dict),
|
|
310
|
+
("path", get_flat_path_dict),
|
|
311
|
+
("underscore", get_flat_underscore_dict),
|
|
312
|
+
(tuple_reducer, get_flat_tuple_dict),
|
|
313
|
+
(path_reducer, get_flat_path_dict),
|
|
314
|
+
(underscore_reducer, get_flat_underscore_dict),
|
|
315
|
+
],
|
|
316
|
+
)
|
|
317
|
+
def test_flatten_dict_with_list_with_reducer(
|
|
318
|
+
dict_with_list, flat_tuple_dict_with_list, reducer, expected_flat_dict_func
|
|
319
|
+
):
|
|
320
|
+
expected_flat_dict = expected_flat_dict_func(flat_tuple_dict_with_list)
|
|
321
|
+
assert flatten(dict_with_list, reducer=reducer) == expected_flat_dict
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
def test_unflatten_dict_with_list(dict_with_list, flat_tuple_dict_with_list):
|
|
325
|
+
assert unflatten(flat_tuple_dict_with_list) == dict_with_list
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
@pytest.mark.parametrize(
|
|
329
|
+
"splitter, flat_dict_func",
|
|
330
|
+
[
|
|
331
|
+
("tuple", get_flat_tuple_dict),
|
|
332
|
+
("path", get_flat_path_dict),
|
|
333
|
+
("underscore", get_flat_underscore_dict),
|
|
334
|
+
(tuple_splitter, get_flat_tuple_dict),
|
|
335
|
+
(path_splitter, get_flat_path_dict),
|
|
336
|
+
(underscore_splitter, get_flat_underscore_dict),
|
|
337
|
+
],
|
|
338
|
+
)
|
|
339
|
+
def test_unflatten_dict_with_list_with_splitter(
|
|
340
|
+
dict_with_list, flat_tuple_dict_with_list, splitter, flat_dict_func
|
|
341
|
+
):
|
|
342
|
+
flat_dict = flat_dict_func(flat_tuple_dict_with_list)
|
|
343
|
+
assert unflatten(flat_dict, splitter=splitter) == dict_with_list
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
@pytest.fixture
|
|
347
|
+
def flat_tuple_dict_with_enumerated_list():
|
|
348
|
+
return {
|
|
349
|
+
("a",): "0",
|
|
350
|
+
("b", "a"): "1.0",
|
|
351
|
+
("b", "b"): "1.1",
|
|
352
|
+
("c", "a"): "2.0",
|
|
353
|
+
("c", "b", "a"): "2.1.0",
|
|
354
|
+
("c", "b", "b", 0): "2.1.1.0",
|
|
355
|
+
("c", "b", "b", 1): "2.1.1.1",
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def test_flatten_dict_with_list_with_enumerate_types(
|
|
360
|
+
dict_with_list, flat_tuple_dict_with_enumerated_list
|
|
361
|
+
):
|
|
362
|
+
assert (
|
|
363
|
+
flatten(dict_with_list, enumerate_types=(list,))
|
|
364
|
+
== flat_tuple_dict_with_enumerated_list
|
|
365
|
+
)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def test_flatten_list():
|
|
369
|
+
assert flatten([1, 2], enumerate_types=(list,)) == {(0,): 1, (1,): 2}
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
@pytest.fixture
|
|
373
|
+
def dict_with_generator():
|
|
374
|
+
return {
|
|
375
|
+
"a": "0",
|
|
376
|
+
"b": {
|
|
377
|
+
"a": "1.0",
|
|
378
|
+
"b": "1.1",
|
|
379
|
+
},
|
|
380
|
+
"c": {
|
|
381
|
+
"a": "2.0",
|
|
382
|
+
"b": {
|
|
383
|
+
"a": "2.1.0",
|
|
384
|
+
"b": ("2.1.1.%d" % i for i in range(2)),
|
|
385
|
+
"c": (i for i in ()), # empty generator
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
def test_flatten_dict_with_generator_with_enumerate_types(
|
|
392
|
+
dict_with_generator, flat_tuple_dict_with_enumerated_list
|
|
393
|
+
):
|
|
394
|
+
assert (
|
|
395
|
+
flatten(dict_with_generator, enumerate_types=(GeneratorType,))
|
|
396
|
+
== flat_tuple_dict_with_enumerated_list
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
@pytest.fixture
|
|
401
|
+
def dict_with_empty_dict():
|
|
402
|
+
return {
|
|
403
|
+
"a": "0",
|
|
404
|
+
"b": {
|
|
405
|
+
"a": "1.0",
|
|
406
|
+
"b": "1.1",
|
|
407
|
+
},
|
|
408
|
+
"c": {
|
|
409
|
+
"a": "2.0",
|
|
410
|
+
"b": {
|
|
411
|
+
"a": "2.1.0",
|
|
412
|
+
"b": "2.1.1",
|
|
413
|
+
"c": {},
|
|
414
|
+
},
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
@pytest.fixture
|
|
420
|
+
def flat_tuple_dict_with_empty_dict():
|
|
421
|
+
return {
|
|
422
|
+
("a",): "0",
|
|
423
|
+
("b", "a"): "1.0",
|
|
424
|
+
("b", "b"): "1.1",
|
|
425
|
+
("c", "a"): "2.0",
|
|
426
|
+
("c", "b", "a"): "2.1.0",
|
|
427
|
+
("c", "b", "b"): "2.1.1",
|
|
428
|
+
("c", "b", "c"): {},
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def test_flatten_dict_with_empty_dict(dict_with_empty_dict, flat_tuple_dict):
|
|
433
|
+
assert flatten(dict_with_empty_dict) == flat_tuple_dict
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def test_flatten_dict_with_empty_dict_kept(
|
|
437
|
+
dict_with_empty_dict, flat_tuple_dict_with_empty_dict
|
|
438
|
+
):
|
|
439
|
+
assert (
|
|
440
|
+
flatten(dict_with_empty_dict, keep_empty_types=(dict,))
|
|
441
|
+
== flat_tuple_dict_with_empty_dict
|
|
442
|
+
)
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def test_flatten_dict_with_keep_empty_types(normal_dict, flat_tuple_dict):
|
|
446
|
+
assert flatten(normal_dict, keep_empty_types=(dict, str)) == flat_tuple_dict
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
@pytest.mark.parametrize(
|
|
450
|
+
"delimiter, delimiter_equivalent", [(".", "dot"), ("_", "underscore")]
|
|
451
|
+
)
|
|
452
|
+
def test_make_reducer(normal_dict, delimiter, delimiter_equivalent):
|
|
453
|
+
reducer = make_reducer(delimiter)
|
|
454
|
+
flattened_dict_using_make_reducer = flatten(normal_dict, reducer=reducer)
|
|
455
|
+
flattened_dict_using_equivalent_reducer = flatten(
|
|
456
|
+
normal_dict, reducer=delimiter_equivalent
|
|
457
|
+
)
|
|
458
|
+
assert flattened_dict_using_make_reducer == flattened_dict_using_equivalent_reducer
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
@pytest.mark.parametrize(
|
|
462
|
+
"delimiter, delimiter_equivalent", [(".", "dot"), ("_", "underscore")]
|
|
463
|
+
)
|
|
464
|
+
def test_make_splitter(normal_dict, delimiter, delimiter_equivalent):
|
|
465
|
+
splitter = make_splitter(delimiter)
|
|
466
|
+
flat_dict = flatten(normal_dict, delimiter_equivalent)
|
|
467
|
+
unflattened_dict_using_make_splitter = unflatten(flat_dict, splitter=splitter)
|
|
468
|
+
unflattened_dict_using_equivalent_splitter = unflatten(
|
|
469
|
+
flat_dict, splitter=delimiter_equivalent
|
|
470
|
+
)
|
|
471
|
+
assert (
|
|
472
|
+
unflattened_dict_using_make_splitter
|
|
473
|
+
== unflattened_dict_using_equivalent_splitter
|
|
474
|
+
)
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: flatten-dict
|
|
3
|
+
Version: 0.5.0
|
|
4
|
+
Summary: A flexible utility for flattening and unflattening dict-like objects in Python.
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Author: Ian Lin
|
|
8
|
+
Author-email: you@example.com
|
|
9
|
+
Requires-Python: >=3.10,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Utilities
|
|
18
|
+
Project-URL: Homepage, https://github.com/ianlini/flatten-dict
|
|
19
|
+
Project-URL: Repository, https://github.com/ianlini/flatten-dict
|
|
20
|
+
Description-Content-Type: text/x-rst
|
|
21
|
+
|
|
22
|
+
flatten-dict
|
|
23
|
+
============
|
|
24
|
+
.. image:: https://github.com/ianlini/flatten-dict/actions/workflows/main.yml/badge.svg
|
|
25
|
+
:target: https://github.com/ianlini/flatten-dict/actions
|
|
26
|
+
.. image:: https://img.shields.io/pypi/v/flatten-dict.svg
|
|
27
|
+
:target: https://pypi.org/project/flatten-dict/
|
|
28
|
+
.. image:: https://img.shields.io/pypi/l/flatten-dict.svg
|
|
29
|
+
:target: https://github.com/ianlini/flatten-dict/blob/master/LICENSE
|
|
30
|
+
.. image:: https://img.shields.io/github/stars/ianlini/flatten-dict.svg?style=social
|
|
31
|
+
:target: https://github.com/ianlini/flatten-dict
|
|
32
|
+
|
|
33
|
+
A flexible utility for flattening and unflattening dict-like objects in Python.
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
Introduction
|
|
37
|
+
------------
|
|
38
|
+
This package provides a function ``flatten()`` for flattening dict-like objects in Python (tested with 3.10-3.15).
|
|
39
|
+
It also provides some key joining methods (reducer), and you can choose the reducer you want or even implement your own reducer.
|
|
40
|
+
You can also invert the resulting flat dict using ``unflatten()``.
|
|
41
|
+
|
|
42
|
+
Installation
|
|
43
|
+
------------
|
|
44
|
+
|
|
45
|
+
.. code-block:: bash
|
|
46
|
+
|
|
47
|
+
pip install flatten-dict
|
|
48
|
+
|
|
49
|
+
Documentation
|
|
50
|
+
-------------
|
|
51
|
+
|
|
52
|
+
Flatten
|
|
53
|
+
```````
|
|
54
|
+
|
|
55
|
+
.. code-block:: python
|
|
56
|
+
|
|
57
|
+
def flatten(d, reducer='tuple', inverse=False, enumerate_types=(), keep_empty_types=()):
|
|
58
|
+
"""Flatten `Mapping` object.
|
|
59
|
+
|
|
60
|
+
Parameters
|
|
61
|
+
----------
|
|
62
|
+
d : dict-like object
|
|
63
|
+
The dict that will be flattened.
|
|
64
|
+
reducer : {'tuple', 'path', 'underscore', 'dot', Callable}
|
|
65
|
+
The key joining method. If a `Callable` is given, the `Callable` will be
|
|
66
|
+
used to reduce.
|
|
67
|
+
'tuple': The resulting key will be tuple of the original keys.
|
|
68
|
+
'path': Use `os.path.join` to join keys.
|
|
69
|
+
'underscore': Use underscores to join keys.
|
|
70
|
+
'dot': Use dots to join keys.
|
|
71
|
+
inverse : bool
|
|
72
|
+
Whether you want invert the resulting key and value.
|
|
73
|
+
max_flatten_depth : int
|
|
74
|
+
Maximum depth to merge.
|
|
75
|
+
enumerate_types : Sequence[type]
|
|
76
|
+
Flatten these types using `enumerate`.
|
|
77
|
+
For example, if we set `enumerate_types` to ``(list,)``,
|
|
78
|
+
`list` indices become keys: ``{'a': ['b', 'c']}`` -> ``{('a', 0): 'b', ('a', 1): 'c'}``.
|
|
79
|
+
keep_empty_types : Sequence[type]
|
|
80
|
+
By default, ``flatten({1: 2, 3: {}})`` will give you ``{(1,): 2}``, that is, the key ``3``
|
|
81
|
+
will disappear.
|
|
82
|
+
This is also applied for the types in `enumerate_types`, that is,
|
|
83
|
+
``flatten({1: 2, 3: []}, enumerate_types=(list,))`` will give you ``{(1,): 2}``.
|
|
84
|
+
If you want to keep those empty values, you can specify the types in `keep_empty_types`:
|
|
85
|
+
|
|
86
|
+
>>> flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
|
|
87
|
+
{(1,): 2, (3,): {}}
|
|
88
|
+
|
|
89
|
+
Returns
|
|
90
|
+
-------
|
|
91
|
+
flat_dict : dict
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
Examples
|
|
95
|
+
::::::::
|
|
96
|
+
|
|
97
|
+
>>> from flatten_dict import flatten
|
|
98
|
+
>>> from pprint import pprint
|
|
99
|
+
>>> normal_dict = {
|
|
100
|
+
... 'a': '0',
|
|
101
|
+
... 'b': {
|
|
102
|
+
... 'a': '1.0',
|
|
103
|
+
... 'b': '1.1',
|
|
104
|
+
... },
|
|
105
|
+
... 'c': {
|
|
106
|
+
... 'a': '2.0',
|
|
107
|
+
... 'b': {
|
|
108
|
+
... 'a': '2.1.0',
|
|
109
|
+
... 'b': '2.1.1',
|
|
110
|
+
... },
|
|
111
|
+
... },
|
|
112
|
+
... }
|
|
113
|
+
>>> pprint(flatten(normal_dict))
|
|
114
|
+
{('a',): '0',
|
|
115
|
+
('b', 'a'): '1.0',
|
|
116
|
+
('b', 'b'): '1.1',
|
|
117
|
+
('c', 'a'): '2.0',
|
|
118
|
+
('c', 'b', 'a'): '2.1.0',
|
|
119
|
+
('c', 'b', 'b'): '2.1.1'}
|
|
120
|
+
>>> pprint(flatten(normal_dict, reducer='path'))
|
|
121
|
+
{'a': '0',
|
|
122
|
+
'b/a': '1.0',
|
|
123
|
+
'b/b': '1.1',
|
|
124
|
+
'c/a': '2.0',
|
|
125
|
+
'c/b/a': '2.1.0',
|
|
126
|
+
'c/b/b': '2.1.1'}
|
|
127
|
+
>>> pprint(flatten(normal_dict, reducer='path', inverse=True))
|
|
128
|
+
{'0': 'a',
|
|
129
|
+
'1.0': 'b/a',
|
|
130
|
+
'1.1': 'b/b',
|
|
131
|
+
'2.0': 'c/a',
|
|
132
|
+
'2.1.0': 'c/b/a',
|
|
133
|
+
'2.1.1': 'c/b/b'}
|
|
134
|
+
>>> pprint(flatten(normal_dict, reducer='path', max_flatten_depth=2))
|
|
135
|
+
{'a': '0',
|
|
136
|
+
'b/a': '1.0',
|
|
137
|
+
'b/b': '1.1',
|
|
138
|
+
'c/a': '2.0',
|
|
139
|
+
'c/b': {'a': '2.1.0', 'b': '2.1.1'}}
|
|
140
|
+
|
|
141
|
+
The `reducer` parameter supports ``'tuple'``, ``'path'``, ``'underscore'``, ``'dot'`` and `Callable`. We can customize the reducer using a function:
|
|
142
|
+
|
|
143
|
+
>>> def underscore_reducer(k1, k2):
|
|
144
|
+
... if k1 is None:
|
|
145
|
+
... return k2
|
|
146
|
+
... else:
|
|
147
|
+
... return k1 + "_" + k2
|
|
148
|
+
...
|
|
149
|
+
>>> pprint(flatten(normal_dict, reducer=underscore_reducer))
|
|
150
|
+
{'a': '0',
|
|
151
|
+
'b_a': '1.0',
|
|
152
|
+
'b_b': '1.1',
|
|
153
|
+
'c_a': '2.0',
|
|
154
|
+
'c_b_a': '2.1.0',
|
|
155
|
+
'c_b_b': '2.1.1'}
|
|
156
|
+
|
|
157
|
+
There is also a factory function `make_reducer()` to help you create customized reducer. The function currently only supports customized delimiter:
|
|
158
|
+
|
|
159
|
+
>>> from flatten_dict.reducers import make_reducer
|
|
160
|
+
>>> pprint(flatten(normal_dict, reducer=make_reducer(delimiter='_')))
|
|
161
|
+
{'a': '0',
|
|
162
|
+
'b_a': '1.0',
|
|
163
|
+
'b_b': '1.1',
|
|
164
|
+
'c_a': '2.0',
|
|
165
|
+
'c_b_a': '2.1.0',
|
|
166
|
+
'c_b_b': '2.1.1'}
|
|
167
|
+
|
|
168
|
+
If we have some iterable (e.g., `list`) in the `dict`, we will normally get this:
|
|
169
|
+
|
|
170
|
+
>>> flatten({'a': [1, 2, 3], 'b': 'c'})
|
|
171
|
+
{('a',): [1, 2, 3], ('b',): 'c'}
|
|
172
|
+
|
|
173
|
+
If we want to use its indices as keys, then we can use the parameter `enumerate_types`:
|
|
174
|
+
|
|
175
|
+
>>> flatten({'a': [1, 2, 3], 'b': 'c'}, enumerate_types=(list,))
|
|
176
|
+
{('a', 0): 1, ('a', 1): 2, ('a', 2): 3, ('b',): 'c'}
|
|
177
|
+
|
|
178
|
+
We can even flatten a `list` directly:
|
|
179
|
+
|
|
180
|
+
>>> flatten([1, 2, 3], enumerate_types=(list,))
|
|
181
|
+
{(0,): 1, (1,): 2, (2,): 3}
|
|
182
|
+
|
|
183
|
+
If there is an empty dict in the values, by default, it will disappear after flattened:
|
|
184
|
+
|
|
185
|
+
>>> flatten({1: 2, 3: {}})
|
|
186
|
+
{(1,): 2}
|
|
187
|
+
|
|
188
|
+
We can keep the empty dict in the result using ``keep_empty_types=(dict,)``:
|
|
189
|
+
|
|
190
|
+
>>> flatten({1: 2, 3: {}}, keep_empty_types=(dict,))
|
|
191
|
+
{(1,): 2, (3,): {}}
|
|
192
|
+
|
|
193
|
+
Unflatten
|
|
194
|
+
`````````
|
|
195
|
+
|
|
196
|
+
.. code-block:: python
|
|
197
|
+
|
|
198
|
+
def unflatten(d, splitter='tuple', inverse=False):
|
|
199
|
+
"""Unflatten dict-like object.
|
|
200
|
+
|
|
201
|
+
Parameters
|
|
202
|
+
----------
|
|
203
|
+
d : dict-like object
|
|
204
|
+
The dict that will be unflattened.
|
|
205
|
+
splitter : {'tuple', 'path', 'underscore', 'dot', Callable}
|
|
206
|
+
The key splitting method. If a Callable is given, the Callable will be
|
|
207
|
+
used to split `d`.
|
|
208
|
+
'tuple': Use each element in the tuple key as the key of the unflattened dict.
|
|
209
|
+
'path': Use `pathlib.Path.parts` to split keys.
|
|
210
|
+
'underscore': Use underscores to split keys.
|
|
211
|
+
'dot': Use dots to split keys.
|
|
212
|
+
inverse : bool
|
|
213
|
+
Whether you want to invert the key and value before flattening.
|
|
214
|
+
|
|
215
|
+
Returns
|
|
216
|
+
-------
|
|
217
|
+
unflattened_dict : dict
|
|
218
|
+
"""
|
|
219
|
+
|
|
220
|
+
Examples
|
|
221
|
+
::::::::
|
|
222
|
+
|
|
223
|
+
>>> from pprint import pprint
|
|
224
|
+
>>> from flatten_dict import unflatten
|
|
225
|
+
>>> flat_dict = {
|
|
226
|
+
... ('a',): '0',
|
|
227
|
+
... ('b', 'a'): '1.0',
|
|
228
|
+
... ('b', 'b'): '1.1',
|
|
229
|
+
... ('c', 'a'): '2.0',
|
|
230
|
+
... ('c', 'b', 'a'): '2.1.0',
|
|
231
|
+
... ('c', 'b', 'b'): '2.1.1',
|
|
232
|
+
... }
|
|
233
|
+
>>> pprint(unflatten(flat_dict))
|
|
234
|
+
{'a': '0',
|
|
235
|
+
'b': {'a': '1.0', 'b': '1.1'},
|
|
236
|
+
'c': {'a': '2.0', 'b': {'a': '2.1.0', 'b': '2.1.1'}}}
|
|
237
|
+
>>> flat_dict = {
|
|
238
|
+
... 'a': '0',
|
|
239
|
+
... 'b/a': '1.0',
|
|
240
|
+
... 'b/b': '1.1',
|
|
241
|
+
... 'c/a': '2.0',
|
|
242
|
+
... 'c/b/a': '2.1.0',
|
|
243
|
+
... 'c/b/b': '2.1.1',
|
|
244
|
+
... }
|
|
245
|
+
>>> pprint(unflatten(flat_dict, splitter='path'))
|
|
246
|
+
{'a': '0',
|
|
247
|
+
'b': {'a': '1.0', 'b': '1.1'},
|
|
248
|
+
'c': {'a': '2.0', 'b': {'a': '2.1.0', 'b': '2.1.1'}}}
|
|
249
|
+
>>> flat_dict = {
|
|
250
|
+
... '0': 'a',
|
|
251
|
+
... '1.0': 'b/a',
|
|
252
|
+
... '1.1': 'b/b',
|
|
253
|
+
... '2.0': 'c/a',
|
|
254
|
+
... '2.1.0': 'c/b/a',
|
|
255
|
+
... '2.1.1': 'c/b/b',
|
|
256
|
+
... }
|
|
257
|
+
>>> pprint(unflatten(flat_dict, splitter='path', inverse=True))
|
|
258
|
+
{'a': '0',
|
|
259
|
+
'b': {'a': '1.0', 'b': '1.1'},
|
|
260
|
+
'c': {'a': '2.0', 'b': {'a': '2.1.0', 'b': '2.1.1'}}}
|
|
261
|
+
|
|
262
|
+
The `splitter` parameter supports ``'tuple'``, ``'path'``, ``'underscore'``, ``'dot'`` and `Callable`. We can customize the reducer using a function:
|
|
263
|
+
|
|
264
|
+
>>> def underscore_splitter(flat_key):
|
|
265
|
+
... return flat_key.split("_")
|
|
266
|
+
...
|
|
267
|
+
>>> flat_dict = {
|
|
268
|
+
... 'a': '0',
|
|
269
|
+
... 'b_a': '1.0',
|
|
270
|
+
... 'b_b': '1.1',
|
|
271
|
+
... 'c_a': '2.0',
|
|
272
|
+
... 'c_b_a': '2.1.0',
|
|
273
|
+
... 'c_b_b': '2.1.1',
|
|
274
|
+
... }
|
|
275
|
+
>>> pprint(unflatten(flat_dict, splitter=underscore_splitter))
|
|
276
|
+
{'a': '0',
|
|
277
|
+
'b': {'a': '1.0', 'b': '1.1'},
|
|
278
|
+
'c': {'a': '2.0', 'b': {'a': '2.1.0', 'b': '2.1.1'}}}
|
|
279
|
+
|
|
280
|
+
There is also a factory function `make_splitter()` to help you create customized splitter. The function currently only supports customized delimiter:
|
|
281
|
+
|
|
282
|
+
>>> from flatten_dict.splitters import make_splitter
|
|
283
|
+
>>> pprint(unflatten(flat_dict, splitter=make_splitter(delimiter='_')))
|
|
284
|
+
{'a': '0',
|
|
285
|
+
'b': {'a': '1.0', 'b': '1.1'},
|
|
286
|
+
'c': {'a': '2.0', 'b': {'a': '2.1.0', 'b': '2.1.1'}}}
|
|
287
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
flatten_dict/__init__.py,sha256=f38pB1Z4Q7s5v0jqIq6hVAQIOMbDnADIbd2_WEDW7jw,186
|
|
2
|
+
flatten_dict/flatten_dict.py,sha256=IMGHq2bE1RWTZPqBzIVskOy8PpKUB7jAg20wh7APG30,5513
|
|
3
|
+
flatten_dict/reducer.py,sha256=irHqGQlgcr807TPE_-DsY9286TsDDQ_RKJL4BfwRpI8,278
|
|
4
|
+
flatten_dict/reducers.py,sha256=6_XtTqv9xkFamDmpmQA48gesJFcuBOyAjTOJgCxf3kQ,831
|
|
5
|
+
flatten_dict/splitter.py,sha256=PlEcWhu4eAQIDYoffSHznx3UTdlrV8hHQ9qT5RQnAjA,283
|
|
6
|
+
flatten_dict/splitters.py,sha256=Dt3ufj00m9iV7S7ptEYk76b1SLwvglnxEKHZkzCHW4c,746
|
|
7
|
+
flatten_dict/tests/flatten_dict_test.py,sha256=9KzgRh7c0MZUGvQaYnunNm-PBSKRk7N0A2662nc2iP4,12845
|
|
8
|
+
flatten_dict-0.5.0.dist-info/METADATA,sha256=_VxmnEThCFv8rnHyG6wlbOkfCX77YYa_7tF6kpJthiU,8926
|
|
9
|
+
flatten_dict-0.5.0.dist-info/WHEEL,sha256=Vz2fHgx6HFtSwhs8KvkHLqH5Ea4w1_rner5uNVGCeIE,88
|
|
10
|
+
flatten_dict-0.5.0.dist-info/licenses/LICENSE,sha256=2mzVYhXROtXqbir2WKWhyRQlE9f2JEAeFnkpyqxc_-c,1064
|
|
11
|
+
flatten_dict-0.5.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Ian Lin
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|