Completed
Push — master ( eb677f...0fa067 )
by Fabio
04:22
created

benedict.utils.dict_util.keypaths()   A

Complexity

Conditions 5

Size

Total Lines 14
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 14
rs 9.2333
c 0
b 0
f 0
cc 5
nop 2
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.__class__()
39
    keys = list(d.keys())
40
    for key in keys:
41
        value = d.get(key, None)
42
        if predicate(key, value):
43
            new_dict[key] = value
44
    return new_dict
45
46
47
def flatten(d, separator='_', base=''):
48
    new_dict = d.__class__()
49
    keys = list(d.keys())
50
    for key in keys:
51
        keypath = '{}{}{}'.format(
52
            base, separator, key) if base and separator else key
53
        value = d.get(key, None)
54
        if isinstance(value, dict):
55
            new_value = flatten(value, separator=separator, base=keypath)
56
            new_value.update(new_dict)
57
            new_dict.update(new_value)
58
        else:
59
            new_dict[keypath] = value
60
    return new_dict
61
62
63
def invert(d, flat=False):
64
    new_dict = d.__class__()
65
    if flat:
66
        for key, value in d.items():
67
            new_dict.setdefault(value, key)
68
    else:
69
        for key, value in d.items():
70
            new_dict.setdefault(value, []).append(key)
71
    return new_dict
72
73
74
def items_sorted_by_keys(d, reverse=False):
75
    return sorted(d.items(), key=lambda item: item[0], reverse=reverse)
76
77
78
def items_sorted_by_values(d, reverse=False):
79
    return sorted(d.items(), key=lambda item: item[1], reverse=reverse)
80
81
82
def keypaths(d, separator='.'):
83
    if not separator or not isinstance(separator, string_types):
84
        raise ValueError('separator argument must be a (non-empty) string.')
85
    def f(parent, parent_keys):
86
        kp = []
87
        for key, value in parent.items():
88
            keys = parent_keys + [key]
89
            kp += [separator.join(text_type(k) for k in keys)]
90
            if isinstance(value, dict):
91
                kp += f(value, keys)
92
        return kp
93
    kp = f(d, [])
94
    kp.sort()
95
    return kp
96
97
98
def merge(d, other, *args):
99
    others = [other] + list(args)
100
    for other in others:
101
        for key, value in other.items():
102
            src = d.get(key, None)
103
            if isinstance(src, dict) and isinstance(value, dict):
104
                merge(src, value)
105
            else:
106
                d[key] = value
107
    return d
108
109
110
def move(d, key_src, key_dest):
111
    if key_dest == key_src:
112
        return
113
    d[key_dest] = d.pop(key_src)
114
115
116
def remove(d, keys, *args):
117
    if isinstance(keys, string_types):
118
        keys = [keys]
119
    keys += args
120
    for key in keys:
121
        d.pop(key, None)
122
123
124
def standardize(d):
125
    def f(parent, key, value):
126
        if isinstance(key, string_types):
127
            # https://stackoverflow.com/a/12867228/2096218
128
            norm_key = re.sub(
129
                r'((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))', r'_\1', key)
130
            norm_key = slugify(norm_key, separator='_')
131
            move(parent, key, norm_key)
132
    traverse(d, f)
133
134
135
def subset(d, keys, *args):
136
    new_dict = d.__class__()
137
    if isinstance(keys, string_types):
138
        keys = [keys]
139
    keys += args
140
    for key in keys:
141
        new_dict[key] = d.get(key, None)
142
    return new_dict
143
144
145
def swap(d, key1, key2):
146
    if key1 == key2:
147
        return
148
    d[key1], d[key2] = d[key2], d[key1]
149
150
151
def traverse(d, callback):
152
    if not callable(callback):
153
        raise ValueError('callback argument must be a callable.')
154
    keys = list(d.keys())
155
    for key in keys:
156
        value = d.get(key, None)
157
        callback(d, key, value)
158
        if isinstance(value, dict):
159
            traverse(value, callback)
160
161
162
def unique(d):
163
    values = []
164
    keys = list(d.keys())
165
    for key in keys:
166
        value = d.get(key, None)
167
        if value in values:
168
            d.pop(key, None)
169
            continue
170
        values.append(value)
171