Repository: ipython/traitlets
Pull request #394 (2017-04-15 16:40:36)

Add link method to link and dlink
The new ``link`` method simply reinstates the link.




Patch:
diff --git a/traitlets/traitlets.py b/traitlets/traitlets.py
index 30d5baf..2874539 100644
--- a/traitlets/traitlets.py
+++ b/traitlets/traitlets.py
@@ -270,11 +270,15 @@ class link(object):
     def __init__(self, source, target):
         _validate_link(source, target)
         self.source, self.target = source, target
+        self.link()
+
+    def link(self):
         try:
-            setattr(target[0], target[1], getattr(source[0], source[1]))
+            setattr(self.target[0], self.target[1],
+                    getattr(self.source[0], self.source[1]))
         finally:
-            source[0].observe(self._update_target, names=source[1])
-            target[0].observe(self._update_source, names=target[1])
+            self.source[0].observe(self._update_target, names=self.source[1])
+            self.target[0].observe(self._update_source, names=self.target[1])
 
     @contextlib.contextmanager
     def _busy_updating(self):
@@ -299,7 +303,6 @@ class link(object):
     def unlink(self):
         self.source[0].unobserve(self._update_target, names=self.source[1])
         self.target[0].unobserve(self._update_source, names=self.target[1])
-        self.source, self.target = None, None
 
 
 class directional_link(object):
@@ -325,9 +328,12 @@ class directional_link(object):
         self._transform = transform if transform else lambda x: x
         _validate_link(source, target)
         self.source, self.target = source, target
+        self.link()
+
+    def link(self):
         try:
-            setattr(target[0], target[1],
-                    self._transform(getattr(source[0], source[1])))
+            setattr(self.target[0], self.target[1],
+                    self._transform(getattr(self.source[0], self.source[1])))
         finally:
             self.source[0].observe(self._update, names=self.source[1])
 
@@ -348,7 +354,6 @@ class directional_link(object):
 
     def unlink(self):
         self.source[0].unobserve(self._update, names=self.source[1])
-        self.source, self.target = None, None
 
 dlink = directional_link
 


Test patch:
diff --git a/traitlets/tests/test_traitlets.py b/traitlets/tests/test_traitlets.py
index b3efaa2..3a81e88 100644
--- a/traitlets/tests/test_traitlets.py
+++ b/traitlets/tests/test_traitlets.py
@@ -3,7 +3,7 @@
 
 # Copyright (c) IPython Development Team.
 # Distributed under the terms of the Modified BSD License.
-# 
+#
 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
 # also under the terms of the Modified BSD License.
 
@@ -521,29 +521,29 @@ class TestHasTraitsNotify(TestCase):
 
         class A(HasTraits):
             listen_to = ['a']
-            
+
             a = Int(0)
             b = 0
-            
+
             def __init__(self, **kwargs):
                 super(A, self).__init__(**kwargs)
                 self.on_trait_change(self.listener1, ['a'])
-            
+
             def listener1(self, name, old, new):
                 self.b += 1
 
         class B(A):
-                    
+
             c = 0
             d = 0
-            
+
             def __init__(self, **kwargs):
                 super(B, self).__init__(**kwargs)
                 self.on_trait_change(self.listener2)
-            
+
             def listener2(self, name, old, new):
                 self.c += 1
-            
+
             def _a_changed(self, name, old, new):
                 self.d += 1
 
@@ -653,7 +653,7 @@ class TestObserveDecorator(TestCase):
             b = Int()
             _notify1 = []
             _notify_any = []
-            
+
             @observe('a')
             def _a_changed(self, change):
                 self._notify1.append(change)
@@ -717,29 +717,29 @@ class TestObserveDecorator(TestCase):
 
         class A(HasTraits):
             listen_to = ['a']
-            
+
             a = Int(0)
             b = 0
-            
+
             def __init__(self, **kwargs):
                 super(A, self).__init__(**kwargs)
                 self.observe(self.listener1, ['a'])
-            
+
             def listener1(self, change):
                 self.b += 1
 
         class B(A):
-                    
+
             c = 0
             d = 0
-            
+
             def __init__(self, **kwargs):
                 super(B, self).__init__(**kwargs)
                 self.observe(self.listener2)
