##// END OF EJS Templates
Merge pull request #4901 from jdfreder/jason_traitlets...
Brian E. Granger -
r15019:7dbba6e9 merge
parent child Browse files
Show More
@@ -32,7 +32,7 b' from IPython.utils.traitlets import ('
32 32 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
33 33 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
34 34 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
35 ObjectName, DottedObjectName, CRegExp
35 ObjectName, DottedObjectName, CRegExp, bind
36 36 )
37 37 from IPython.utils import py3compat
38 38 from IPython.testing.decorators import skipif
@@ -973,3 +973,103 b' def test_dict_assignment():'
973 973 d['a'] = 5
974 974 nt.assert_equal(d, c.value)
975 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 52 # Imports
53 53 #-----------------------------------------------------------------------------
54 54
55
55 import contextlib
56 56 import inspect
57 57 import re
58 58 import sys
@@ -67,6 +67,7 b' except:'
67 67 from .importstring import import_item
68 68 from IPython.utils import py3compat
69 69 from IPython.utils.py3compat import iteritems
70 from IPython.testing.skipdoctest import skip_doctest
70 71
71 72 SequenceTypes = (list, tuple, set, frozenset)
72 73
@@ -182,6 +183,60 b' def getmembers(object, predicate=None):'
182 183 results.sort()
183 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 242 # Base TraitType for all traits
General Comments 0
You need to be logged in to leave comments. Login now