Passed
Push — master ( 353b2e...f9c4d8 )
by sosei
127:17 queued 120:37
created

multivalued_dict.__matchkv__()   A

Complexity

Conditions 1

Size

Total Lines 12
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 3
dl 0
loc 12
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 multivalued_dict(UserDict):
10
    '''
11
        multivalued_dict() -> new empty dictionary
12
        multivalued_dict(mapping) -> new dictionary initialized from a mapping object's
13
            (key, value) pairs
14
        multivalued_dict(iterable) -> new dictionary initialized as if via:
15
            d = {}
16
            for k, v in iterable:
17
                d[k].append(v)
18
        multivalued_dict(**kwargs) -> new dictionary initialized with the name=value pairs
19
            in the keyword argument list.  For example:  dict(one=1, two=2)
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
        >>> multivalued_dict([['a', 'test-1']], [['b', 'test-2']])
49
        Traceback (most recent call last):
50
        TypeError: multivalued_dict expected at most 1 arguments, got 2
51
    '''
52
    
53
    __marker = object()
54
    
55
    @staticmethod
56
    def __is_self(v_self):
57
        if not multivalued_dict.__is_multivalued_dict__(v_self):
58
            raise TypeError(f"descriptor '{inspect.currentframe().f_back.f_code.co_name}' requires a 'multivalued_dict' object but received a '{v_self.__class__.__name__}'")
59
    
60
    @classmethod
61
    def __is_multivalued_dict__(cls, x):
62
        '''
63
            >>> mv_d = multivalued_dict()
64
            >>> multivalued_dict.__is_multivalued_dict__(mv_d)
65
            True
66
        '''
67
        return (isinstance(x, cls) or ((True if x.default_factory == type([]) else False) if isinstance(x, defaultdict) else False))
68
    
69
    @classmethod
70
    def fromkeys(cls, iterable, value = None):
71
        '''
72
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'])
73
            multivalued_dict({'a': [None], 'b': [None], 'c': [None]})
74
            >>> multivalued_dict.fromkeys(['a', 'b', 'c'], 'test')
75
            multivalued_dict({'a': ['test'], 'b': ['test'], 'c': ['test']})
76
        '''
77
        dict_var = dict.fromkeys(iterable, value)
78
        return cls(dict_var)
79
80
    def __init__(self, *args, **kwargs):
81
        '''
82
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
83
            >>> mv_d
84
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
85
            
86
            >>> mv_d.__init__({'d': 'test-4'})
87
            >>> mv_d
88
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4']})
89
            
90
            >>> multivalued_dict.__init__(mv_d, {'e': 'test-5'})
91
            >>> mv_d
92
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4'], 'e': ['test-5']})
93
            
94
            >>> mv_d.__init__({'a': 'test-6'})
95
            >>> mv_d
96
            multivalued_dict({'a': ['test-1', 'test-6'], 'b': ['test-2'], 'c': ['test-3'], 'd': ['test-4'], 'e': ['test-5']})
97
            
98
            >>> multivalued_dict.__init__('x')
99
            Traceback (most recent call last):
100
            TypeError: descriptor '__init__' requires a 'multivalued_dict' object but received a 'str'
101
        '''
102
        multivalued_dict.__is_self(self)
103
        len_of_args = len(args)
104
        if len_of_args > 1:
105
            raise TypeError(f'multivalued_dict expected at most 1 arguments, got {len_of_args}')
106
        else:
107
            if not hasattr(self, 'data'):
108
                self.data = defaultdict(list)
109
            if len_of_args == 1:
110
                initial_items = args[0]
111
                self.update(initial_items)
112
        if kwargs != dict():
113
            self.update(kwargs)
114
    
115
    def __repr__(self):
116
        multivalued_dict.__is_self(self)
117
        return f'multivalued_dict({dict(self.data)})'
118
    
119
    def __iter__(self):
120
        '''
121
            >>> multivalued_dict(multivalued_dict({'a': 'test-1'}))
122
            multivalued_dict({'a': ['test-1']})
123
            
124
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
125
            >>> multivalued_dict(mv_d.__iter__())
126
            multivalued_dict({'a': [['test-1']], 'b': [['test-2']], 'c': [['test-3']]})
127
        '''
128
        multivalued_dict.__is_self(self)
129
        return iter(self.data.items())
130
    
131
    def __len__(self):
132
        '''
133
            >>> mv_d = multivalued_dict([['a', 'test-1'], ['a', 'test-2'], ['a', 'test-3'], ['b', 'test-4']])
134
            >>> mv_d.__len__()
135
            2
136
        '''
137
        multivalued_dict.__is_self(self)