-            
+
             def listener2(self, change):
                 self.c += 1
-            
+
             @observe('a')
             def _a_changed(self, change):
                 self.d += 1
@@ -839,7 +839,7 @@ class TestHasTraits(TestCase):
             def __init__(self, i):
                 super(A, self).__init__()
                 self.i = i
-        
+
         a = A(5)
         self.assertEqual(a.i, 5)
         # should raise TypeError if no positional arg given
@@ -1499,7 +1499,7 @@ class TestList(TraitTestBase):
     _default_value = []
     _good_values = [[], [1], list(range(10)), (1,2)]
     _bad_values = [10, [1,'a'], 'a']
-    
+
     def coerce(self, value):
         if value is not None:
             value = list(value)
@@ -1509,7 +1509,7 @@ class Foo(object):
     pass
 
 class NoneInstanceListTrait(HasTraits):
-    
+
     value = List(Instance(Foo))
 
 class TestNoneInstanceList(TraitTestBase):
@@ -1575,7 +1575,7 @@ class TestTupleTrait(TraitTestBase):
 
     obj = TupleTrait()
 
-    _default_value = (1,) 
+    _default_value = (1,)
     _good_values = [(1,), (0,), [1]]
     _bad_values = [10, (1, 2), ('a'), (), None]
 
@@ -1647,7 +1647,7 @@ def test_dict_assignment():
     c = DictTrait()
     c.value = d
     d['a'] = 5
-    assert d == c.value  
+    assert d == c.value
     assert c.value is d
 
 
@@ -1745,7 +1745,7 @@ class TestValidationHook(TestCase):
                 if self.parity == 'odd' and (value % 2 == 0):
                     raise TraitError('Expected an odd number')
                 return value
-        
+
         u = Parity()
         u.parity = 'odd'
         u.value = 1  # OK
@@ -1827,8 +1827,8 @@ class TestLink(TestCase):
         b.count = 4
         self.assertEqual(a.value, b.count)
 
-    def test_unlink(self):
-        """Verify two linked traitlets can be unlinked."""
+    def test_unlink_link(self):
+        """Verify two linked traitlets can be unlinked and relinked."""
 
         # Create two simple classes with Int traitlets.
         class A(HasTraits):
@@ -1844,6 +1844,10 @@ class TestLink(TestCase):
         # Change one of the values to make sure they don't stay in sync.
         a.value = 5
         self.assertNotEqual(a.value, b.value)
+        c.link()
+        self.assertEqual(a.value, b.value)
+        a.value += 1
+        self.assertEqual(a.value, b.value)
 
     def test_callbacks(self):
         """Verify two linked traitlets have their callbacks called once."""
@@ -1855,7 +1859,7 @@ class TestLink(TestCase):
             count = Int()
         a = A(value=9)
         b = B(count=8)
-        
+
         # Register callbacks that count.
         callback_count = []
         def a_callback(name, old, new):
@@ -1951,8 +1955,8 @@ class TestDirectionalLink(TestCase):
         b.value = 6
         self.assertEqual(a.value, 5)
 
-    def test_unlink(self):
-        """Verify two linked traitlets can be unlinked."""
+    def test_unlink_link(self):
+        """Verify two linked traitlets can be unlinked and relinked."""
 
         # Create two simple classes with Int traitlets.
         class A(HasTraits):
@@ -1968,6 +1972,10 @@ class TestDirectionalLink(TestCase):
         # Change one of the values to make sure they don't stay in sync.
         a.value = 5
         self.assertNotEqual(a.value, b.value)
+        c.link()
+        self.assertEqual(a.value, b.value)
+        a.value += 1
+        self.assertEqual(a.value, b.value)
 
 class Pickleable(HasTraits):
 
@@ -1979,7 +1987,7 @@ class Pickleable(HasTraits):
         return commit['value']
 
     j = Int()
-    
+
     def __init__(self):
         with self.hold_trait_notifications():
             self.i = 1
@@ -2084,7 +2092,7 @@ def test_cache_modification():
 
 class OrderTraits(HasTraits):
     notified = Dict()
-    
+
     a = Unicode()
     b = Unicode()
     c = Unicode()
