Passed
Push — master ( f8fde5...8bd6a5 )
by sosei
01:12
created

multivalued_dict   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 245
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 92
dl 0
loc 245
rs 9.1199
c 0
b 0
f 0
wmc 41

16 Methods

Rating   Name   Duplication   Size   Complexity  
A KeyValuePairsError.__init__() 0 2 1
A KeyValuePairsError.__repr__() 0 2 1
A multivalued_dict.__is_multivalued_dict__() 0 8 3
B multivalued_dict.__init__() 0 27 6
A multivalued_dict.items() 0 5 1
A multivalued_dict.__lenvalue__() 0 12 2
A multivalued_dict.count() 0 7 1
A multivalued_dict.values() 0 5 1
A multivalued_dict.copy() 0 5 1
B multivalued_dict.__delkv__() 0 33 7
D multivalued_dict.update() 0 49 12
A multivalued_dict.fromkeys() 0 10 1
A multivalued_dict.__matchkv__() 0 11 1
A multivalued_dict.keys() 0 5 1
A multivalued_dict.reverse() 0 5 1
A multivalued_dict.__repr__() 0 2 1

How to fix   Complexity   

Complexity

Complex classes like multivalued_dict 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
from collections import defaultdict
2
from collections import UserDict
3
from collections.abc import Iterable
4
5
START_POS = 'S'
6
END_POS = 'E'
7
8
class MultivaluedDictError(Exception):
9
    pass
10
    
11
class KeyValuePairsError(MultivaluedDictError):
12
    def __init__(self, list_of_not_kv_pair):
13
        self.list_of_not_kv_pair = list_of_not_kv_pair
14
        
15
    def __repr__(self):
16
        return f'{list_of_not_kv_pair} does not form a key-value pair. '
17
    
18
class multivalued_dict(UserDict):
19
    '''
20
        >>> mv_d = multivalued_dict()
21
        >>> mv_d
22
        multivalued_dict({})
23
        
24
        >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
25
        >>> mv_d
26
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
27
        
28
        >>> mv_d = multivalued_dict([['a', 'test-1'], ['b', 'test-2'], ['a', 'test-3']])
29
        >>> mv_d
30
        multivalued_dict({'a': ['test-1', 'test-3'], 'b': ['test-2']})
31
        
32
        >>> mv_d = multivalued_dict(a = 'test-1', b = 'test-2', c = 'test-3')
33
        >>> mv_d
34
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
35
        
36
        >>> mv_d = multivalued_dict([['a', 'test-1'], ['c', 'test-3']], b = 'test-2')
37
        >>> mv_d
38
        multivalued_dict({'a': ['test-1'], 'c': ['test-3'], 'b': ['test-2']})
39
        
40
        >>> mv_d0 = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
41
        >>> mv_d0
42
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
43
        >>> mv_d = multivalued_dict(mv_d0)
44
        >>> mv_d
45
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
46
    '''
47
48
    @classmethod
49
    def __is_multivalued_dict__(cls, x):
50
        '''
51
        >>> mv_d = multivalued_dict()
52
        >>> multivalued_dict.__is_multivalued_dict__(mv_d)
53
        True
54
        '''
55
        return (isinstance(x, cls) or ((True if x.default_factory == type([]) else False) if isinstance(x, defaultdict) else False))
56
    
57
    def __init__(self, *args, **kwargs):
58
        '''
59
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
60
            >>> mv_d
61
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
62
            
63
            >>> mv_d.__init__({'d': 'test-4'})
64
            >>> mv_d
65
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4']})
66
            
67
            >>> multivalued_dict.__init__(mv_d, {'e': 'test-5'})
68
            >>> mv_d
69
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4'], 'e': ['test-5']})
70
        '''
71
        if not multivalued_dict.__is_multivalued_dict__(self):
72
            raise TypeError(f"descriptor '__init__' requires a 'multivalued_dict' object but received a {type(self)}")
73
        len_of_args = len(args)
74
        if len_of_args > 1:
75
            raise TypeError(f'expected at most 1 arguments, got {len_of_args}')
76
        else:
77
            if 'data' not in self.__dict__:
78
                self.data = defaultdict(list)
79
            if len_of_args == 1:
80
                initial_items = args[0]
81
                self.update(initial_items)
82
        if kwargs != dict():
83
            self.update(kwargs)
84
    
85
    def __repr__(self):
86
        return f'multivalued_dict({dict(self.data)})'
87
    
88
    def __lenvalue__(self, key = None):
89
        '''
90
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
91
            >>> mv_d.__lenvalue__()
92
            6
93
            >>> mv_d.__lenvalue__('a')
94
            3
95
        '''
96
        if key == None:
97
            return sum(map(len, self.data.values()))
98
        else:
99
            return len(self.data[key])
100
    
101
    def __matchkv__(self, key, value):
102
        '''
103
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
104
            >>> mv_d.__matchkv__('b', 3)
105
            False
106
            >>> mv_d.__matchkv__('a', 2)
107
            True
108
            >>> mv_d.__matchkv__('d', 1)
109
            False
110
        '''
