Passed
Push — master ( 9ac6d7...842787 )
by sosei
01:01
created

multivalued_dict.multivalued_dict.count()   A

Complexity

Conditions 1

Size

Total Lines 8
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 3
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
from collections import defaultdict
2
from collections import UserDict
3
from collections.abc import Iterable
4
import inspect
5
6
START_POS = 'S'
7
END_POS = 'E'
8
9
class MultivaluedDictError(Exception):
10
    pass
11
    
12
class KeyValuePairsError(MultivaluedDictError):
13
    def __init__(self, list_of_not_kv_pair):
14
        self.list_of_not_kv_pair = list_of_not_kv_pair
15
        
16
    def __repr__(self):
17
        return f'{list_of_not_kv_pair} does not form a key-value pair. '
18
    
19
class multivalued_dict(UserDict):
20
    '''
21
        >>> mv_d = multivalued_dict()
22
        >>> mv_d
23
        multivalued_dict({})
24
        
25
        >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
26
        >>> mv_d
27
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
28
        
29
        >>> mv_d = multivalued_dict([['a', 'test-1'], ['b', 'test-2'], ['a', 'test-3']])
30
        >>> mv_d
31
        multivalued_dict({'a': ['test-1', 'test-3'], 'b': ['test-2']})
32
        
33
        >>> mv_d = multivalued_dict(a = 'test-1', b = 'test-2', c = 'test-3')
34
        >>> mv_d
35
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
36
        
37
        >>> mv_d = multivalued_dict([['a', 'test-1'], ['c', 'test-3']], b = 'test-2')
38
        >>> mv_d
39
        multivalued_dict({'a': ['test-1'], 'c': ['test-3'], 'b': ['test-2']})
40
        
41
        >>> mv_d0 = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
42
        >>> mv_d0
43
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
44
        >>> mv_d = multivalued_dict(mv_d0)
45
        >>> mv_d
46
        multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
47
    '''
48
    
49
    __marker = object()
50
    
51
    @staticmethod
52
    def __is_self(v_self):
53
        if not multivalued_dict.__is_multivalued_dict__(v_self):
54
            raise TypeError(f"descriptor '{inspect.currentframe().f_back.f_code.co_name}' requires a 'multivalued_dict' object but received a '{v_self.__class__.__name__}'")
55
    
56
    @classmethod
57
    def __is_multivalued_dict__(cls, x):
58
        '''
59
        >>> mv_d = multivalued_dict()
60
        >>> multivalued_dict.__is_multivalued_dict__(mv_d)
61
        True
62
        '''
63
        return (isinstance(x, cls) or ((True if x.default_factory == type([]) else False) if isinstance(x, defaultdict) else False))
64
    
65
    @classmethod
66
    def fromkeys(cls, iterable, value = None):
67
        '''
68
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'])
69
            multivalued_dict({'a': [None], 'b': [None], 'c': [None]})
70
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'], 'test')
71
            multivalued_dict({'a': ['test'], 'b': ['test'], 'c': ['test']})
72
        '''
73
        dict_var = dict.fromkeys(iterable, value)
74
        return cls(dict_var)
75
    
76
    def __init__(self, *args, **kwargs):
77
        '''
78
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
79
            >>> mv_d
80
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
81
            
82
            >>> mv_d.__init__({'d': 'test-4'})
83
            >>> mv_d
84
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4']})
85
            
86
            >>> multivalued_dict.__init__(mv_d, {'e': 'test-5'})
87
            >>> mv_d
88
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4'], 'e': ['test-5']})
89
        '''
90
        multivalued_dict.__is_self(self)
91
        len_of_args = len(args)
92
        if len_of_args > 1:
93
            raise TypeError(f'multivalued_dict expected at most 1 arguments, got {len_of_args}')
94
        else:
95
            if 'data' not in self.__dict__:
96
                self.data = defaultdict(list)
97
            if len_of_args == 1:
98
                initial_items = args[0]
99
                self.update(initial_items)
100
        if kwargs != dict():
101
            self.update(kwargs)
102
    
103
    def __repr__(self):
104
        multivalued_dict.__is_self(self)
105
        return f'multivalued_dict({dict(self.data)})'
106
    
107
    def __iter__(self):
108
        '''
109
            
110
        '''
111
        multivalued_dict.__is_self(self)
112
        return self.data.__iter__()
113
    
114
    def __len__(self):
115
        '''
116
            >>> mv_d = multivalued_dict([['a', 'test-1'], ['a', 'test-2'], ['a', 'test-3'], ['b', 'test-4']])
117
            >>> mv_d.__len__()
118
            2
119
        '''