@@ -2097,10 +2105,10 @@ class OrderTraits(HasTraits):
     j = Unicode()
     k = Unicode()
     l = Unicode()
-    
+
     def _notify(self, name, old, new):
         """check the value of all traits when each trait change is triggered
-        
+
         This verifies that the values are not sensitive
         to dict ordering when loaded from kwargs
         """
@@ -2109,7 +2117,7 @@ class OrderTraits(HasTraits):
         self.notified[name] = {
             c: getattr(self, c) for c in 'abcdefghijkl'
         }
-    
+
     def __init__(self, **kwargs):
         self.on_trait_change(self._notify)
         super(OrderTraits, self).__init__(**kwargs)
@@ -2303,7 +2311,7 @@ def test_default_value_repr():
         n = Integer(0)
         lis = List()
         d = Dict()
-    
+
     assert C.t.default_value_repr() == "'traitlets.HasTraits'"
     assert C.t2.default_value_repr() == "'traitlets.traitlets.HasTraits'"
     assert C.n.default_value_repr() == '0'
@@ -2312,7 +2320,7 @@ def test_default_value_repr():
 
 
 class TransitionalClass(HasTraits):
-    
+
     d = Any()
     @default('d')
     def _d_default(self):
@@ -2320,19 +2328,19 @@ class TransitionalClass(HasTraits):
 
     parent_super = False
     calls_super = Integer(0)
-    
+
     @default('calls_super')
     def _calls_super_default(self):
         return -1
-    
+
     @observe('calls_super')
     @observe_compat
     def _calls_super_changed(self, change):
         self.parent_super = change
-    
+
     parent_override = False
     overrides = Integer(0)
-    
+
     @observe('overrides')
     @observe_compat
     def _overrides_changed(self, change):
@@ -2342,12 +2350,12 @@ class TransitionalClass(HasTraits):
 class SubClass(TransitionalClass):
     def _d_default(self):
         return SubClass
-    
+
     subclass_super = False
     def _calls_super_changed(self, name, old, new):
         self.subclass_super = True
         super(SubClass, self)._calls_super_changed(name, old, new)
-    
+
     subclass_override = False
     def _overrides_changed(self, name, old, new):
         self.subclass_override = True
@@ -2366,7 +2374,7 @@ def test_subclass_compat():
 
 class DefinesHandler(HasTraits):
     parent_called = False
-    
+
     trait = Integer()
     @observe('trait')
     def handler(self, change):
@@ -2375,7 +2383,7 @@ class DefinesHandler(HasTraits):
 
 class OverridesHandler(DefinesHandler):
     child_called = False
-    
+
     @observe('trait')
     def handler(self, change):
         self.child_called = True
@@ -2390,7 +2398,7 @@ def test_subclass_override_observer():
 
 class DoesntRegisterHandler(DefinesHandler):
     child_called = False
-    
+
     def handler(self, change):
         self.child_called = True
 
@@ -2405,7 +2413,7 @@ def test_subclass_override_not_registered():
 
 class AddsHandler(DefinesHandler):
     child_called = False
-    
+
     @observe('trait')
     def child_handler(self, change):
         self.child_called = True
@@ -2462,10 +2470,10 @@ def test_super_args():
         def __init__(self, *args, **kwargs):
             self.super_args = args
             self.super_kwargs = kwargs
-    
+
     class SuperHasTraits(HasTraits, SuperRecorder):
         i = Integer()
-    
+
     obj = SuperHasTraits('a1', 'a2', b=10, i=5, c='x')
     assert obj.i ==  5
     assert not hasattr(obj, 'b')
@@ -2476,7 +2484,7 @@ def test_super_args():
 def test_super_bad_args():
     class SuperHasTraits(HasTraits):
         a = Integer()
-    
+
     if sys.version_info < (3,):
         # Legacy Python, object.__init__ warns itself, instead of raising
         w = ['object.__init__']
@@ -2484,7 +2492,7 @@ def test_super_bad_args():
         w = ["Passing unrecoginized arguments"]
     with expected_warnings(w):
         obj = SuperHasTraits(a=1, b=2)
-    assert obj.a ==  1 
+    assert obj.a ==  1
     assert not hasattr(obj, 'b')