1# Tests for PyObjCTools.KeyValueCoding 2from PyObjCTools.TestSupport import * 3 4from PyObjCTools import KeyValueCoding 5import operator 6import os 7 8 9class TestHelpers (TestCase): 10 def test_msum(self): 11 self.assertEqual(KeyValueCoding.msum([1, 1e100, 1, -1e100] * 10000), 20000) 12 self.assertEqual(KeyValueCoding.msum([1.0, 2.0, 3.0, 4.0]), 10.0) 13 14 def test_keyCaps(self): 15 self.assertEqual(KeyValueCoding.keyCaps("attr"), "Attr") 16 self.assertEqual(KeyValueCoding.keyCaps("Attr"), "Attr") 17 self.assertEqual(KeyValueCoding.keyCaps("AttR"), "AttR") 18 self.assertEqual(KeyValueCoding.keyCaps("attr_with_value"), "Attr_with_value") 19 20 self.assertEqual(KeyValueCoding.keyCaps(b"attr"), b"Attr") 21 self.assertEqual(KeyValueCoding.keyCaps(b"Attr"), b"Attr") 22 self.assertEqual(KeyValueCoding.keyCaps(b"AttR"), b"AttR") 23 self.assertEqual(KeyValueCoding.keyCaps(b"attr_with_value"), b"Attr_with_value") 24 25 26class TestArrayOperators (TestCase): 27 def test_unknown_function(self): 28 values = [ { 'a': 1 } ] 29 30 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, values, '@nofunction.a') 31 32 33 def test_sum(self): 34 arrayOperators = KeyValueCoding._ArrayOperators 35 36 values = [ 37 { 'a' : 1 }, 38 { 'a' : 2, 'b': 4 }, 39 { 'a' : 3, 'b': 2 }, 40 { 'a' : 4 }, 41 ] 42 self.assertEqual(arrayOperators.sum(values, 'a'), 10) 43 self.assertEqual(arrayOperators.sum(values, 'b'), 6) 44 self.assertEqual(arrayOperators.sum(values, 'c'), 0) 45 self.assertEqual(arrayOperators.sum([], 'b'), 0) 46 self.assertRaises(KeyError, arrayOperators.sum, [], ()) 47 48 self.assertEqual(KeyValueCoding.getKeyPath(values, '@sum.a'), 10) 49 self.assertEqual(KeyValueCoding.getKeyPath(values, '@sum.b'), 6) 50 self.assertEqual(KeyValueCoding.getKeyPath(values, '@sum.c'), 0) 51 52 53 def test_avg(self): 54 arrayOperators = KeyValueCoding._ArrayOperators 55 56 values = [ 57 { 'a' : 1 }, 58 { 'a' : 2, 'b': 4 }, 59 { 'a' : 3, 'b': 2 }, 60 { 'a' : 4 }, 61 ] 62 self.assertEqual(arrayOperators.avg(values, 'a'), 2.5) 63 self.assertEqual(arrayOperators.avg(values, 'b'), 1.5) 64 self.assertEqual(arrayOperators.avg(values, 'c'), 0) 65 self.assertEqual(arrayOperators.avg([], 'b'), 0) 66 self.assertRaises(KeyError, arrayOperators.avg, [], ()) 67 68 self.assertEqual(KeyValueCoding.getKeyPath(values, '@avg.a'), 2.5) 69 self.assertEqual(KeyValueCoding.getKeyPath(values, '@avg.b'), 1.5) 70 self.assertEqual(KeyValueCoding.getKeyPath(values, '@avg.c'), 0) 71 72 def test_count(self): 73 arrayOperators = KeyValueCoding._ArrayOperators 74 75 values = [ 76 { 'a' : 1 }, 77 { 'a' : 2, 'b': 4 }, 78 { 'a' : 3, 'b': 2 }, 79 { 'a' : 4 }, 80 ] 81 self.assertEqual(arrayOperators.count(values, 'a'), len(values)) 82 self.assertEqual(arrayOperators.count(values, 'b'), len(values)) 83 self.assertEqual(arrayOperators.count(values, ()), len(values)) 84 self.assertEqual(KeyValueCoding.getKeyPath(values, '@count'), len(values)) 85 self.assertEqual(KeyValueCoding.getKeyPath(values, '@count.a'), len(values)) 86 87 def test_max(self): 88 arrayOperators = KeyValueCoding._ArrayOperators 89 90 values = [ 91 { 'a' : 1 }, 92 { 'a' : 2, 'b': 5 }, 93 { 'a' : 3, 'b': 2 }, 94 { 'a' : 4 }, 95 ] 96 self.assertEqual(arrayOperators.max(values, 'a'), 4) 97 self.assertEqual(arrayOperators.max(values, 'b'), 5) 98 self.assertRaises(KeyError, arrayOperators.max, values, ()) 99 self.assertEqual(KeyValueCoding.getKeyPath(values, '@max.a'), 4) 100 101 def test_min(self): 102 arrayOperators = KeyValueCoding._ArrayOperators 103 104 values = [ 105 { 'a' : 1 }, 106 { 'a' : 2, 'b': 5 }, 107 { 'a' : 3, 'b': 2 }, 108 { 'a' : 4 }, 109 ] 110 self.assertEqual(arrayOperators.min(values, 'a'), 1) 111 self.assertEqual(arrayOperators.min(values, 'b'), 2) 112 self.assertRaises(KeyError, arrayOperators.min, values, ()) 113 self.assertEqual(KeyValueCoding.getKeyPath(values, '@min.a'), 1) 114 115 def test_unionOfObjects(self): 116 arrayOperators = KeyValueCoding._ArrayOperators 117 118 values = [ 119 { 'a' : { 'b': 1 } }, 120 { 'a' : { 'b': 1 } }, 121 { 'a' : { 'b': 2 } }, 122 { 'a' : { 'b': 3 } }, 123 ] 124 125 self.assertEqual(arrayOperators.unionOfObjects(values, ('a', 'b')), [1, 1, 2, 3 ]) 126 self.assertEqual(KeyValueCoding.getKeyPath(values, '@unionOfObjects.a.b'), [1, 1, 2, 3]) 127 128 values.append({'a': {}}) 129 self.assertRaises(KeyError, arrayOperators.unionOfObjects, values, ('a', 'b')) 130 131 def test_distinctUnionOfObjects(self): 132 arrayOperators = KeyValueCoding._ArrayOperators 133 134 class Int (object): 135 def __init__(self, value): 136 self._value = value 137 138 def __repr__(self): 139 return 'Int(%r)'%(self._value) 140 141 def __eq__(self, other): 142 if isinstance(other, int): 143 return self._value == other 144 145 elif isinstance(other, Int): 146 return self._value == other._value 147 148 else: 149 return False 150 151 def __hash__(self): raise TypeError 152 153 values = [ 154 { 'a' : { 'b': 1 } }, 155 { 'a' : { 'b': Int(1) } }, 156 { 'a' : { 'b': 2 } }, 157 { 'a' : { 'b': Int(3) } }, 158 { 'a' : { 'b': Int(3) } }, 159 ] 160 161 self.assertEqual(arrayOperators.distinctUnionOfObjects(values, ('a', 'b')), [1, 2, 3 ]) 162 self.assertEqual(KeyValueCoding.getKeyPath(values, '@distinctUnionOfObjects.a.b'), [1, 2, 3 ]) 163 164 values.append({'a': {}}) 165 self.assertRaises(KeyError, arrayOperators.distinctUnionOfObjects, values, ('a', 'b')) 166 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, values, '@distinctUnionOfObjects.a.b') 167 168 class Rec (object): 169 def __init__(self, b): 170 self.b = b 171 172 def __eq__(self, other): 173 return type(self) == type(other) and self.b == other.b 174 175 def __hash__(self): raise TypeError 176 177 values = [ 178 { 'a' : Rec(1) }, 179 { 'a' : Rec(1) }, 180 { 'a' : Rec(2) }, 181 { 'a' : Rec(3) }, 182 ] 183 self.assertEqual(arrayOperators.distinctUnionOfObjects(values, ('a', 'b')), [1, 2, 3 ]) 184 185 def test_unionOfArrays(self): 186 arrayOperators = KeyValueCoding._ArrayOperators 187 188 class Rec (object): 189 def __init__(self, **kwds): 190 for k, v in kwds.items(): 191 setattr(self, k, v) 192 193 def __eq__(self, other): 194 return type(self) is type(other) and self.__dict__ == other.__dict__ 195 196 def __hash__(self): raise TypeError 197 198 class Str (object): 199 def __init__(self, value): 200 self._value = value 201 202 def __repr__(self): 203 return 'Str(%r)'%(self._value) 204 205 def __eq__(self, other): 206 if isinstance(other, str): 207 return self._value == other 208 209 elif isinstance(other, Str): 210 return self._value == other._value 211 212 else: 213 return False 214 215 def __cmp__(self, other): 216 if isinstance(other, str): 217 return cmp(self._value, other) 218 219 elif isinstance(other, Str): 220 return cmp(self._value, other._value) 221 222 else: 223 return NotImplementedError 224 225 def __hash__(self): raise TypeError 226 227 transactions = [ 228 [ 229 dict(payee='Green Power', amount=120.0), 230 dict(payee='Green Power', amount=150.0), 231 dict(payee=Str('Green Power'), amount=170.0), 232 Rec(payee='Car Loan', amount=250.0), 233 dict(payee='Car Loan', amount=250.0), 234 dict(payee='Car Loan', amount=250.0), 235 dict(payee=Str('General Cable'), amount=120.0), 236 dict(payee='General Cable', amount=155.0), 237 Rec(payee='General Cable', amount=120.0), 238 dict(payee='Mortgage', amount=1250.0), 239 dict(payee='Mortgage', amount=1250.0), 240 dict(payee='Mortgage', amount=1250.0), 241 dict(payee='Animal Hospital', amount=600.0), 242 ], 243 [ 244 dict(payee='General Cable - Cottage', amount=120.0), 245 dict(payee='General Cable - Cottage', amount=155.0), 246 Rec(payee='General Cable - Cottage', amount=120.0), 247 dict(payee='Second Mortgage', amount=1250.0), 248 dict(payee='Second Mortgage', amount=1250.0), 249 dict(payee=Str('Second Mortgage'), amount=1250.0), 250 dict(payee='Hobby Shop', amount=600.0), 251 ] 252 ] 253 254 self.assertEqual(arrayOperators.distinctUnionOfArrays(transactions, ('payee',)), ['Green Power', 'Car Loan', 'General Cable', 'Mortgage', 'Animal Hospital', 'General Cable - Cottage', 'Second Mortgage', 'Hobby Shop']) 255 self.assertEqual(KeyValueCoding.getKeyPath(transactions, '@distinctUnionOfArrays.payee'), ['Green Power', 'Car Loan', 'General Cable', 'Mortgage', 'Animal Hospital', 'General Cable - Cottage', 'Second Mortgage', 'Hobby Shop']) 256 self.assertEqual(arrayOperators.unionOfArrays(transactions, ('payee',)), [ 257 'Green Power', 258 'Green Power', 259 'Green Power', 260 'Car Loan', 261 'Car Loan', 262 'Car Loan', 263 'General Cable', 264 'General Cable', 265 'General Cable', 266 'Mortgage', 267 'Mortgage', 268 'Mortgage', 269 'Animal Hospital', 270 'General Cable - Cottage', 271 'General Cable - Cottage', 272 'General Cable - Cottage', 273 'Second Mortgage', 274 'Second Mortgage', 275 'Second Mortgage', 276 'Hobby Shop' 277 ]) 278 self.assertEqual(KeyValueCoding.getKeyPath(transactions, '@unionOfArrays.payee'), [ 279 'Green Power', 280 'Green Power', 281 'Green Power', 282 'Car Loan', 283 'Car Loan', 284 'Car Loan', 285 'General Cable', 286 'General Cable', 287 'General Cable', 288 'Mortgage', 289 'Mortgage', 290 'Mortgage', 291 'Animal Hospital', 292 'General Cable - Cottage', 293 'General Cable - Cottage', 294 'General Cable - Cottage', 295 'Second Mortgage', 296 'Second Mortgage', 297 'Second Mortgage', 298 'Hobby Shop' 299 ]) 300 301 self.assertRaises(KeyError, arrayOperators.unionOfArrays, transactions, 'date') 302 self.assertRaises(KeyError, arrayOperators.distinctUnionOfArrays, transactions, 'date') 303 304 def testUnionOfSets(self): 305 arrayOperators = KeyValueCoding._ArrayOperators 306 307 class Rec (object): 308 def __init__(self, n): 309 self.n = n 310 311 def __eq__(self, other): 312 return self.n == other.n 313 314 def __hash__(self): 315 return hash(self.n) 316 317 318 values = { 319 frozenset({ 320 Rec(1), 321 Rec(1), 322 Rec(2), 323 }), 324 frozenset({ 325 Rec(1), 326 Rec(3), 327 }), 328 } 329 330 self.assertEqual(arrayOperators.distinctUnionOfSets(values, 'n'), {1,2,3}) 331 332class TestDeprecatedJunk (TestCase): 333 def test_deprecated_class (self): 334 with filterWarnings('error', DeprecationWarning): 335 self.assertRaises(DeprecationWarning, KeyValueCoding.ArrayOperators) 336 337 338 o = KeyValueCoding.ArrayOperators() 339 self.assertIsInstance(o, KeyValueCoding._ArrayOperators) 340 341 def test_deprecated_object (self): 342 with filterWarnings('error', DeprecationWarning): 343 self.assertRaises(DeprecationWarning, getattr, KeyValueCoding.arrayOperators, 'avg') 344 345 self.assertEqual(KeyValueCoding.arrayOperators.avg, KeyValueCoding._ArrayOperators.avg) 346 347 348 349null = objc.lookUpClass('NSNull').null() 350 351class TestPythonObject (TestCase): 352 def test_dict_get(self): 353 d = {'a':1 } 354 self.assertEqual(KeyValueCoding.getKey(d, 'a'), 1) 355 self.assertRaises(KeyError, KeyValueCoding.getKey, d, 'b') 356 357 def test_array_get(self): 358 l = [{'a': 1, 'b':2 }, {'a':2} ] 359 self.assertEqual(KeyValueCoding.getKey(l, 'a'), [1, 2]) 360 self.assertEqual(KeyValueCoding.getKey(l, 'b'), [2, null]) 361 362 def test_attr_get(self): 363 class Record (object): 364 __slots__ = ('slot1', '__dict__') 365 def __init__(self, **kwds): 366 for k, v in kwds.items(): 367 setattr(self, k, v) 368 369 @property 370 def prop1(self): 371 return 'a property' 372 373 r = Record(slot1=42, attr1='a') 374 375 self.assertEqual(KeyValueCoding.getKey(r, 'slot1'), 42) 376 self.assertEqual(KeyValueCoding.getKey(r, 'attr1'), 'a') 377 self.assertEqual(KeyValueCoding.getKey(r, 'prop1'), 'a property') 378 self.assertEqual(KeyValueCoding.getKeyPath(r, 'slot1'), 42) 379 self.assertEqual(KeyValueCoding.getKeyPath(r, 'attr1'), 'a') 380 self.assertEqual(KeyValueCoding.getKeyPath(r, 'prop1'), 'a property') 381 382 r = Record(attr1=Record(attr2='b', attr3=[Record(a=1), Record(a=2, b='b')])) 383 self.assertRaises(KeyError, KeyValueCoding.getKey, r, 'slot1') 384 self.assertRaises(KeyError, KeyValueCoding.getKey, r, 'attr99') 385 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, r, 'slot1') 386 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, r, 'attr99') 387 388 self.assertEqual(KeyValueCoding.getKeyPath(r, 'attr1.attr2'), 'b') 389 self.assertEqual(KeyValueCoding.getKeyPath(r, 'attr1.attr3.a'), [1, 2]) 390 self.assertEqual(KeyValueCoding.getKeyPath(r, 'attr1.attr3.b'), [null, 'b']) 391 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, r, 'attr3') 392 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, r, 'attr1.attr9') 393 394 395 def test_cocoa_get(self): 396 r = objc.lookUpClass('NSObject').alloc().init() 397 self.assertEqual(KeyValueCoding.getKey(r, 'description'), r.description()) 398 self.assertEqual(KeyValueCoding.getKeyPath(r, 'description'), r.description()) 399 self.assertEqual(KeyValueCoding.getKeyPath(r, 'description.length'), len(r.description())) 400 self.assertRaises(KeyError, KeyValueCoding.getKey, r, 'nosuchattr') 401 self.assertRaises(KeyError, KeyValueCoding.getKeyPath, r, 'description.nosuchattr') 402 403 def test_accessor_get(self): 404 class Object (object): 405 def get_attr1(self): 406 return "attr1" 407 408 def getAttr1(self): 409 return "Attr1" 410 411 def attr1(self): 412 return ".attr1" 413 414 def get_attr2(self): 415 return "attr2" 416 417 def attr2(self): 418 return '.attr2' 419 420 def attr3(self): 421 return '.attr3' 422 423 def isAttr4(self): 424 return "attr4?" 425 426 @objc.selector 427 def attrsel(self): 428 return 'selattr' 429 430 r = Object() 431 self.assertEqual(KeyValueCoding.getKey(r, 'attr1'), 'Attr1') 432 self.assertEqual(KeyValueCoding.getKey(r, 'attr2'), 'attr2') 433 self.assertEqual(KeyValueCoding.getKey(r, 'attr3'), '.attr3') 434 self.assertEqual(KeyValueCoding.getKey(r, 'attr4'), 'attr4?') 435 self.assertEqual(KeyValueCoding.getKey(r, 'attrsel'), 'selattr') 436 437 t = Object() 438 o = objc.lookUpClass('NSObject').alloc().init() 439 l = [] 440 441 r.attr5 = t.isAttr4 442 r.attr6 = o.description 443 r.attr7 = l.__len__ 444 r.attr8 = os.getpid 445 r.attr9 = 'attribute 9' 446 447 self.assertEqual(KeyValueCoding.getKey(r, 'attr5'), t.isAttr4) 448 self.assertEqual( KeyValueCoding.getKey(r, 'attr6'), r.attr6) 449 self.assertEqual(KeyValueCoding.getKey(r, 'attr7'), l.__len__) 450 self.assertEqual(KeyValueCoding.getKey(r, 'attr8'), os.getpid()) 451 self.assertEqual(KeyValueCoding.getKey(r, 'attr9'), 'attribute 9') 452 self.assertEqual(KeyValueCoding.getKey(1.5, 'hex'), (1.5).hex()) 453 454 def test_none_get(self): 455 self.assertEqual(KeyValueCoding.getKey(None, 'a'), None) 456 self.assertEqual(KeyValueCoding.getKeyPath(None, 'a'), None) 457 458 def test_none_set(self): 459 # setKey(None, 'any', 'value') is documented as a no-op 460 # check that this doesn't raise an exception. 461 v = None 462 KeyValueCoding.setKey(v, 'a', 42) 463 KeyValueCoding.setKeyPath(v, 'a', 42) 464 465 def test_dict_set(self): 466 v = {'a': 42, 'c':{} } 467 468 KeyValueCoding.setKey(v, 'a', 43) 469 KeyValueCoding.setKey(v, 'b', 'B') 470 self.assertEqual(v, {'a': 43, 'b': 'B', 'c':{} }) 471 472 KeyValueCoding.setKeyPath(v, 'a', 44) 473 KeyValueCoding.setKeyPath(v, 'b', 'C') 474 KeyValueCoding.setKeyPath(v, 'c.a', 'A') 475 self.assertEqual(v, {'a': 44, 'b': 'C', 'c':{'a': 'A'} }) 476 477 def test_attr_set(self): 478 class R (object): 479 @property 480 def attr3(self): 481 return self._attr3 482 483 @attr3.setter 484 def attr3(self, v): 485 self._attr3 = v * 2 486 487 @property 488 def attr4(self): 489 return self._attr4 490 491 def attr6(self): 492 return self._attr6 493 494 r = R() 495 r._attr1 = 42 496 r._attr4 = 43 497 r.attr5 = {} 498 r._attr6 = 9 499 500 KeyValueCoding.setKey(r, 'attr1', 1) 501 KeyValueCoding.setKey(r, 'attr2', 2) 502 KeyValueCoding.setKey(r, 'attr3', 3) 503 self.assertRaises(KeyError, KeyValueCoding.setKey, r, 'attr4', 4) 504 KeyValueCoding.setKey(r, 'attr6', 7) 505 506 self.assertEqual(r._attr1, 1) 507 self.assertEqual(r.attr2, 2) 508 self.assertEqual(r.attr3, 6) 509 self.assertEqual(r._attr3, 6) 510 self.assertEqual(r._attr6, 7) 511 512 KeyValueCoding.setKeyPath(r, 'attr1', 'one') 513 KeyValueCoding.setKeyPath(r, 'attr2', 'two') 514 KeyValueCoding.setKeyPath(r, 'attr3', 'three') 515 KeyValueCoding.setKeyPath(r, 'attr5.sub1', 3) 516 KeyValueCoding.setKeyPath(r, 'attr6', 'seven') 517 518 self.assertEqual(r._attr1, 'one') 519 self.assertEqual(r.attr2, 'two') 520 self.assertEqual(r.attr3, 'threethree') 521 self.assertEqual(r._attr3, 'threethree') 522 self.assertEqual(r.attr5, {'sub1': 3}) 523 self.assertEqual(r._attr6, 'seven') 524 525 def test_cocoa_set(self): 526 o = objc.lookUpClass('NSMutableDictionary').alloc().init() 527 KeyValueCoding.setKey(o, 'attr', 'value') 528 self.assertEqual(o, {'attr': 'value'}) 529 530 KeyValueCoding.setKeyPath(o, 'attr', 'value2') 531 self.assertEqual(o, {'attr': 'value2'}) 532 533 o = objc.lookUpClass('NSObject').alloc().init() 534 self.assertRaises(KeyError, KeyValueCoding.setKey, o, 'description', 'hello') 535 self.assertRaises(KeyError, KeyValueCoding.setKeyPath, o, 'description', 'hello') 536 537 def test_accessor(self): 538 class Record (object): 539 def __init__(self): 540 self._attr1 = 1 541 self._attr2 = 2 542 self._attr3 = 3 543 544 def attr1(self): 545 return self._attr1 546 547 def setAttr1_(self, value): 548 self._attr1 = (1, value) 549 550 def setAttr1(self, value): 551 self._attr1 = (2, value) 552 553 def set_attr1(self, value): 554 self._attr1 = (3, value) 555 556 def setAttr2(self, value): 557 self._attr2 = (2, value) 558 559 def set_attr2(self, value): 560 self._attr2 = (3, value) 561 562 def set_attr3(self, value): 563 self._attr3 = (3, value) 564 565 set_no_attr = 4 566 567 o = Record() 568 self.assertEqual(o._attr1, 1) 569 self.assertEqual(o._attr2, 2) 570 self.assertEqual(o._attr3, 3) 571 self.assertEqual(o.set_no_attr, 4) 572 573 KeyValueCoding.setKey(o, 'attr1', 9) 574 KeyValueCoding.setKey(o, 'attr2', 10) 575 KeyValueCoding.setKey(o, 'attr3', 11) 576 KeyValueCoding.setKey(o, 'no_attr', 12) 577 578 579 self.assertEqual(o._attr1, (1, 9)) 580 self.assertEqual(o._attr2, (2, 10)) 581 self.assertEqual(o._attr3, (3, 11)) 582 self.assertEqual(o.no_attr, 12) 583 584 KeyValueCoding.setKeyPath(o, 'attr1', 29) 585 KeyValueCoding.setKeyPath(o, 'attr2', 210) 586 KeyValueCoding.setKeyPath(o, 'attr3', 211) 587 588 self.assertEqual(o._attr1, (1, 29)) 589 self.assertEqual(o._attr2, (2, 210)) 590 self.assertEqual(o._attr3, (3, 211)) 591 592 o._attr1 = {'a': 'b'} 593 KeyValueCoding.setKeyPath(o, 'attr1.a', 30) 594 self.assertEqual(o._attr1, {'a': 30}) 595 596 597 def testMixedGraph(self): 598 arr = objc.lookUpClass('NSMutableArray').alloc().init() 599 d1 = objc.lookUpClass('NSMutableDictionary').alloc().init() 600 d2 = objc.lookUpClass('NSMutableDictionary').alloc().init() 601 d3 = {} 602 603 root = { 'a': arr, 'd': d2 } 604 605 arr.addObject_(d1) 606 arr.addObject_(d3) 607 d1['k'] = 1 608 d3['k'] = 2 609 610 KeyValueCoding.setKeyPath(root, 'd.a', 'the letter A') 611 self.assertEqual(d2, {'a': 'the letter A', }) 612 613 self.assertEqual(KeyValueCoding.getKeyPath(root, 'd.a'), 'the letter A') 614 self.assertEqual(KeyValueCoding.getKeyPath(arr, 'k'), [1, 2]) 615 self.assertEqual(KeyValueCoding.getKeyPath(root, 'a.k'), [1, 2]) 616 617 def testMixedGraph2(self): 618 arr = objc.lookUpClass('NSMutableArray').alloc().init() 619 d1 = objc.lookUpClass('NSMutableDictionary').alloc().init() 620 d2 = objc.lookUpClass('NSMutableDictionary').alloc().init() 621 d3 = {} 622 623 root = objc.lookUpClass('NSMutableDictionary').dictionaryWithDictionary_({ 'a': arr, 'd': d2 }) 624 625 arr.addObject_(d1) 626 arr.addObject_(d3) 627 d1['k'] = 1 628 d3['k'] = 2 629 630 KeyValueCoding.setKeyPath(root, 'd.a', 'the letter A') 631 self.assertEqual(d2, {'a': 'the letter A', }) 632 633 self.assertEqual(KeyValueCoding.getKeyPath(root, 'd.a'), 'the letter A') 634 self.assertEqual(KeyValueCoding.getKeyPath(arr, 'k'), [1, 2]) 635 self.assertEqual(KeyValueCoding.getKeyPath(root, 'a.k'), [1, 2]) 636 637 638 639class TestKVCHelper (TestCase): 640 def setUp(self): 641 self._orig = { 642 'getKey': KeyValueCoding.getKey, 643 'setKey': KeyValueCoding.setKey, 644 'getKeyPath': KeyValueCoding.getKeyPath, 645 'setKeyPath': KeyValueCoding.setKeyPath, 646 } 647 self._trace = [] 648 def getKey(obj, k): 649 self._trace.append(('get', obj, k)) 650 def setKey(obj, k, v): 651 self._trace.append(('set', obj, k, v)) 652 def getKeyPath(obj, k): 653 self._trace.append(('get-path', obj, k)) 654 def setKeyPath(obj, k, v): 655 self._trace.append(('set-path', obj, k, v)) 656 KeyValueCoding.getKey = getKey 657 KeyValueCoding.setKey = setKey 658 KeyValueCoding.getKeyPath = getKeyPath 659 KeyValueCoding.setKeyPath = setKeyPath 660 661 662 def tearDown(self): 663 for k in self._orig: 664 setattr(KeyValueCoding, k, self._orig[k]) 665 666 def test_repr(self): 667 for o in [ 668 object(), 42, "42", b"42", b"42".decode('latin1') 669 ]: 670 self.assertEqual(repr(KeyValueCoding.kvc(o)), repr(o)) 671 672 def test_attribute_access(self): 673 v = object() 674 o = KeyValueCoding.kvc(v) 675 o.key 676 o.key2 677 getattr(o, "key3.key4") 678 self.assertEqual(self._trace, [ 679 ('get', v, 'key'), 680 ('get', v, 'key2'), 681 ('get', v, 'key3.key4'), 682 ]) 683 self._trace[:] = [] 684 685 o.key = 42 686 setattr(o, "key3.key4", 99) 687 self.assertEqual(self._trace, [ 688 ('set', v, 'key', 42), 689 ('set', v, 'key3.key4', 99), 690 ]) 691 self._trace[:] = [] 692 693 class Object (object): 694 pass 695 696 v = Object() 697 o = KeyValueCoding.kvc(v) 698 o.key = 42 699 o._key = 99 700 self.assertEqual(self._trace, [ 701 ('set', v, 'key', 42), 702 ]) 703 self.assertEqual(o._key, 99) 704 self.assertIn('_key', o.__dict__) 705 self.assertNotIn('key', o.__dict__) 706 707 708 def test_item_access(self): 709 v = object() 710 o = KeyValueCoding.kvc(v) 711 o['key'] 712 o['key2'] 713 o['key3.key4'] 714 self.assertEqual(self._trace, [ 715 ('get-path', v, 'key'), 716 ('get-path', v, 'key2'), 717 ('get-path', v, 'key3.key4'), 718 ]) 719 self._trace[:] = [] 720 721 o["key"] = 42 722 o["key3.key4"] = 99 723 self.assertEqual(self._trace, [ 724 ('set-path', v, 'key', 42), 725 ('set-path', v, 'key3.key4', 99), 726 ]) 727 self._trace[:] = [] 728 729 730 self.assertRaises(TypeError, operator.getitem, o, 42) 731 self.assertRaises(TypeError, operator.setitem, o, 42, 99) 732 733if __name__ == "__main__": 734 main() 735