Show More
@@ -32,7 +32,7 b' from IPython.utils.traitlets import (' | |||||
32 | HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict, |
|
32 | HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict, | |
33 | Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, |
|
33 | Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, | |
34 | Undefined, Type, This, Instance, TCPAddress, List, Tuple, |
|
34 | Undefined, Type, This, Instance, TCPAddress, List, Tuple, | |
35 | ObjectName, DottedObjectName, CRegExp |
|
35 | ObjectName, DottedObjectName, CRegExp, bind | |
36 | ) |
|
36 | ) | |
37 | from IPython.utils import py3compat |
|
37 | from IPython.utils import py3compat | |
38 | from IPython.testing.decorators import skipif |
|
38 | from IPython.testing.decorators import skipif | |
@@ -973,3 +973,103 b' def test_dict_assignment():' | |||||
973 | d['a'] = 5 |
|
973 | d['a'] = 5 | |
974 | nt.assert_equal(d, c.value) |
|
974 | nt.assert_equal(d, c.value) | |
975 | nt.assert_true(c.value is d) |
|
975 | nt.assert_true(c.value is d) | |
|
976 | ||||
|
977 | class TestBind(TestCase): | |||
|
978 | def test_connect_same(self): | |||
|
979 | """Verify two traitlets of the same type can be bound together using bind.""" | |||
|
980 | ||||
|
981 | # Create two simple classes with Int traitlets. | |||
|
982 | class A(HasTraits): | |||
|
983 | value = Int() | |||
|
984 | a = A(value=9) | |||
|
985 | b = A(value=8) | |||
|
986 | ||||
|
987 | # Conenct the two classes. | |||
|
988 | c = bind((a, 'value'), (b, 'value')) | |||
|
989 | ||||
|
990 | # Make sure the values are the same at the point of binding. | |||
|
991 | self.assertEqual(a.value, b.value) | |||
|
992 | ||||
|
993 | # Change one of the values to make sure they stay in sync. | |||
|
994 | a.value = 5 | |||
|
995 | self.assertEqual(a.value, b.value) | |||
|
996 | b.value = 6 | |||
|
997 | self.assertEqual(a.value, b.value) | |||
|
998 | ||||
|
999 | def test_bind_different(self): | |||
|
1000 | """Verify two traitlets of different types can be bound together using bind.""" | |||
|
1001 | ||||
|
1002 | # Create two simple classes with Int traitlets. | |||
|
1003 | class A(HasTraits): | |||
|
1004 | value = Int() | |||
|
1005 | class B(HasTraits): | |||
|
1006 | count = Int() | |||
|
1007 | a = A(value=9) | |||
|
1008 | b = B(count=8) | |||
|
1009 | ||||
|
1010 | # Conenct the two classes. | |||
|
1011 | c = bind((a, 'value'), (b, 'count')) | |||
|
1012 | ||||
|
1013 | # Make sure the values are the same at the point of binding. | |||
|
1014 | self.assertEqual(a.value, b.count) | |||
|
1015 | ||||
|
1016 | # Change one of the values to make sure they stay in sync. | |||
|
1017 | a.value = 5 | |||
|
1018 | self.assertEqual(a.value, b.count) | |||
|
1019 | b.count = 4 | |||
|
1020 | self.assertEqual(a.value, b.count) | |||
|
1021 | ||||
|
1022 | def test_unbind(self): | |||
|
1023 | """Verify two binded traitlets can be unbinded.""" | |||
|
1024 | ||||
|
1025 | # Create two simple classes with Int traitlets. | |||
|
1026 | class A(HasTraits): | |||
|
1027 | value = Int() | |||
|
1028 | a = A(value=9) | |||
|
1029 | b = A(value=8) | |||
|
1030 | ||||
|
1031 | # Conenct the two classes. | |||
|
1032 | c = bind((a, 'value'), (b, 'value')) | |||
|
1033 | a.value = 4 | |||
|
1034 | c.unbind() | |||
|
1035 | ||||
|
1036 | # Change one of the values to make sure they stay in sync. | |||
|
1037 | a.value = 5 | |||
|
1038 | self.assertNotEqual(a.value, b.value) | |||
|
1039 | ||||
|
1040 | def test_callbacks(self): | |||
|
1041 | """Verify two binded traitlets have their callbacks called once.""" | |||
|
1042 | ||||
|
1043 | # Create two simple classes with Int traitlets. | |||
|
1044 | class A(HasTraits): | |||
|
1045 | value = Int() | |||
|
1046 | class B(HasTraits): | |||
|
1047 | count = Int() | |||
|
1048 | a = A(value=9) | |||
|
1049 | b = B(count=8) | |||
|
1050 | ||||
|
1051 | # Register callbacks that count. | |||
|
1052 | callback_count = [] | |||
|
1053 | def a_callback(name, old, new): | |||
|
1054 | callback_count.append('a') | |||
|
1055 | a.on_trait_change(a_callback, 'value') | |||
|
1056 | def b_callback(name, old, new): | |||
|
1057 | callback_count.append('b') | |||
|
1058 | b.on_trait_change(b_callback, 'count') | |||
|
1059 | ||||
|
1060 | # Conenct the two classes. | |||
|
1061 | c = bind((a, 'value'), (b, 'count')) | |||
|
1062 | ||||
|
1063 | # Make sure b's count was set to a's value once. | |||
|
1064 | self.assertEqual(''.join(callback_count), 'b') | |||
|
1065 | del callback_count[:] | |||
|
1066 | ||||
|
1067 | # Make sure a's value was set to b's count once. | |||
|
1068 | b.count = 5 | |||
|
1069 | self.assertEqual(''.join(callback_count), 'ba') | |||
|
1070 | del callback_count[:] | |||
|
1071 | ||||
|
1072 | # Make sure b's count was set to a's value once. | |||
|
1073 | a.value = 4 | |||
|
1074 | self.assertEqual(''.join(callback_count), 'ab') | |||
|
1075 | del callback_count[:] |
@@ -52,7 +52,7 b' Authors:' | |||||
52 | # Imports |
|
52 | # Imports | |
53 | #----------------------------------------------------------------------------- |
|
53 | #----------------------------------------------------------------------------- | |
54 |
|
54 | |||
55 |
|
55 | import contextlib | ||
56 | import inspect |
|
56 | import inspect | |
57 | import re |
|
57 | import re | |
58 | import sys |
|
58 | import sys | |
@@ -67,6 +67,7 b' except:' | |||||
67 | from .importstring import import_item |
|
67 | from .importstring import import_item | |
68 | from IPython.utils import py3compat |
|
68 | from IPython.utils import py3compat | |
69 | from IPython.utils.py3compat import iteritems |
|
69 | from IPython.utils.py3compat import iteritems | |
|
70 | from IPython.testing.skipdoctest import skip_doctest | |||
70 |
|
71 | |||
71 | SequenceTypes = (list, tuple, set, frozenset) |
|
72 | SequenceTypes = (list, tuple, set, frozenset) | |
72 |
|
73 | |||
@@ -182,6 +183,60 b' def getmembers(object, predicate=None):' | |||||
182 | results.sort() |
|
183 | results.sort() | |
183 | return results |
|
184 | return results | |
184 |
|
185 | |||
|
186 | @skip_doctest | |||
|
187 | class bind(object): | |||
|
188 | """Bind traits from different objects together so they remain in sync. | |||
|
189 | ||||
|
190 | Parameters | |||
|
191 | ---------- | |||
|
192 | obj : pairs of objects/attributes | |||
|
193 | ||||
|
194 | Examples | |||
|
195 | -------- | |||
|
196 | ||||
|
197 | >>> c = bind((obj1, 'value'), (obj2, 'value'), (obj3, 'value')) | |||
|
198 | >>> obj1.value = 5 # updates other objects as well | |||
|
199 | """ | |||
|
200 | updating = False | |||
|
201 | def __init__(self, *args): | |||
|
202 | if len(args) < 2: | |||
|
203 | raise TypeError('At least two traitlets must be provided.') | |||
|
204 | ||||
|
205 | self.objects = {} | |||
|
206 | initial = getattr(args[0][0], args[0][1]) | |||
|
207 | for obj,attr in args: | |||
|
208 | if getattr(obj, attr) != initial: | |||
|
209 | setattr(obj, attr, initial) | |||
|
210 | ||||
|
211 | callback = self._make_closure(obj,attr) | |||
|
212 | obj.on_trait_change(callback, attr) | |||
|
213 | self.objects[(obj,attr)] = callback | |||
|
214 | ||||
|
215 | @contextlib.contextmanager | |||
|
216 | def _busy_updating(self): | |||
|
217 | self.updating = True | |||
|
218 | try: | |||
|
219 | yield | |||
|
220 | finally: | |||
|
221 | self.updating = False | |||
|
222 | ||||
|
223 | def _make_closure(self, sending_obj, sending_attr): | |||
|
224 | def update(name, old, new): | |||
|
225 | self._update(sending_obj, sending_attr, new) | |||
|
226 | return update | |||
|
227 | ||||
|
228 | def _update(self, sending_obj, sending_attr, new): | |||
|
229 | if self.updating: | |||
|
230 | return | |||
|
231 | with self._busy_updating(): | |||
|
232 | for obj,attr in self.objects.keys(): | |||
|
233 | if obj is not sending_obj or attr != sending_attr: | |||
|
234 | setattr(obj, attr, new) | |||
|
235 | ||||
|
236 | def unbind(self): | |||
|
237 | for key, callback in self.objects.items(): | |||
|
238 | (obj,attr) = key | |||
|
239 | obj.on_trait_change(callback, attr, remove=True) | |||
185 |
|
240 | |||
186 | #----------------------------------------------------------------------------- |
|
241 | #----------------------------------------------------------------------------- | |
187 | # Base TraitType for all traits |
|
242 | # Base TraitType for all traits |
General Comments 0
You need to be logged in to leave comments.
Login now