Passed
Push — master ( 56190a...2d7d04 )
by Fabio
01:26
created

benedict.utils.dict_util   F

Complexity

Total Complexity 80

Size/Duplication

Total Lines 243
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 80
eloc 194
dl 0
loc 243
rs 2
c 0
b 0
f 0

21 Functions

Rating   Name   Duplication   Size   Complexity  
C clean() 0 10 10
A clone() 0 2 1
A dump() 0 6 2
A filter() 0 11 4
A keypaths() 0 14 5
A rename() 0 2 1
B resolve() 0 23 5
A remove() 0 6 3
A standardize() 0 9 2
A unflatten() 0 14 4
A flatten() 0 16 4
A move() 0 6 4
A invert() 0 10 4
A traverse() 0 9 4
A swap() 0 4 2
C search() 0 18 10
A merge() 0 10 5
A subset() 0 9 3
A items_sorted_by_values() 0 2 2
A unique() 0 9 3
A items_sorted_by_keys() 0 2 2

How to fix   Complexity   

Complexity

Complex classes like benedict.utils.dict_util often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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