111
        return value in self.data[key]
112
        
113
    def __delkv__(self, key, value, allkv = True, direction = START_POS):
114
        '''
115
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'z'], ['a', 'y'], ['a', 'z'], ['a', 'y']])
116
            >>> mv_d
117
            multivalued_dict({'a': ['x', 'y', 'z', 'y', 'z', 'y']})
118
            
119
            >>> mv_d.__delkv__('a', 'y', False)
120
            >>> mv_d
121
            multivalued_dict({'a': ['x', 'z', 'y', 'z', 'y']})
122
            
123
            >>> mv_d.__delkv__('a', 'y', False, END_POS)
124
            >>> mv_d
125
            multivalued_dict({'a': ['x', 'z', 'y', 'z']})
126
            
127
            >>> mv_d.__delkv__('a', 'z')
128
            >>> mv_d
129
            multivalued_dict({'a': ['x', 'y']})
130
        '''
131
        assert allkv in (True, False), '"allkv" can only be True or False'
132
        assert direction in (START_POS, END_POS), '"direction" can only be START_POS or END_POS'
133
        
134
        if allkv:
135
            while value in self.data[key]:
136
                self.data[key].remove(value)
137
        else:
138
            if direction == START_POS:
139
                self.data[key].remove(value)
140
            elif direction == END_POS:
141
                value_len = len(self.data[key])
142
                for i in range(value_len):
143
                    if self.data[key][-1 - i] == value:
144
                        self.data[key].__delitem__(-1 - i)
145
                        break
146
147
    def count(self, key, value):
148
        '''
149
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'y'], ['a', 'z'], ['a', 'z'], ['a', 'z']])
150
            >>> mv_d.count('a', 'y')
151
            2
152
        '''
153
        return self.data[key].count(value)
154
    
155
    def update(self, *args, **kwargs):
156
        '''
157
            >>> mv_d = multivalued_dict()
158
            >>> mv_d
159
            multivalued_dict({})
160
            
161
            >>> mv_d.update({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
162
            >>> mv_d
163
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
164
            
165
            >>> mv_d.update([['a', 'test-4'], ['a', 'test-5']])
166
            >>> mv_d
167
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3']})
168
            
169
            >>> mv_d.update(c = 'test-3')
170
            >>> mv_d
171
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3', 'test-3']})
172
            
173
            >>> mv_d.update([['b', 'test-6'], ['c', 'test-7']], a = 'test-8')
174
            >>> mv_d
175
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5', 'test-8'], 'b': ['test-2', 'test-6'], 'c': ['test-3', 'test-3', 'test-7']})
176
            
177
            >>> mv_d.update(multivalued_dict({'d': 'test-9', 'e': 'test-10'}))
178
            >>> mv_d
179
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5', 'test-8'], 'b': ['test-2', 'test-6'], 'c': ['test-3', 'test-3', 'test-7'], 'd': ['test-9'], 'e': ['test-10']})
180
        '''
181
        if not multivalued_dict.__is_multivalued_dict__(self):
182
            raise TypeError(f"descriptor 'update' requires a 'multivalued_dict' object but received a {type(self)}")
183
        len_of_args = len(args)
184
        if len_of_args > 1:
185
            raise TypeError(f'expected at most 1 arguments, got {len_of_args}')
186
        if len_of_args == 1:
187
            update_items = args[0]
188
            if not isinstance(update_items, Iterable):
189
                raise TypeError(f'{type(update_items)} object is not iterable ')
190
            if multivalued_dict.__is_multivalued_dict__(update_items):
191
                for _key, _value in update_items.items():
192
                    self.data[_key].extend(_value)
193
            elif isinstance(update_items, dict):
194
                for _key, _value in update_items.items():
195
                    self.data[_key].append(_value)
196
            else:
197
                for item in update_items:
198
                    if len(item) != 2:
199
                        raise KeyValuePairsError(item)
200
                    _key, _value = item
201
                    self.data[_key].append(_value)
202
        if kwargs != dict():
203
            self.update(kwargs)
204
    
205
    def reverse(self, key):
206
        '''
207
            
208
        '''
209
        self.data[key].reverse()
210
    
211
    def copy(self):
212
        '''
213
            
214
        '''
215
        return multivalued_dict(self.data)
216
    
217
    def items(self):
218
        '''
219
            
220
        '''
221
        return self.data.items()
222
    
223
    def keys(self):
224
        '''
225
            
226
        '''
227
        return self.data.keys()
228
    
229
    def values(self):
230
        '''
231
            
232
        '''
233
        return self.data.values()
234
    
235
    @classmethod
236
    def fromkeys(cls, iterable, value = None):
237
        '''
238
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'])
239
            multivalued_dict({'a': [None], 'b': [None], 'c': [None]})
240
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'], 'test')
241
            multivalued_dict({'a': ['test'], 'b': ['test'], 'c': ['test']})
242
        '''
243
        dict_var = dict.fromkeys(iterable, value)
244
        return cls(dict_var)
245