138
        return self.data.__len__()
139
    
140
    def __lenvalue__(self, key = __marker):
141
        '''
142
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
143
            >>> mv_d.__lenvalue__()
144
            6
145
            >>> mv_d.__lenvalue__('a')
146
            3
147
        '''
148
        multivalued_dict.__is_self(self)
149
        if key is self.__marker:
150
            return sum(map(len, self.data.values()))
151
        else:
152
            return len(self.data[key])
153
    
154
    def __getitem__(self, key):
155
        '''
156
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
157
            >>> mv_d['a']
158
            ['test-1']
159
            
160
            >>> mv_d['d']
161
            Traceback (most recent call last):
162
            KeyError: 'd'
163
        '''
164
        multivalued_dict.__is_self(self)
165
        if key in self.data:
166
            return self.data[key]
167
        else:
168
            raise KeyError(key)
169
    
170
    def __matchkv__(self, key, value):
171
        '''
172
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3], ['b', 1], ['b', 2], ['c', 1]])
173
            >>> mv_d.__matchkv__('b', 3)
174
            False
175
            >>> mv_d.__matchkv__('a', 2)
176
            True
177
            >>> mv_d.__matchkv__('d', 1)
178
            False
179
        '''
180
        multivalued_dict.__is_self(self)
181
        return value in self.data[key]
182
    
183
    def __eq__(self, other):
184
        '''
185
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
186
            >>> mv_d
187
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
188
            >>> mv_d == {'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']}
189
            True
190
            >>> mv_d == {'a': ['test-1'], 'b': ['test-2'], 'c': ['test-0']}
191
            False
192
        '''
193
        multivalued_dict.__is_self(self)
194
        return self.data.__eq__(other)
195
    
196
    def __contains__(self, key):
197
        '''
198
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
199
            >>> mv_d
200
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
201
            >>> 'a' in mv_d
202
            True
203
            >>> 'd' in mv_d
204
            False
205
        '''
206
        multivalued_dict.__is_self(self)
207
        return self.data.__contains__(key)
208
    
209
    def __delkv__(self, key, value, allkv = True, direction = START_POS):
210
        '''
211
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'z'], ['a', 'y'], ['a', 'z'], ['a', 'y']])
212
            >>> mv_d
213
            multivalued_dict({'a': ['x', 'y', 'z', 'y', 'z', 'y']})
214
            
215
            >>> mv_d.__delkv__('a', 'y', False)
216
            >>> mv_d
217
            multivalued_dict({'a': ['x', 'z', 'y', 'z', 'y']})
218
            
219
            >>> mv_d.__delkv__('a', 'y', False, END_POS)
220
            >>> mv_d
221
            multivalued_dict({'a': ['x', 'z', 'y', 'z']})
222
            
223
            >>> mv_d.__delkv__('a', 'z')
224
            >>> mv_d
225
            multivalued_dict({'a': ['x', 'y']})
226
        '''
227
        multivalued_dict.__is_self(self)
228
        assert allkv in (True, False), '"allkv" can only be True or False'
229
        assert direction in (START_POS, END_POS), '"direction" can only be START_POS or END_POS'
230
        
231
        if allkv:
232
            while value in self.data[key]:
233
                self.data[key].remove(value)
234
        else:
235
            if direction == START_POS:
236
                self.data[key].remove(value)
237
            elif direction == END_POS:
238
                value_len = len(self.data[key])
239
                for i in range(value_len):
240
                    if self.data[key][-1 - i] == value:
241
                        self.data[key].__delitem__(-1 - i)
242
                        break
243
    
244
    def __delitem__(self, key):
245
        '''
246
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
247
            >>> mv_d.__delitem__('b')
248
            >>> mv_d
249
            multivalued_dict({'a': ['test-1'], 'c': ['test-3']})
250
        '''
251
        multivalued_dict.__is_self(self)
252
        self.data.__delitem__(key)
253
        
254
    def __setitem__(self, key, item):
255
        '''
256
            >>> mv_d = multivalued_dict([['a', 'test-1'], ['a', 'test-2'], ['a', 'test-3'], ['b', 'test-4']])
257
            >>> mv_d
258
            multivalued_dict({'a': ['test-1', 'test-2', 'test-3'], 'b': ['test-4']})
259
            
260
            >>> mv_d.__setitem__('c', 'test-5')
261
            >>> mv_d
262
            multivalued_dict({'a': ['test-1', 'test-2', 'test-3'], 'b': ['test-4'], 'c': ['test-5']})
263
            
264
            >>> mv_d.__setitem__('a', 'test-0')
265
            >>> mv_d
266
            multivalued_dict({'a': ['test-0'], 'b': ['test-4'], 'c': ['test-5']})
267
        '''