120
        multivalued_dict.__is_self(self)
121
        return self.data.__len__()
122
    
123
    def __lenvalue__(self, key = None):
124
        '''
125
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
126
            >>> mv_d.__lenvalue__()
127
            6
128
            >>> mv_d.__lenvalue__('a')
129
            3
130
        '''
131
        multivalued_dict.__is_self(self)
132
        if key == None:
133
            return sum(map(len, self.data.values()))
134
        else:
135
            return len(self.data[key])
136
    
137
    def __getitem__(self, key):
138
        '''
139
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
140
            >>> mv_d['a']
141
            ['test-1']
142
        '''
143
        multivalued_dict.__is_self(self)
144
        if key in self.data:
145
            return self.data[key]
146
        else:
147
            raise KeyError(key)
148
    
149
    def __matchkv__(self, key, value):
150
        '''
151
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
152
            >>> mv_d.__matchkv__('b', 3)
153
            False
154
            >>> mv_d.__matchkv__('a', 2)
155
            True
156
            >>> mv_d.__matchkv__('d', 1)
157
            False
158
        '''
159
        multivalued_dict.__is_self(self)
160
        return value in self.data[key]
161
    
162
    def __eq__(self, other):
163
        '''
164
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
165
            >>> mv_d
166
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
167
            >>> mv_d == {'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']}
168
            True
169
            >>> mv_d == {'a': ['test-1'], 'b': ['test-2'], 'c': ['test-0']}
170
            False
171
        '''
172
        multivalued_dict.__is_self(self)
173
        return self.data.__eq__(other)
174
    
175
    def __contains__(self, key):
176
        '''
177
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
178
            >>> mv_d
179
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
180
            >>> 'a' in mv_d
181
            True
182
            >>> 'd' in mv_d
183
            False
184
        '''
185
        multivalued_dict.__is_self(self)
186
        return self.data.__contains__(key)
187
    
188
    def __delkv__(self, key, value, allkv = True, direction = START_POS):
189
        '''
190
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'z'], ['a', 'y'], ['a', 'z'], ['a', 'y']])
191
            >>> mv_d
192
            multivalued_dict({'a': ['x', 'y', 'z', 'y', 'z', 'y']})
193
            
194
            >>> mv_d.__delkv__('a', 'y', False)
195
            >>> mv_d
196
            multivalued_dict({'a': ['x', 'z', 'y', 'z', 'y']})
197
            
198
            >>> mv_d.__delkv__('a', 'y', False, END_POS)
199
            >>> mv_d
200
            multivalued_dict({'a': ['x', 'z', 'y', 'z']})
201
            
202
            >>> mv_d.__delkv__('a', 'z')
203
            >>> mv_d
204
            multivalued_dict({'a': ['x', 'y']})
205
        '''
206
        multivalued_dict.__is_self(self)
207
        assert allkv in (True, False), '"allkv" can only be True or False'
208
        assert direction in (START_POS, END_POS), '"direction" can only be START_POS or END_POS'
209
        
210
        if allkv:
211
            while value in self.data[key]:
212
                self.data[key].remove(value)
213
        else:
214
            if direction == START_POS:
215
                self.data[key].remove(value)
216
            elif direction == END_POS:
217
                value_len = len(self.data[key])
218
                for i in range(value_len):
219
                    if self.data[key][-1 - i] == value:
220
                        self.data[key].__delitem__(-1 - i)
221
                        break
222
    
223
    def __delitem__(self, key):
224
        '''
225
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
226
            >>> mv_d.__delitem__('b')
227
            >>> mv_d
228
            multivalued_dict({'a': ['test-1'], 'c': ['test-3']})
229
        '''
230
        multivalued_dict.__is_self(self)
231
        self.data.__delitem__(key)
232
        
233
    def __setitem__(self, key, item):
234
        '''
235
            >>> mv_d = multivalued_dict([['a', 'test-1'], ['a', 'test-2'], ['a', 'test-3'], ['b', 'test-4']])
236
            >>> mv_d
237
            multivalued_dict({'a': ['test-1', 'test-2', 'test-3'], 'b': ['test-4']})
238
            
239
            >>> mv_d.__setitem__('c', 'test-5')
240
            >>> mv_d
241
            multivalued_dict({'a': ['test-1', 'test-2', 'test-3'], 'b': ['test-4'], 'c': ['test-5']})
242
            
243
            >>> mv_d.__setitem__('a', 'test-0')
244
            >>> mv_d
245
            multivalued_dict({'a': ['test-0'], 'b': ['test-4'], 'c': ['test-5']})
246
        '''
