1# -*- coding: utf-8 -*- 2 3# 4# omdict - Ordered Multivalue Dictionary. 5# 6# Ansgar Grunseid 7# grunseid.com 8# grunseid@gmail.com 9# 10# License: Build Amazing Things (Unlicense) 11# 12 13from __future__ import absolute_import 14 15import unittest 16from itertools import product, repeat 17 18import six 19from six.moves import map, zip, zip_longest 20 21from orderedmultidict.orderedmultidict import omdict 22 23try: 24 from collections import OrderedDict as odict # Python 2.7+. 25except ImportError: 26 from ordereddict import OrderedDict as odict # Python 2.4-2.6. 27 28_unique = object() 29 30 31def callable_attr(o, attr): 32 return hasattr(o, attr) and callable(getattr(o, attr)) 33 34 35def is_iterator(i): 36 return callable_attr(i, 'next') or callable_attr(i, '__next__') 37 38 39# Utility list subclass to expose items() and iteritems() methods on basic 40# lists. This provides a common iteration interface for lists and dictionaries 41# for looping through their items without having to test for and maintain two 42# separate bodies, one for lists and one for dictionaries. 43# 44# So instead of requiring two bodies, one for lists and one for dicts 45# 46# lists = [[(1,1),(2,2)]] 47# dicts = [{1:1,2:2}] 48# for lst in lists: 49# lst == ... 50# for dic in dicts: 51# dic.items() == ... 52# 53# list and dictionary looping bodies can be merged with itemlist 54# 55# itemlist = [itemlist([(1,1),(2,2)]), {1:1,2:2}] 56# for ilist in itemlist: 57# ilist.items() == ... 58# 59 60 61class itemlist(list): 62 63 def items(self): 64 return self 65 66 def iteritems(self): 67 return iter(self) 68 69 70class TestOmdict(unittest.TestCase): 71 72 def setUp(self): 73 self.inits = [ 74 {}, {1: 1}, {1: 1, 2: 2, 3: 3}, {None: None}, { 75 None: None, 1: 1, 2: 2}, {False: False}, 76 ] 77 self.inits += list(map(itemlist, [ 78 [], [(1, 1)], [(1, 1), (2, 2)], [(1, 1), (2, 2), (1, 1)], 79 [(1, 1), (1, 1), (1, 1)], [(None, None), (None, None)], 80 [(False, False)], 81 [(None, 1), (1, None), (None, None), (None, 1), (1, None)], 82 ])) 83 84 # Updates to test update() and updateall(). 85 self.updates = [ 86 {}, {7: 7}, {7: 7, 8: 8, 9: 9}, {None: None}, {1: 1, 2: 2}] 87 self.updates += list(map(itemlist, [ 88 [], [(7, 7)], [(7, 7), (8, 8), (9, 9)], [(None, 'none')], 89 [(9, 9), (1, 2)], [(7, 7), (7, 7), (8, 8), (7, 77)], 90 [(1, 11), (1, 111), (1, 1111), (2, 22), 91 (2, 222), ('a', 'a'), ('a', 'aa')], 92 ])) 93 94 self.keyword_updates = [ 95 {}, {'1': 1}, {'1': 1, '2': 2}, { 96 'sup': 'pumps', 'scewps': None}, {'aa': 'aa'}, 97 ] 98 99 # Items not initially in any of the multidict inputs self.inits. 100 self.nonitems = [ 101 (44, 44), (None, 44), (55, None), ('a', 'b'), (11, 11), (22, 22)] 102 103 # Keys not initially in any of the multidict inputs self.inits or in 104 # self.nonitems. 105 self.nonkeys = [_unique, 'asdfasdosduf', 'oaisfiapsn', 'ioausopdaui'] 106 107 self.valuelist = [1, 2, 3, None, 'a', 'b', object()] 108 109 def test_init(self): 110 for init in self.inits: 111 omd = omdict(init) 112 assert omd.allitems() == list(init.items()) 113 114 omd1 = omdict(init) 115 omd2 = omdict(omd1) 116 assert omd1.allitems() == list(omd2.allitems()) 117 118 # Support both *args and **kwargs dictionary initialization. 119 items = [('sape', 4139), ('guido', 4127), ('jack', 4098)] 120 assert omdict(items).allitems() == items 121 omd_item_set = set(omdict(sape=4139, guido=4127, jack=4098).items()) 122 assert omd_item_set == set(items) # Keyword order isn't preserved. 123 124 def test_load(self): 125 omd = omdict() 126 for init in self.inits: 127 assert omd.load(init) == omd 128 assert omd.allitems() == list(init.items()) 129 130 def test_copy(self): 131 for init in self.inits: 132 omd = omdict(init) 133 copy = omd.copy() 134 assert omd is not copy and omd == copy 135 136 def test_clear(self): 137 for init in self.inits: 138 omd = omdict(init) 139 omd.clear() 140 assert omd.items() == [] 141 142 def test_fromkeys(self): 143 for init in self.inits: 144 keys = [key for key, value in init.items()] 145 allitems = omdict.fromkeys(keys, _unique).allitems() 146 assert allitems == list(zip(keys, repeat(_unique))) 147 148 def test_has_key(self): 149 for init in self.inits: 150 omd = omdict(init) 151 for key, value in init.items(): 152 assert key in omd 153 154 def test_update(self): 155 # Some manual tests. 156 omd = omdict() 157 omd.update([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) 158 assert omd.allitems() == [(1, 111), (2, 22), (3, 3)] 159 160 omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) 161 omd.update({1: None, 2: None, 3: None}) 162 assert omd.allitems() == [(1, None), (2, None), (3, None)] 163 164 for init in self.inits: 165 zipped = zip(self.updates, self.keyword_updates) 166 for update, keyword_update in zipped: 167 omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init) 168 oldomd = omd1.copy() 169 # Reduce the update to just the final items that will be 170 # present post update(), where repeated keys will be reduced to 171 # their last occurring value. For example, [(7,7),(7,8)] would 172 # be reduced to [(7,8)]. 173 reduced = [ 174 i for i in update.items() if i in odict(update).items()] 175 176 # Update with a dictionary. 177 omd1.update(update) 178 # Update with keyword expansion. 179 omd2.update(**keyword_update) 180 # Update with both a dictionary and keyword expansion. 181 omd3.update(update, **keyword_update) 182 183 # Verification. 184 if update or keyword_update: 185 for key, value in reduced: 186 assert key in omd1 and key in omd3 187 for key, value in keyword_update.items(): 188 assert key in omd2 and key in omd3 189 else: 190 assert omd1 == omd2 == omd3 == oldomd 191 192 def test_updateall(self): 193 # Some manual tests. 194 omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) 195 omd.updateall({1: None, 2: None, 3: None}) 196 assert omd.allitems() == [(1, None), (2, None), (3, None)] 197 198 omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) 199 omd.updateall([(1, None), (2, None), (3, None), (1, None), (2, None)]) 200 assert omd.allitems() == [ 201 (1, None), (1, None), (2, None), (3, None), (2, None)] 202 203 omd = omdict([(1, 1), (1, 11), (2, 2), (3, 3), (1, 111), (2, 22)]) 204 omd.updateall([(1, None), (1, None), (1, None), (2, None)]) 205 assert omd.allitems() == [ 206 (1, None), (1, None), (2, None), (3, 3), (1, None)] 207 208 for init in self.inits: 209 zipped = zip(self.updates, self.keyword_updates) 210 for update, keyword_update in zipped: 211 omd1, omd2, omd3 = omdict(init), omdict(init), omdict(init) 212 oldomd = omd1.copy() 213 214 # Update with a dictionary. 215 omd1.updateall(update) 216 # Update with keyword expansion. 217 omd2.updateall(**keyword_update) 218 # Update with both a dictionary and keyword expansion. 219 omd3.updateall(update, **keyword_update) 220 221 # Verification. 222 if update or keyword_update: 223 for key, value in six.iteritems(update): 224 assert key in omd1 and key in omd3 225 assert value in omd1.getlist( 226 key) and value in omd3.getlist(key) 227 for key, value in keyword_update.items(): 228 assert key in omd2 and key in omd3 229 assert omd2.getlist( 230 key) == omd3.getlist(key) == [value] 231 else: 232 assert omd1 == omd2 == oldomd 233 234 def test_get(self): 235 for init in self.inits: 236 omd = omdict(init) 237 for key in omd.iterkeys(): 238 assert omd.get(key) == omd[key] 239 for nonkey in self.nonkeys: 240 assert omd.get(nonkey) is None 241 assert omd.get(nonkey, _unique) == _unique 242 243 def test_getlist(self): 244 for init in self.inits: 245 omd = omdict(init) 246 for key in omd: 247 assert omd.getlist( 248 key) == [v for k, v in omd.allitems() if k == key] 249 for nonkey in self.nonkeys: 250 assert omd.getlist(nonkey) == [] 251 assert omd.getlist(nonkey, _unique) == _unique 252 253 def test_setdefault(self): 254 for init in self.inits: 255 omd = omdict(init) 256 for key in omd.iterkeys(): 257 assert omd.setdefault(key, _unique) == omd[key] 258 for nonkey in self.nonkeys: 259 assert omd.setdefault(nonkey) is None 260 assert omd[nonkey] is None 261 omd.load(init) 262 for nonkey in self.nonkeys: 263 assert omd.setdefault(nonkey, 123456) == 123456 264 assert omd[nonkey] == 123456 265 266 def test_setdefaultlist(self): 267 for init in self.inits: 268 omd = omdict(init) 269 for key in omd.iterkeys(): 270 assert omd.setdefaultlist(key, _unique) == omd.getlist(key) 271 for nonkey in self.nonkeys: 272 assert omd.setdefaultlist(nonkey) == [None] 273 assert omd.getlist(nonkey) == [None] 274 omd.load(init) 275 for nonkey in self.nonkeys: 276 assert omd.setdefaultlist(nonkey, [1, 2, 3]) == [1, 2, 3] 277 assert omd.getlist(nonkey) == [1, 2, 3] 278 279 # setdefaultlist() with an empty list of values does nothing. 280 for init in self.inits: 281 omd = omdict(init) 282 for key in omd.iterkeys(): 283 values = omd.getlist(key) 284 assert key in omd 285 assert omd.setdefaultlist(key, []) == values 286 assert key in omd and omd.getlist(key) == values 287 for nonkey in self.nonkeys: 288 assert nonkey not in omd 289 assert omd.setdefaultlist(nonkey, []) == [] 290 assert nonkey not in omd 291 292 def test_add(self): 293 for init in self.inits: 294 omd = omdict(init) 295 for key, value in self.nonitems: 296 assert (key, value) not in omd.allitems() 297 assert omd.add(key, value) == omd 298 assert omd.getlist(key)[-1] == value 299 assert omd.allitems()[-1] == (key, value) 300 301 # Repeat the add() calls with the same items and make sure the old 302 # items aren't replaced. 303 oldomd = omd.copy() 304 for key, value in self.nonitems: 305 assert (key, value) in omd.allitems() 306 assert omd.add(key, value) == omd 307 assert len(omd.getlist(key)) == len(oldomd.getlist(key)) + 1 308 assert omd.getlist(key)[-1] == value 309 assert omd.allitems()[-1] == (key, value) 310 311 # Assert that containers are valid values, too, not just immutables 312 # like integers. 313 assert omd.add(_unique, self.updates) == omd 314 assert omd.getlist(_unique)[-1] == self.updates 315 assert omd.allitems()[-1] == (_unique, self.updates) 316 317 # Add() doesn't require a value, and when one isn't provided it 318 # defaults to None. 319 omd = omdict(init) 320 assert omd.add(_unique) == omd 321 assert _unique in omd and omd[_unique] is None 322 323 def test_addlist(self): 324 for init in self.inits: 325 omd = omdict(init) 326 for nonkey in self.nonkeys: 327 assert (nonkey, self.valuelist) not in omd.allitems() 328 assert omd.addlist(nonkey, self.valuelist) == omd 329 assert omd.getlist(nonkey) == self.valuelist 330 assert (omd.allitems()[-1 * len(self.valuelist):] == 331 list(zip(repeat(nonkey), self.valuelist))) 332 333 # Repeat the addlist() calls with the same items and make sure the 334 # old items aren't replaced. 335 oldomd = omd.copy() 336 for nonkey in self.nonkeys: 337 for value in self.valuelist: 338 assert (nonkey, value) in omd.allitems() 339 assert omd.addlist(nonkey, self.valuelist) == omd 340 assert len(omd.getlist(nonkey)) == ( 341 len(oldomd.getlist(nonkey)) + len(self.valuelist)) 342 assert omd.getlist(nonkey) == oldomd.getlist( 343 nonkey) + self.valuelist 344 assert (omd.allitems()[-1 * len(self.valuelist):] == 345 list(zip(repeat(nonkey), self.valuelist))) 346 347 # If an empty list is provided to addlist(), nothing is added. 348 omd = omdict(init) 349 for nonkey in self.nonkeys: 350 assert omd.addlist(nonkey) == omd and nonkey not in omd 351 assert omd.addlist(nonkey, []) == omd and nonkey not in omd 352 353 def test_setlist(self): 354 for init in self.inits: 355 omd = omdict(init) 356 for key in (omd.keys() + self.nonkeys): 357 if key in omd: 358 assert omd.getlist(key) != self.valuelist 359 assert omd.setlist(key, self.valuelist) 360 assert key in omd and omd.getlist(key) == self.valuelist 361 362 # Setting a key to an empty list is identical to deleting the key. 363 for init in self.inits: 364 omd = omdict(init) 365 for nonkey in self.nonkeys: 366 assert nonkey not in omd 367 omd.setlist(nonkey, []) 368 assert nonkey not in omd 369 for key in omd.keys(): 370 assert key in omd 371 omd.setlist(key, []) 372 assert key not in omd 373 assert not omd 374 375 def test_removevalues(self): 376 for init in self.inits: 377 omd = omdict(init) 378 removevals = omd.removevalues # Shorten to linewrap for PEP 8. 379 for nonkey in self.nonkeys: 380 obj = object() 381 values = [1, 1.1, '1.1', (), [], {}, obj, 5.5, '1.1'] 382 383 assert removevals(nonkey, []).getlist(nonkey) == [] 384 assert removevals(nonkey, values).getlist(nonkey) == [] 385 386 omd.addlist(nonkey, values).removevalues(nonkey, []) 387 assert omd.getlist(nonkey) == values 388 assert removevals(nonkey, values).getlist(nonkey) == [] 389 390 omd.addlist(nonkey, values) 391 assert (removevals(nonkey, [1]).getlist(nonkey) == 392 [1.1, '1.1', (), [], {}, obj, 5.5, '1.1']) 393 assert (removevals(nonkey, ['1.1', obj]).getlist(nonkey) == 394 [1.1, (), [], {}, 5.5]) 395 assert (removevals(nonkey, [[], 5.5, ()]).getlist(nonkey) == 396 [1.1, {}]) 397 assert removevals(nonkey, [{}]).getlist(nonkey) == [1.1] 398 assert removevals(nonkey, [1.1]).getlist(nonkey) == [] 399 assert removevals( 400 nonkey, [9, 9.9, 'nope']).getlist(nonkey) == [] 401 402 def test_pop(self): 403 self._test_pop_poplist(lambda omd, key: omd.get(key) == omd.pop(key)) 404 405 def test_poplist(self): 406 self._test_pop_poplist( 407 lambda omd, key: omd.getlist(key) == omd.poplist(key)) 408 409 def _test_pop_poplist(self, assert_lambda): 410 for init in self.inits: 411 omd = omdict(init) 412 items = omd.items() 413 for key in list(omd.keys()): 414 assert assert_lambda(omd, key) 415 newitems = [item for item in items if item[0] != key] 416 assert omd.items() == newitems 417 items = newitems 418 419 omd.load(init) 420 for nonkey in self.nonkeys: 421 self.assertRaises(KeyError, omd.pop, nonkey) 422 assert omd.pop(nonkey, _unique) == _unique 423 self.assertRaises(KeyError, omd.poplist, nonkey) 424 assert omd.poplist(nonkey, _unique) == _unique 425 426 def test_popvalue(self): 427 # popvalue() with no value provided. 428 for init in self.inits: 429 for last in [True, False]: 430 omd = omdict(init) 431 allitems = omd.allitems() 432 while omd.keys(): 433 for key in omd.keys(): 434 if last: 435 value = omd.getlist(key)[-1] 436 _rremove(allitems, (key, value)) 437 else: 438 value = omd[key] 439 allitems.remove((key, value)) 440 441 assert value == omd.popvalue(key, last=last) 442 assert omd.allitems() == allitems 443 444 omd.load(init) 445 for nonkey in self.nonkeys: 446 self.assertRaises(KeyError, omd.popvalue, nonkey) 447 assert omd.popvalue(nonkey, default=_unique) == _unique 448 449 # popvalue() with value provided. 450 # last = True (default). 451 omd = omdict([(1, 1), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)]) 452 assert omd.popvalue(2, 2) == 2 453 assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (2, 2), (3, 3)] 454 assert omd.popvalue(2, 2) == 2 455 assert omd.allitems() == [(1, 1), (2, 2), (3, 3), (3, 3)] 456 assert omd.popvalue(2, 2) == 2 457 assert omd.allitems() == [(1, 1), (3, 3), (3, 3)] 458 # last = False. 459 omd = omdict([(3, 3), (2, 2), (3, 3), (2, 2), (3, 3), (2, 2)]) 460 assert omd.popvalue(2, 2, last=True) == 2 461 assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (2, 2), (3, 3)] 462 assert omd.popvalue(2, 2, last=True) == 2 463 assert omd.allitems() == [(3, 3), (2, 2), (3, 3), (3, 3)] 464 assert omd.popvalue(2, 2, last=True) == 2 465 assert omd.allitems() == [(3, 3), (3, 3), (3, 3)] 466 467 # Invalid key. 468 self.assertRaises(KeyError, omd.popvalue, _unique, _unique) 469 self.assertRaises(KeyError, omd.popvalue, _unique, 2) 470 self.assertRaises(KeyError, omd.popvalue, _unique, 22) 471 self.assertRaises(KeyError, omd.popvalue, _unique, _unique, last=False) 472 self.assertRaises(KeyError, omd.popvalue, _unique, 2) 473 self.assertRaises(KeyError, omd.popvalue, _unique, 22) 474 assert omd.popvalue(_unique, _unique, 'sup') == 'sup' 475 assert omd.popvalue(_unique, 2, 'sup') == 'sup' 476 assert omd.popvalue(_unique, 22, 'sup') == 'sup' 477 478 # Valid key, invalid value. 479 self.assertRaises(ValueError, omd.popvalue, 3, _unique) 480 self.assertRaises(ValueError, omd.popvalue, 3, _unique, False) 481 482 def test_popitem(self): 483 for init in self.inits: 484 # All permutations of booleans <fromall> and <last>. 485 for fromall, last in product([True, False], repeat=2): 486 omd = omdict(init) 487 allitems = omd.allitems() 488 while omd.allitems(): 489 if fromall: 490 key, value = omd.allitems()[-1 if last else 0] 491 else: 492 key = omd.keys()[-1 if last else 0] 493 value = omd[key] 494 495 popkey, popvalue = omd.popitem(fromall=fromall, last=last) 496 assert popkey == key and popvalue == value 497 498 if fromall: 499 if last: 500 _rremove(allitems, (key, value)) 501 else: 502 allitems.remove((key, value)) 503 else: 504 allitems = [(k, v) for k, v in allitems if k != key] 505 assert omd.allitems() == allitems 506 507 omd = omdict() 508 self.assertRaises(KeyError, omd.popitem) 509 510 def test_poplistitem(self): 511 for init in self.inits: 512 for last in [True, False]: 513 omd, omdcopy = omdict(init), omdict(init) 514 while omd.keys(): 515 key, valuelist = omd.poplistitem(last=last) 516 assert key == omdcopy.keys()[-1 if last else 0] 517 assert valuelist == omdcopy.getlist(key) 518 omdcopy.pop(omdcopy.keys()[-1 if last else 0]) 519 520 # poplistitem() on an empty omdict. 521 self.assertRaises(KeyError, omd.poplistitem) 522 523 # Tests every non-'all' items, keys, values, lists method: items(), keys(), 524 # values(), lists(), listitems() and their iterators iteritems(), 525 # iterkeys(), itervalues(), iterlists(), and iterlistitems(). 526 def test_nonall_item_key_value_lists(self): 527 for init in self.inits: 528 dic = odict(init.items()) 529 omd = omdict(init.items()) 530 531 # Testing items(), keys(), values(), lists(), and listitems(). 532 assert omd.items() == list(dic.items()) 533 assert omd.keys() == list(dic.keys()) 534 assert omd.values() == list(dic.values()) 535 iterator = zip(omd.keys(), omd.lists(), omd.listitems()) 536 for key, valuelist, listitem in iterator: 537 assert omd.values(key) == omd.getlist(key) == valuelist 538 assert omd.items( 539 key) == [i for i in init.items() if i[0] == key] 540 assert listitem == (key, valuelist) 541 542 # Testing iteritems(), iterkeys(), itervalues(), and iterlists(). 543 assert is_iterator(omd.iterkeys()) 544 for key1, key2 in zip(omd.iterkeys(), six.iterkeys(dic)): 545 assert key1 == key2 546 assert is_iterator(omd.itervalues()) 547 for val1, val2 in zip(omd.itervalues(), six.itervalues(dic)): 548 assert val1 == val2 549 assert is_iterator(omd.iteritems()) 550 for item1, item2 in zip(omd.iteritems(), six.iteritems(dic)): 551 assert item1 == item2 552 assert is_iterator(omd.iterlists()) 553 for key, values in zip(six.iterkeys(omd), omd.iterlists()): 554 assert omd.getlist(key) == values 555 assert is_iterator(omd.iterlistitems()) 556 iterator = zip( 557 omd.iterkeys(), omd.iterlists(), omd.iterlistitems()) 558 for key, valuelist, listitem in iterator: 559 assert listitem == (key, valuelist) 560 561 # Test iteritems() and itervalues() with a key. 562 for key in omd.iterkeys(): 563 assert is_iterator(omd.iteritems(key)) 564 assert list(omd.iteritems(key)) == list(zip( 565 repeat(key), omd.getlist(key))) 566 assert is_iterator(omd.itervalues(key)) 567 assert list(omd.itervalues(key)) == omd.getlist(key) 568 for nonkey in self.nonkeys: 569 self.assertRaises(KeyError, omd.iteritems, nonkey) 570 self.assertRaises(KeyError, omd.itervalues, nonkey) 571 572 # Tests every 'all' items, keys, values method: allitems(), allkeys(), 573 # allvalues() and their iterators iterallitems(), iterallkeys(), 574 # iterallvalues(). 575 def test_all_items_keys_values_iterall_items_keys_values(self): 576 for init in self.inits: 577 omd = omdict(init) 578 # map(list, zip(*lst)) - doesn't work if lst is empty, lst == []. 579 keys = [key for key, value in init.items()] 580 values = [value for key, value in init.items()] 581 582 # Test allitems(), allkeys(), allvalues(). 583 assert omd.allitems() == list(init.items()) 584 assert omd.allkeys() == keys 585 assert omd.allvalues() == values 586 587 # Test iterallitems(), iterallkeys(), iterallvalues(). 588 for key1, key2 in zip(omd.iterallkeys(), keys): 589 assert key1 == key2 590 for val1, val2 in zip(omd.iterallvalues(), values): 591 assert val1 == val2 592 for item1, item2 in zip(omd.iterallitems(), init.items()): 593 assert item1 == item2 594 595 # Test allitems(), allvalues(), iterallitems() and iterallvalues() 596 # with a key. 597 for key in omd.iterkeys(): 598 assert (omd.allvalues(key) == list(omd.iterallvalues(key)) == 599 omd.getlist(key)) 600 assert (omd.allitems(key) == list(omd.iterallitems(key)) == 601 list(zip(repeat(key), omd.getlist(key)))) 602 for nonkey in self.nonkeys: 603 self.assertRaises(KeyError, omd.allvalues, nonkey) 604 self.assertRaises(KeyError, omd.allitems, nonkey) 605 self.assertRaises(KeyError, omd.iterallvalues, nonkey) 606 self.assertRaises(KeyError, omd.iterallitems, nonkey) 607 608 def test_reverse(self): 609 for init in self.inits: 610 reversed = list(init.items())[::-1] 611 assert omdict(init).reverse().allitems() == reversed 612 613 def test_eq(self): 614 for init in self.inits: 615 d, omd = dict(init), omdict(init) 616 assert d == omd 617 assert omd == omd 618 assert omd == omd.copy() 619 620 def test_ne(self): 621 diff = omdict([(_unique, _unique)]) 622 for init in self.inits: 623 assert omdict(init) != diff 624 # Compare to basic types. 625 for basic in [1, 1.1, '1.1', (), [], object()]: 626 assert omdict(init) != basic 627 628 def test_len(self): 629 for init in self.inits: 630 assert len(omdict(init)) == len(dict(init)) 631 632 def test_size(self): 633 for init in self.inits: 634 assert omdict(init).size() == len(init) 635 636 def test_iter(self): 637 for init in self.inits: 638 omd = omdict(init) 639 for key1, key2 in zip_longest(iter(omd), omd.iterkeys()): 640 assert key1 == key2 641 642 def test_contains(self): 643 for init in self.inits: 644 omd = omdict(init) 645 for key, value in init.items(): 646 assert key in omd 647 648 def test_getitem(self): 649 for init in self.inits: 650 dic = dict(init) 651 omd = omdict(init) 652 for key in omd.iterkeys(): 653 assert omd[key] == dic[key] 654 655 omd = omdict() 656 self.assertRaises(KeyError, omd.__getitem__, _unique) 657 658 def test_set_setitem(self): 659 for init in self.inits: 660 omd = omdict() 661 omd2 = omdict() 662 for key, value in init.items(): 663 omd[key] = value 664 assert omd2.set(key, value) == omd2 665 assert omd == omd2 and omd[key] == value 666 667 # Store containers as values, not just immutables like integers. 668 omd[_unique] = self.valuelist 669 assert omd2.set(_unique, self.valuelist) == omd2 670 assert omd == omd2 and omd[_unique] == self.valuelist 671 672 def test_delitem(self): 673 for init in self.inits: 674 omd = omdict(init) 675 for key in list(omd.keys()): 676 assert key in omd 677 del omd[key] 678 assert key not in omd 679 680 def test_nonzero(self): 681 for init in self.inits: 682 if init: 683 assert omdict(init) 684 else: 685 assert not omdict(init) 686 687 def test_str(self): 688 for init in self.inits: 689 omd = omdict(init) 690 s = '{%s}' % ', '.join( 691 map(lambda p: '%s: %s' % (p[0], p[1]), omd.allitems())) 692 assert s == str(omd) 693 694 def test_odict_omdict_parity(self): 695 for init in self.inits: 696 d = odict(init) 697 omd = omdict(init) 698 699 self._compare_odict_and_omddict(d, omd) 700 self._compare_odict_and_omddict(d.copy(), omd.copy()) # copy(). 701 d.clear(), omd.clear() # clear(). 702 self._compare_odict_and_omddict(d, omd) 703 704 assert dict().update(init) == omdict().update(init) # update(). 705 dict_fromkeys = list(d.fromkeys(init).items()) 706 omdict_fromkeys = list(omd.fromkeys(init).items()) 707 assert dict_fromkeys == omdict_fromkeys # fromkeys() 708 709 def _compare_odict_and_omddict(self, d, omd): 710 assert len(d) == len(omd) # __len__(). 711 712 # __contains__(), has_key(), get(), and setdefault(). 713 for dkey, omdkey in zip(d, omd): 714 assert dkey == omdkey and dkey in d and omdkey in omd 715 assert dkey in d and omdkey in omd 716 assert d.get(dkey) == omd.get(omdkey) 717 d.setdefault(dkey, _unique) 718 omd.setdefault(omdkey, _unique) 719 assert d.get(dkey) == omd.get(omdkey) and d.get(dkey) != _unique 720 for nonkey in self.nonkeys: 721 assert d.get(nonkey) == omd.get(nonkey) is None 722 d.setdefault(nonkey, _unique) 723 omd.setdefault(nonkey, _unique) 724 assert d.get(nonkey) == omd.get(nonkey) == _unique 725 726 # items(), keys, values(), iteritems(), iterkeys, and itervalues(). 727 iterators = [ 728 zip(d.items(), omd.items(), d.keys(), omd.keys(), 729 d.values(), omd.values()), 730 zip(six.iteritems(d), six.iteritems(omd), six.iterkeys(d), 731 six.iterkeys(omd), six.itervalues(d), six.itervalues(omd))] 732 for iterator in iterators: 733 for ditem, omditem, dkey, omdkey, dvalue, omdvalue in iterator: 734 assert dkey == omdkey 735 assert ditem == omditem 736 assert dvalue == omdvalue 737 738 # pop(). 739 dcopy, omdcopy = d.copy(), omd.copy() 740 while dcopy and omdcopy: 741 dpop = dcopy.pop(list(dcopy.keys())[0]) 742 omdpop = omdcopy.pop(list(omdcopy.keys())[0]) 743 assert dpop == omdpop 744 # popitem(). 745 dcopy, omdcopy = d.copy(), omd.copy() 746 while dcopy and omdcopy: 747 assert dcopy.popitem() == omdcopy.popitem() 748 749 # __getitem__(). 750 for dkey, omdkey in zip(six.iterkeys(d), six.iterkeys(omd)): 751 assert d[dkey] == omd[omdkey] 752 # __setitem__(). 753 for dkey, omdkey in zip(d, omd): 754 d[dkey] = _unique 755 omd[omdkey] = _unique 756 assert dkey == omdkey and d[dkey] == omd[omdkey] 757 # __delitem__(). 758 while d and omd: 759 dkey, omdkey = list(d.keys())[0], list(omd.keys())[0] 760 del d[dkey] 761 del omd[omdkey] 762 assert dkey == omdkey and dkey not in d and omdkey not in omd 763 764 def test_fundamentals(self): 765 # Gets, sets, and pops. 766 omd = omdict() 767 omd[1] = 1 768 omd[2] = 2 769 assert omd.allitems() == [(1, 1), (2, 2)] 770 omd[1] = 11 771 assert omd.allitems() == [(1, 11), (2, 2)] 772 omd.add(1, 1.1) 773 assert omd.allitems() == [(1, 11), (2, 2), (1, 1.1)] 774 assert omd.popvalue(1) == 1.1 775 assert omd.allitems() == [(1, 11), (2, 2)] 776 omd.popvalue(2) 777 assert omd.allitems() == [(1, 11)] 778 omd[2] = [2, 2] 779 assert omd.allitems() == [(1, 11), (2, [2, 2])] 780 omd[1] = None 781 assert omd.allitems() == [(1, None), (2, [2, 2])] 782 omd.add(2, None) 783 assert omd.allitems() == [(1, None), (2, [2, 2]), (2, None)] 784 del omd[2] 785 assert omd.allitems() == [(1, None)] 786 omd[3] = 3 787 assert omd.allitems() == [(1, None), (3, 3)] 788 omd.setlist(1, [1, 11, 111]) 789 assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111)] 790 omd.addlist(1, [1111]) 791 omd = omdict([(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)]) 792 assert omd.allitems() == [(1, 1), (3, 3), (1, 11), (1, 111), (1, 1111)] 793 omd[1] = None 794 assert omd.allitems() == [(1, None), (3, 3)] 795 796 def test_pops(self): 797 init = [(1, 1), (2, 2), (1, 1), (1, 2), (1, 3)] 798 799 # pop(). 800 omd = omdict(init) 801 assert omd.pop(1) == 1 802 assert omd.allitems() == [(2, 2)] 803 assert omd.pop(_unique, 'sup') == 'sup' 804 805 # poplist(). 806 omd = omdict(init) 807 assert omd.poplist(1) == [1, 1, 2, 3] 808 assert omd.allitems() == [(2, 2)] 809 self.assertRaises(KeyError, omd.poplist, _unique) 810 assert omd.poplist(_unique, 'sup') == 'sup' 811 812 # popvalue(). 813 omd = omdict(init) 814 assert omd.popvalue(1) == 3 815 assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)] 816 self.assertRaises(KeyError, omd.popvalue, _unique) 817 assert omd.popvalue(_unique, default='sup') == 'sup' 818 assert omd.popvalue(1, last=False) == 1 819 assert omd.allitems() == [(2, 2), (1, 1), (1, 2)] 820 821 # popitem(). 822 omd = omdict(init) 823 assert omd.popitem() == (2, 2) 824 assert omd.allitems() == [(1, 1), (1, 1), (1, 2), (1, 3)] 825 assert omd.popitem() == (1, 1) 826 assert omd.allitems() == [] 827 omd = omdict(init) 828 assert omd.popitem(fromall=True) == (1, 3) 829 assert omd.allitems() == [(1, 1), (2, 2), (1, 1), (1, 2)] 830 assert omd.popitem(fromall=True, last=False) == (1, 1) 831 assert omd.allitems() == [(2, 2), (1, 1), (1, 2)] 832 833 def test_splats(self): 834 items = [('1', 1), ('2', 2), ('3', 3)] 835 omd = omdict(items) 836 837 def splat(*args, **kwargs): 838 return args, set(kwargs.items()) 839 840 assert splat(*omd, **omd) == (tuple(i[0] for i in items), set(items)) 841 842 843class TestBinaryOperators(unittest.TestCase): 844 845 @property 846 def _items(self): 847 original = (1, ['a']), (2, 'b') 848 one_different = (1, ['a']), (3, 'd') 849 all_different = (1, 'c'), (3, 'd') 850 duplicate_key = (1, ['a']), (1, 'e') 851 empty = tuple() 852 return original, one_different, all_different, duplicate_key, empty 853 854 @property 855 def _or_params(self): 856 original, one_diff, all_diff, duplicate_key, empty = self._items 857 return [ 858 # self, other, other as dict, other as omdict. 859 (original, original, original + original, original + original), 860 (original, one_diff, original + one_diff, original + one_diff), 861 (original, all_diff, original + all_diff, original + all_diff), 862 (original, duplicate_key, original + ((1, 'e'),), original + duplicate_key), 863 (original, empty, original, original), 864 ] 865 866 def test_or(self): 867 for s, t, d, o in self._or_params: 868 assert omdict(s) | dict(t) == omdict(d) 869 assert omdict(s) | omdict(t) == omdict(o) 870 871 def test_ior(self): 872 for s, t, d, o in self._or_params: 873 # Test with dict. 874 a = omdict(s) 875 a |= dict(t) 876 assert a == omdict(d) 877 # Test with omdict. 878 a = omdict(s) 879 a |= omdict(t) 880 assert a == omdict(o) 881 882 883class TestUtilities(unittest.TestCase): 884 885 def test_rfind(self): 886 tests = [([], 1, -1), ([1], 1, 0), ([1, 2], 2, 1), 887 ([1, 2, 1, 2], 1, 2), ([1, 2, 3], 4, -1), ([1, 2, 3], 1, 0)] 888 for lst, item, pos in tests: 889 assert _rfind(lst, item) == pos 890 891 def test_rremove(self): 892 tests = [([1, 1], 1, [1]), ([1], 1, []), ([1, 2], 2, [1]), 893 ([1, 2, 3], 1, [2, 3]), ([1, 2, 1, 2], 1, [1, 2, 2]), 894 ([1, 2, 1], 1, [1, 2])] 895 for lst, item, result in tests: 896 _rremove(lst, item) 897 assert lst == result 898 899 nonitems = [None, 'asdf', object(), 1000000] 900 for nonitem in nonitems: 901 self.assertRaises(ValueError, _rremove, lst, nonitem) 902 903 904def _rfind(lst, item): 905 """ 906 Returns the index of the last occurance of <item> in <lst>. Returns -1 if 907 <item> is not in <l>. 908 ex: _rfind([1,2,1,2], 1) == 2 909 """ 910 try: 911 return (len(lst) - 1) - lst[::-1].index(item) 912 except ValueError: 913 return -1 914 915 916def _rremove(lst, item): 917 """ 918 Removes the last occurance of <item> in <lst>, or raises a ValueError if 919 <item> is not in <list>. 920 ex: _rremove([1,2,1,2], 1) == [1,2,2] 921 """ 922 pos = _rfind(lst, item) 923 if pos >= 0: 924 lst.pop(pos) 925 return lst 926 raise ValueError('_rremove(list, x): x not in list') 927