268
        multivalued_dict.__is_self(self)
269
        self.data.__setitem__(key, [item])
270
    
271
    def get(self, key, default = None):
272
        '''
273
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
274
            >>> mv_d.get('a')
275
            ['test-1']
276
            >>> mv_d.get('d')
277
            [None]
278
        '''
279
        multivalued_dict.__is_self(self)
280
        return self.data.get(key, [default])
281
    
282
    def count(self, key, value):
283
        '''
284
            >>> mv_d = multivalued_dict([['a', 'x'], ['a', 'y'], ['a', 'y'], ['a', 'z'], ['a', 'z'], ['a', 'z']])
285
            >>> mv_d.count('a', 'y')
286
            2
287
        '''
288
        multivalued_dict.__is_self(self)
289
        return self.data[key].count(value)
290
    
291
    def update(self, *args, **kwargs):
292
        '''
293
            >>> mv_d = multivalued_dict()
294
            >>> mv_d
295
            multivalued_dict({})
296
            
297
            >>> mv_d.update({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
298
            >>> mv_d
299
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
300
            
301
            >>> mv_d.update([['a', 'test-4'], ['a', 'test-5']])
302
            >>> mv_d
303
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3']})
304
            
305
            >>> mv_d.update(c = 'test-3')
306
            >>> mv_d
307
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5'], 'b': ['test-2'], 'c': ['test-3', 'test-3']})
308
            
309
            >>> mv_d.update([['b', 'test-6'], ['c', 'test-7']], a = 'test-8')
310
            >>> mv_d
311
            multivalued_dict({'a': ['test-1', 'test-4', 'test-5', 'test-8'], 'b': ['test-2', 'test-6'], 'c': ['test-3', 'test-3', 'test-7']})
312
            
313
            >>> mv_d.update(multivalued_dict({'d': 'test-9', 'e': 'test-10'}))
314
            >>> mv_d
315
            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']})
316
317
            >>> mv_d.update([['a', 'test-1']], [['b', 'test-2']])
318
            Traceback (most recent call last):
319
            TypeError: multivalued_dict expected at most 1 arguments, got 2
320
321
            >>> mv_d.update(1)
322
            Traceback (most recent call last):
323
            TypeError: 'int' object is not iterable
324
            
325
            >>> mv_d.update([1])
326
            Traceback (most recent call last):
327
            TypeError: cannot convert dictionary update sequence element #0 to a sequence
328
            
329
            >>> mv_d.update([['1', '2', '3']])
330
            Traceback (most recent call last):
331
            ValueError: dictionary update sequence element #0 has length 3; 2 is required
332
            
333
            >>> multivalued_dict.update('x')
334
            Traceback (most recent call last):
335
            TypeError: descriptor 'update' requires a 'multivalued_dict' object but received a 'str'
336
        '''
337
        multivalued_dict.__is_self(self)
338
        len_of_args = len(args)
339
        if len_of_args > 1:
340
            raise TypeError(f'multivalued_dict expected at most 1 arguments, got {len_of_args}')
341
        if len_of_args == 1:
342
            update_items = args[0]
343
            if not isinstance(update_items, Iterable):
344
                raise TypeError(f"'{update_items.__class__.__name__}' object is not iterable")
345
            if multivalued_dict.__is_multivalued_dict__(update_items):
346
                for _key, _value in update_items.items():
347
                    self.data[_key].extend(_value)
348
            elif isinstance(update_items, dict):
349
                for _key, _value in update_items.items():
350
                    self.data[_key].append(_value)
351
            else:
352
                i = 0
353
                for item in update_items:
354
                    if not isinstance(item, Iterable):
355
                        raise TypeError(f'cannot convert dictionary update sequence element #{i} to a sequence')
356
                    if len(item) != 2:
357
                        raise ValueError(f'dictionary update sequence element #{i} has length {len(item)}; 2 is required')
358
                    _key, _value = item
359
                    self.data[_key].append(_value)
360
                    i += 1
361
        if kwargs != dict():
362
            self.update(kwargs)
363
    
364
    def setdefault(self, key, default = None):
365
        '''
366
            >>> mv_d = multivalued_dict({'a': 'test-1', 'c': 'test-3'})
367
            >>> mv_d.setdefault('a')
368
            ['test-1']
369
            >>> mv_d.setdefault('b')
370
            [None]
371
            >>> mv_d
372
            multivalued_dict({'a': ['test-1'], 'c': ['test-3'], 'b': [None]})
373
            >>> mv_d.setdefault('d', 'test=4')
374
            ['test=4']
375
            >>> mv_d
376
            multivalued_dict({'a': ['test-1'], 'c': ['test-3'], 'b': [None], 'd': ['test=4']})
377
        '''
