Show More
@@ -19,7 +19,8 b' from IPython.utils.traitlets import (' | |||||
19 | HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict, |
|
19 | HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict, | |
20 | Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, |
|
20 | Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError, | |
21 | Undefined, Type, This, Instance, TCPAddress, List, Tuple, |
|
21 | Undefined, Type, This, Instance, TCPAddress, List, Tuple, | |
22 |
ObjectName, DottedObjectName, CRegExp, link, |
|
22 | ObjectName, DottedObjectName, CRegExp, link, directional_link, | |
|
23 | EventfulList, EventfulDict | |||
23 | ) |
|
24 | ) | |
24 | from IPython.utils import py3compat |
|
25 | from IPython.utils import py3compat | |
25 | from IPython.testing.decorators import skipif |
|
26 | from IPython.testing.decorators import skipif | |
@@ -1147,6 +1148,71 b' class TestLink(TestCase):' | |||||
1147 | self.assertEqual(''.join(callback_count), 'ab') |
|
1148 | self.assertEqual(''.join(callback_count), 'ab') | |
1148 | del callback_count[:] |
|
1149 | del callback_count[:] | |
1149 |
|
1150 | |||
|
1151 | class TestDirectionalLink(TestCase): | |||
|
1152 | def test_connect_same(self): | |||
|
1153 | """Verify two traitlets of the same type can be linked together using directional_link.""" | |||
|
1154 | ||||
|
1155 | # Create two simple classes with Int traitlets. | |||
|
1156 | class A(HasTraits): | |||
|
1157 | value = Int() | |||
|
1158 | a = A(value=9) | |||
|
1159 | b = A(value=8) | |||
|
1160 | ||||
|
1161 | # Conenct the two classes. | |||
|
1162 | c = directional_link((a, 'value'), (b, 'value')) | |||
|
1163 | ||||
|
1164 | # Make sure the values are the same at the point of linking. | |||
|
1165 | self.assertEqual(a.value, b.value) | |||
|
1166 | ||||
|
1167 | # Change one the value of the source and check that it synchronizes the target. | |||
|
1168 | a.value = 5 | |||
|
1169 | self.assertEqual(b.value, 5) | |||
|
1170 | # Change one the value of the target and check that it has no impact on the source | |||
|
1171 | b.value = 6 | |||
|
1172 | self.assertEqual(a.value, 5) | |||
|
1173 | ||||
|
1174 | def test_link_different(self): | |||
|
1175 | """Verify two traitlets of different types can be linked together using link.""" | |||
|
1176 | ||||
|
1177 | # Create two simple classes with Int traitlets. | |||
|
1178 | class A(HasTraits): | |||
|
1179 | value = Int() | |||
|
1180 | class B(HasTraits): | |||
|
1181 | count = Int() | |||
|
1182 | a = A(value=9) | |||
|
1183 | b = B(count=8) | |||
|
1184 | ||||
|
1185 | # Conenct the two classes. | |||
|
1186 | c = directional_link((a, 'value'), (b, 'count')) | |||
|
1187 | ||||
|
1188 | # Make sure the values are the same at the point of linking. | |||
|
1189 | self.assertEqual(a.value, b.count) | |||
|
1190 | ||||
|
1191 | # Change one the value of the source and check that it synchronizes the target. | |||
|
1192 | a.value = 5 | |||
|
1193 | self.assertEqual(b.count, 5) | |||
|
1194 | # Change one the value of the target and check that it has no impact on the source | |||
|
1195 | b.value = 6 | |||
|
1196 | self.assertEqual(a.value, 5) | |||
|
1197 | ||||
|
1198 | def test_unlink(self): | |||
|
1199 | """Verify two linked traitlets can be unlinked.""" | |||
|
1200 | ||||
|
1201 | # Create two simple classes with Int traitlets. | |||
|
1202 | class A(HasTraits): | |||
|
1203 | value = Int() | |||
|
1204 | a = A(value=9) | |||
|
1205 | b = A(value=8) | |||
|
1206 | ||||
|
1207 | # Connect the two classes. | |||
|
1208 | c = directional_link((a, 'value'), (b, 'value')) | |||
|
1209 | a.value = 4 | |||
|
1210 | c.unlink() | |||
|
1211 | ||||
|
1212 | # Change one of the values to make sure they don't stay in sync. | |||
|
1213 | a.value = 5 | |||
|
1214 | self.assertNotEqual(a.value, b.value) | |||
|
1215 | ||||
1150 | class Pickleable(HasTraits): |
|
1216 | class Pickleable(HasTraits): | |
1151 | i = Int() |
|
1217 | i = Int() | |
1152 | j = Int() |
|
1218 | j = Int() |
@@ -227,6 +227,61 b' class link(object):' | |||||
227 | (obj,attr) = key |
|
227 | (obj,attr) = key | |
228 | obj.on_trait_change(callback, attr, remove=True) |
|
228 | obj.on_trait_change(callback, attr, remove=True) | |
229 |
|
229 | |||
|
230 | @skip_doctest | |||
|
231 | class directional_link(object): | |||
|
232 | """Link the trait of a source object with traits of target objects. | |||
|
233 | ||||
|
234 | Parameters | |||
|
235 | ---------- | |||
|
236 | source : pair of object, name | |||
|
237 | targets : pairs of objects/attributes | |||
|
238 | ||||
|
239 | Examples | |||
|
240 | -------- | |||
|
241 | ||||
|
242 | >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value')) | |||
|
243 | >>> src.value = 5 # updates target objects | |||
|
244 | >>> tgt1.value = 6 # does not update other objects | |||
|
245 | """ | |||
|
246 | updating = False | |||
|
247 | ||||
|
248 | def __init__(self, source, *targets): | |||
|
249 | self.source = source | |||
|
250 | self.targets = targets | |||
|
251 | ||||
|
252 | # Update current value | |||
|
253 | src_attr_value = getattr(source[0], source[1]) | |||
|
254 | for obj, attr in targets: | |||
|
255 | if getattr(obj, attr) != src_attr_value: | |||
|
256 | setattr(obj, attr, src_attr_value) | |||
|
257 | ||||
|
258 | # Wire | |||
|
259 | self.source[0].on_trait_change(self._update, self.source[1]) | |||
|
260 | ||||
|
261 | @contextlib.contextmanager | |||
|
262 | def _busy_updating(self): | |||
|
263 | self.updating = True | |||
|
264 | try: | |||
|
265 | yield | |||
|
266 | finally: | |||
|
267 | self.updating = False | |||
|
268 | ||||
|
269 | def _update(self, name, old, new): | |||
|
270 | if self.updating: | |||
|
271 | return | |||
|
272 | with self._busy_updating(): | |||
|
273 | for obj, attr in self.targets: | |||
|
274 | setattr(obj, attr, new) | |||
|
275 | ||||
|
276 | def unlink(self): | |||
|
277 | self.source[0].on_trait_change(self._update, self.source[1], remove=True) | |||
|
278 | self.source = None | |||
|
279 | self.targets = [] | |||
|
280 | ||||
|
281 | def dlink(source, *targets): | |||
|
282 | """Shorter helper function returning a directional_link object""" | |||
|
283 | return directional_link(source, *targets) | |||
|
284 | ||||
230 | #----------------------------------------------------------------------------- |
|
285 | #----------------------------------------------------------------------------- | |
231 | # Base TraitType for all traits |
|
286 | # Base TraitType for all traits | |
232 | #----------------------------------------------------------------------------- |
|
287 | #----------------------------------------------------------------------------- |
General Comments 0
You need to be logged in to leave comments.
Login now