247
        multivalued_dict.__is_self(self)
248
        self.data.__setitem__(key, [item])
249
    
250
    def get(self, key, default = None):
251
        '''
252
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
253
            >>> mv_d.get('a')
254
            ['test-1']
255
            >>> mv_d.get('d')
256
            [None]
257
        '''
258
        multivalued_dict.__is_self(self)
259
        return self.data.get(key, [default])
260
    
261
    def count(self, key, value):
262
        '''
263
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'y'], ['a', 'z'], ['a', 'z'], ['a', 'z']])
264
            >>> mv_d.count('a', 'y')
265
            2
266
        '''
267
        multivalued_dict.__is_self(self)
268
        return self.data[key].count(value)
269
    
270
    def update(self, *args, **kwargs):
271
        '''
272
            >>> mv_d = multivalued_dict()
273
            >>> mv_d
274
            multivalued_dict({})
275
            
276
            >>> mv_d.update({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
277
            >>> mv_d
278
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
279
            
280
            >>> mv_d.update([['a', 'test-4'], ['a', 'test-5']])
281
            >>> mv_d
282
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3']})
283
            
284
            >>> mv_d.update(c = 'test-3')
285
            >>> mv_d
286
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3', 'test-3']})
287
            
288
            >>> mv_d.update([['b', 'test-6'], ['c', 'test-7']], a = 'test-8')
289
            >>> mv_d
290
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5', 'test-8'], 'b': ['test-2', 'test-6'], 'c': ['test-3', 'test-3', 'test-7']})
291
            
292
            >>> mv_d.update(multivalued_dict({'d': 'test-9', 'e': 'test-10'}))
293
            >>> mv_d
294
            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']})
295
        '''
296
        multivalued_dict.__is_self(self)
297
        len_of_args = len(args)
298
        if len_of_args > 1:
299
            raise TypeError(f'multivalued_dict expected at most 1 arguments, got {len_of_args}')
300
        if len_of_args == 1:
301
            update_items = args[0]
302
            if not isinstance(update_items, Iterable):
303
                raise TypeError(f"'{update_items.__class__.__name__}' object is not iterable")
304
            if multivalued_dict.__is_multivalued_dict__(update_items):
305
                for _key, _value in update_items.items():
306
                    self.data[_key].extend(_value)
307
            elif isinstance(update_items, dict):
308
                for _key, _value in update_items.items():
309
                    self.data[_key].append(_value)
310
            else:
311
                i = 0
312
                for item in update_items:
313
                    if not isinstance(item, Iterable):
314
                        raise TypeError(f'cannot convert dictionary update sequence element #{i} to a sequence')
315
                    if len(item) != 2:
316
                        raise ValueError(f'dictionary update sequence element #{i} has length {len(item)}; 2 is required')
317
                    _key, _value = item
318
                    self.data[_key].append(_value)
319
                    i += 1
320
        if kwargs != dict():
321
            self.update(kwargs)
322
    
323
    def setdefault(self, key, default = None):
324
        '''
325
            
326
        '''
327
        multivalued_dict.__is_self(self)
328
        return self.data.setdefault(key, [default])
329
        
330
    def pop(self, key, default=__marker):
331
        '''
332
            
333
        '''
334
        multivalued_dict.__is_self(self)
335
        if default is self.__marker:
336
            return self.data.pop(key)
337
        else:
338
            return self.data.pop(key, [default])
339
    
340
    def popitem(self):
341
        '''
342
            
343
        '''
344
        multivalued_dict.__is_self(self)
345
        return self.data.popitem()
346
    
347
    def copy(self):
348
        '''
349
            
350
        '''
351
        multivalued_dict.__is_self(self)
352
        return multivalued_dict(self.data)
353
    
354
    def clear(self):
355
        '''
356
            
357
        '''
358
        multivalued_dict.__is_self(self)
359
        return self.data.clear()
360
    
361
    def items(self):
362
        '''
363
            
364
        '''
365
        multivalued_dict.__is_self(self)
366
        return self.data.items()
367
    
368
    def keys(self):
369
        '''
370
            
371
        '''
372
        multivalued_dict.__is_self(self)
373
        return self.data.keys()
374
    
375
    def values(self):
376
        '''
377
            
378
        '''
379
        multivalued_dict.__is_self(self)
380
        return self.data.values()
381
    
382
    def reverse(self, key):
383
        '''
384
            
385
        '''
386
        multivalued_dict.__is_self(self)
387
        self.data[key].reverse()
388