378
        multivalued_dict.__is_self(self)
379
        return self.data.setdefault(key, [default])
380
        
381
    def pop(self, key, default=__marker):
382
        '''
383
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
384
            >>> mv_d.pop('b')
385
            ['test-2']
386
            >>> mv_d
387
            multivalued_dict({'a': ['test-1'], 'c': ['test-3']})
388
            
389
            >>> mv_d.pop('d')
390
            Traceback (most recent call last):
391
            KeyError: 'd'
392
            
393
            >>> mv_d.pop('d', 'test-0')
394
            ['test-0']
395
            >>> mv_d
396
            multivalued_dict({'a': ['test-1'], 'c': ['test-3']})
397
        '''
398
        multivalued_dict.__is_self(self)
399
        if default is self.__marker:
400
            return self.data.pop(key)
401
        else:
402
            return self.data.pop(key, [default])
403
    
404
    def popitem(self):
405
        '''
406
            D.popitem() -> (k, v), remove and return some (key, value) pair as a 2-tuple; but raise KeyError if D is empty.
407
            
408
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
409
            >>> mv_d
410
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
411
            >>> mv_d.popitem()
412
            ('c', ['test-3'])
413
        '''
414
        multivalued_dict.__is_self(self)
415
        return self.data.popitem()
416
    
417
    def copy(self):
418
        '''
419
            D.copy() -> a shallow copy of D
420
            
421
            >>> mv_d_a = multivalued_dict([['a', 1], ['a', 2], ['a', 3]])
422
            >>> mv_d_b = mv_d_a.copy()
423
            >>> mv_d_a
424
            multivalued_dict({'a': [1, 2, 3]})
425
            >>> mv_d_b
426
            multivalued_dict({'a': [1, 2, 3]})
427
            >>> mv_d_a['a'][1] = 99
428
            >>> mv_d_a
429
            multivalued_dict({'a': [1, 99, 3]})
430
            >>> mv_d_b
431
            multivalued_dict({'a': [1, 2, 3]})
432
        '''
433
        multivalued_dict.__is_self(self)
434
        return multivalued_dict(self.data)
435
    
436
    def items(self):
437
        '''
438
            D.items() -> a set-like object providing a view on D's items
439
            
440
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
441
            >>> for k, v in mv_d.items():
442
            ...     print(f'key = {k}, value = {v}')
443
            ...
444
            key = a, value = ['test-1']
445
            key = b, value = ['test-2']
446
            key = c, value = ['test-3']
447
        '''
448
        multivalued_dict.__is_self(self)
449
        return self.data.items()
450
    
451
    def keys(self):
452
        '''
453
            D.keys() -> a set-like object providing a view on D's keys
454
            
455
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
456
            >>> for k in mv_d.keys():
457
            ...     print(f'key = {k}')
458
            ...
459
            key = a
460
            key = b
461
            key = c
462
        '''
463
        multivalued_dict.__is_self(self)
464
        return self.data.keys()
465
    
466
    def values(self):
467
        '''
468
            D.values() -> an object providing a view on D's values
469
            
470
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
471
            >>> for v in mv_d.values():
472
            ...     print(f'value = {v}')
473
            ...
474
            value = ['test-1']
475
            value = ['test-2']
476
            value = ['test-3']
477
        '''
478
        multivalued_dict.__is_self(self)
479
        return self.data.values()
480
    
481
    def clear(self):
482
        '''
483
            D.clear() -> None.  Remove all items from D.
484
            
485
            >>> mv_d = multivalued_dict({'a': 'test-1', 'b': 'test-2', 'c': 'test-3'})
486
            >>> mv_d
487
            multivalued_dict({'a': ['test-1'], 'b': ['test-2'], 'c': ['test-3']})
488
            >>> mv_d.clear()
489
            >>> mv_d
490
            multivalued_dict({})
491
        '''
492
        multivalued_dict.__is_self(self)
493
        self.data.clear()
494
    
495
    def reverse(self, key):
496
        '''
497
            >>> mv_d = multivalued_dict([['a', 1], ['a', 2], ['a', 3]])
498
            >>> mv_d
499
            multivalued_dict({'a': [1, 2, 3]})
500
            >>> mv_d.reverse('a')
501
            >>> mv_d
502
            multivalued_dict({'a': [3, 2, 1]})
503
        '''
504
        multivalued_dict.__is_self(self)
505
        self.data[key].reverse()
506