Passed
Push — master ( 28b03c...e6e2ec )
by Fabio
01:17
created

benedict.utils.dict_util.clean()   A

Complexity

Conditions 4

Size

Total Lines 11
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 11
dl 0
loc 11
rs 9.85
c 0
b 0
f 0
cc 4
nop 4
1
# -*- coding: utf-8 -*-
2
3
from six import string_types, text_type
4
from slugify import slugify
5
6
import copy
7
import json
8
import re
9
10
11
def clean(d, strings=True, dicts=True, lists=True):
12
    keys = list(d.keys())
13
    for key in keys:
14
        value = d.get(key, None)
15
        if not value:
16
            del_none = value is None
17
            del_string = strings and isinstance(value, string_types)
18
            del_dict = dicts and isinstance(value, dict)
19
            del_list = lists and isinstance(value, (list, set, tuple, ))
20
            if any([del_none, del_string, del_dict, del_list]):
21
                del d[key]
22
23
24
def clone(d):
25
    return copy.deepcopy(d)
26
27
28
def dump(data):
29
    def encoder(obj):
30
        json_types = (bool, dict, float, int, list, tuple, ) + string_types
31
        if not isinstance(obj, json_types):
32
            return str(obj)
33
    return json.dumps(data, indent=4, sort_keys=True, default=encoder)
34
35
36
def filter(d, predicate):
37
    if not callable(predicate):
38
        raise ValueError('predicate argument must be a callable.')
39
    new_dict = d.copy()
40
    new_dict.clear()
41
    keys = list(d.keys())
42
    for key in keys:
43
        value = d.get(key, None)
44
        if predicate(key, value):
45
            new_dict[key] = value
46
    return new_dict
47
48
49
def flatten(d, separator='_', **kwargs):
50
    new_dict = d.copy()
51
    new_dict.clear()
52
    keys = list(d.keys())
53
    base_key = kwargs.pop('base_key', '')
54
    for key in keys:
55
        new_key = '{}{}{}'.format(
56
            base_key, separator, key) if base_key and separator else key
57
        value = d.get(key, None)
58
        if isinstance(value, dict):
59
            new_value = flatten(value, separator=separator, base_key=new_key)
60
            new_value.update(new_dict)
61
            new_dict.update(new_value)
62
        else:
63
            new_dict[new_key] = value
64
    return new_dict
65
66
67
def invert(d, flat=False):
68
    new_dict = d.copy()
69
    new_dict.clear()
70
    if flat:
71
        for key, value in d.items():
72
            new_dict.setdefault(value, key)
73
    else:
74
        for key, value in d.items():
75
            new_dict.setdefault(value, []).append(key)
76
    return new_dict
77
78
79
def items_sorted_by_keys(d, reverse=False):
80
    return sorted(d.items(), key=lambda item: item[0], reverse=reverse)
81
82
83
def items_sorted_by_values(d, reverse=False):
84
    return sorted(d.items(), key=lambda item: item[1], reverse=reverse)
85
86
87
def keypaths(d, separator='.'):
88
    if not separator or not isinstance(separator, string_types):
89
        raise ValueError('separator argument must be a (non-empty) string.')
90
91
    def f(parent, parent_keys):
92
        kp = []
93
        for key, value in parent.items():
94
            keys = parent_keys + [key]
95
            kp += [separator.join(text_type(k) for k in keys)]
96
            if isinstance(value, dict):
97
                kp += f(value, keys)
98
        return kp
99
    kp = f(d, [])
100
    kp.sort()
101
    return kp
102
103
104
def merge(d, other, *args):
105
    others = [other] + list(args)
106
    for other in others:
107
        for key, value in other.items():
108
            src = d.get(key, None)
109
            if isinstance(src, dict) and isinstance(value, dict):
110
                merge(src, value)
111
            else:
112
                d[key] = value
113
    return d
114
115
116
def move(d, key_src, key_dest, overwrite=True):
117
    if key_dest == key_src:
118
        return
119
    if key_dest in d and not overwrite:
120
        raise KeyError
121
    d[key_dest] = d.pop(key_src)
122
123
124
def remove(d, keys, *args):
125
    if isinstance(keys, string_types):
126
        keys = [keys]
127
    keys += args
128
    for key in keys:
129
        d.pop(key, None)
130
131
132
def rename(d, key, key_new):
133
    move(d, key, key_new, overwrite=False)
134
135
136
def resolve(d, keys, **kwargs):
137
    create_intermediates = kwargs.pop('create_intermediates', False)
138
    result = (None, None, None, )
139
    parent = d
140
    i = 0
141
    j = len(keys)
142
    while i < j:
143
        key = keys[i]
144
        try:
145
            value = parent[key]
146
            result = (parent, key, value, )
147
            parent = value
148
            i += 1
149
        except (KeyError, ):
150
            if create_intermediates:
151
                parent[key] = {}
152
                continue
153
            result = (None, None, None, )
154
            break
155
        except (TypeError, ValueError, ):
156
            result = (None, None, None, )
157
            break
158
    return result
159
160
161
def search(d, query,
162
           in_keys=True, in_values=True, exact=False, case_sensitive=True):
163
    items = []
164
165
    def get_term(value):
166
        v_is_str = isinstance(value, string_types)
167
        v = value.lower() if (v_is_str and not case_sensitive) else value
168
        return (v, v_is_str, )
169
170
    q, q_is_str = get_term(query)
171
172
    def get_match(cond, value):
173
        if not cond:
174
            return False
175
        v, v_is_str = get_term(value)
176
        # TODO: add regex support
177
        if exact:
178
            return q == v
179
        elif q_is_str and v_is_str:
180
            return q in v
181
        return False
182
183
    def f(item_dict, item_key, item_value):
184
        if get_match(in_keys, item_key) or get_match(in_values, item_value):
185
            items.append((item_dict, item_key, item_value, ))
186
    traverse(d, f)
187
    return items
188
189
190
def standardize(d):
191
    def f(item_dict, item_key, item_value):
192
        if isinstance(item_key, string_types):
193
            # https://stackoverflow.com/a/12867228/2096218
194
            norm_key = re.sub(
195
                r'((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))', r'_\1', item_key)
196
            norm_key = slugify(norm_key, separator='_')
197
            move(item_dict, item_key, norm_key)
198
    traverse(d, f)
199
200
201
def subset(d, keys, *args):
202
    new_dict = d.copy()
203
    new_dict.clear()
204
    if isinstance(keys, string_types):
205
        keys = [keys]
206
    keys += args
207
    for key in keys:
208
        new_dict[key] = d.get(key, None)
209
    return new_dict
210
211
212
def swap(d, key1, key2):
213
    if key1 == key2:
214
        return
215
    d[key1], d[key2] = d[key2], d[key1]
216
217
218
def traverse(d, callback):
219
    if not callable(callback):
220
        raise ValueError('callback argument must be a callable.')
221
    keys = list(d.keys())
222
    for key in keys:
223
        value = d.get(key, None)
224
        callback(d, key, value)
225
        if isinstance(value, dict):
226
            traverse(value, callback)
227
228
229
def unflatten(d, separator='_'):
230
    new_dict = d.copy()
231
    new_dict.clear()
232
    new_dict_cursor = new_dict
233
    keys = list(d.keys())
234
    for key in keys:
235
        value = d.get(key, None)
236
        new_value = unflatten(value, separator=separator) if isinstance(
237
            value, dict) else value
238
        new_keys = key.split(separator) if separator in key else [key]
239
        new_parent, new_key, _ = resolve(
240
            new_dict, new_keys, create_intermediates=True)
241
        new_parent[new_key] = new_value
242
    return new_dict
243
244
245
def unique(d):
246
    values = []
247
    keys = list(d.keys())
248
    for key in keys:
249
        value = d.get(key, None)
250
        if value in values:
251
            d.pop(key, None)
252
            continue
253
        values.append(value)
254