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

benedict.dicts.keypath.KeypathDict._list_keys()   B

Complexity

Conditions 6

Size

Total Lines 14
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 14
rs 8.6666
c 0
b 0
f 0
cc 6
nop 2
1
# -*- coding: utf-8 -*-
2
3
from benedict.utils import dict_util
4
from six import string_types
5
6
7
class KeypathDict(dict):
8
9
    def __init__(self, *args, **kwargs):
10
        self._keypath_separator = kwargs.pop('keypath_separator', '.')
11
        super(KeypathDict, self).__init__(*args, **kwargs)
12
        self._check_keypath_separator_in_keys(self)
13
14
    @property
15
    def keypath_separator(self):
16
        return self._keypath_separator
17
18
    @keypath_separator.setter
19
    def keypath_separator(self, value):
20
        self._keypath_separator = value
21
        self._check_keypath_separator_in_keys(self)
22
23
    def _check_keypath_separator_in_keys(self, d):
24
        sep = self._keypath_separator
25
        if not isinstance(d, dict) or not sep:
26
            return
27
28
        def check_key(parent, key, value):
29
            if key and isinstance(key, string_types) and sep in key:
30
                raise ValueError(
31
                    'keys should not contain keypath separator '
32
                    '\'{}\', found: \'{}\'.'.format(sep, key))
33
        dict_util.traverse(d, check_key)
34
35
    def _goto_keys(self, keys):
36
        result = (None, None, None, )
37
        parent = self
38
        i = 0
39
        j = len(keys)
40
        while i < j:
41
            key = keys[i]
42
            try:
43
                value = parent[key]
44
                result = (parent, key, value, )
45
                parent = value
46
                i += 1
47
            except (KeyError, TypeError, ) as e:
48
                result = (None, None, None, )
49
                break
50
        return result
51
52
    def _list_keys(self, key):
53
        if isinstance(key, string_types):
54
            sep = self._keypath_separator
55
            if sep and sep in key:
56
                return list(key.split(sep))
57
            else:
58
                return [key]
59
        elif isinstance(key, (list, tuple, )):
60
            keys = []
61
            for key_item in key:
62
                keys += self._list_keys(key_item)
63
            return keys
64
        else:
65
            return [key]
66
67
    def __contains__(self, key):
68
        keys = self._list_keys(key)
69
        if len(keys) > 1:
70
            parent, key, value = self._goto_keys(keys)
71
            if isinstance(parent, dict) and parent.__contains__(key):
72
                return True
73
            else:
74
                return False
75
        else:
76
            return super(KeypathDict, self).__contains__(key)
77
78
    def __delitem__(self, key):
79
        keys = self._list_keys(key)
80
        if len(keys) > 1:
81
            parent, key, value = self._goto_keys(keys)
82
            if isinstance(parent, dict):
83
                parent.__delitem__(key)
84
            else:
85
                raise KeyError
86
        else:
87
            super(KeypathDict, self).__delitem__(key)
88
89
    def __getitem__(self, key):
90
        keys = self._list_keys(key)
91
        value = None
92
        if len(keys) > 1:
93
            parent, key, value = self._goto_keys(keys)
94
            if isinstance(parent, dict):
95
                return parent.__getitem__(key)
96
            else:
97
                raise KeyError
98
        else:
99
            value = super(KeypathDict, self).__getitem__(key)
100
        return value
101
102
    def __setitem__(self, key, value):
103
        self._check_keypath_separator_in_keys(value)
104
        keys = self._list_keys(key)
105
        if len(keys) > 1:
106
            i = 0
107
            j = len(keys)
108
            item = self
109
            while i < j:
110
                key = keys[i]
111
                if i < (j - 1):
112
                    if item is self:
113
                        subitem = super(KeypathDict, self).get(key, None)
114
                    else:
115
                        subitem = item.get(key, None)
116
                    if not isinstance(subitem, dict):
117
                        subitem = item[key] = {}
118
                    item = subitem
119
                else:
120
                    item[key] = value
121
                i += 1
122
        else:
123
            super(KeypathDict, self).__setitem__(key, value)
124
125
    @classmethod
126
    def fromkeys(cls, sequence, value=None):
127
        d = KeypathDict()
128
        for key in sequence:
129
            d[key] = value
130
        return d
131
132
    def get(self, key, default=None):
133
        keys = self._list_keys(key)
134
        if len(keys) > 1:
135
            parent, key, value = self._goto_keys(keys)
136
            if isinstance(parent, dict):
137
                return parent.get(key, default)
138
            else:
139
                return default
140
        else:
141
            return super(KeypathDict, self).get(key, default)
142
143
    def pop(self, key, *args, **kwargs):
144
        if kwargs and 'default' in kwargs:
145
            default_arg = True
146
            default = kwargs.get('default', None)
147
        elif args:
148
            default_arg = True
149
            default = args[0]
150
        else:
151
            default_arg = False
152
            default = None
153
        keys = self._list_keys(key)
154
        if len(keys) > 1:
155
            parent, key, value = self._goto_keys(keys)
156
            if isinstance(parent, dict):
157
                if default_arg:
158
                    return parent.pop(key, default)
159
                else:
160
                    return parent.pop(key)
161
            else:
162
                if default_arg:
163
                    return default
164
                else:
165
                    raise KeyError
166
        else:
167
            if default_arg:
168
                return super(KeypathDict, self).pop(key, default)
169
            else:
170
                return super(KeypathDict, self).pop(key)
171
172
    def set(self, key, value):
173
        self.__setitem__(key, value)
174
175
    def setdefault(self, key, default=None):
176
        if key not in self:
177
            self.__setitem__(key, default)
178
            return default
179
        else:
180
            return self.__getitem__(key)
181
182
    def update(self, other):
183
        self._check_keypath_separator_in_keys(other)
184
        super(KeypathDict, self).update(other)
185