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