##// END OF EJS Templates
Merge pull request #8114 from minrk/delay_trait_notifications...
Thomas Kluyver -
r20819:4f935ba4 merge
parent child Browse files
Show More
@@ -69,6 +69,11 class Configurable(HasTraits):
69 69 self.parent = parent
70 70
71 71 config = kwargs.pop('config', None)
72
73 # load kwarg traits, other than config
74 super(Configurable, self).__init__(**kwargs)
75
76 # load config
72 77 if config is not None:
73 78 # We used to deepcopy, but for now we are trying to just save
74 79 # by reference. This *could* have side effects as all components
@@ -81,9 +86,12 class Configurable(HasTraits):
81 86 else:
82 87 # allow _config_default to return something
83 88 self._load_config(self.config)
84 # This should go second so individual keyword arguments override
85 # the values in config.
86 super(Configurable, self).__init__(**kwargs)
89
90 # Ensure explicit kwargs are applied after loading config.
91 # This is usually redundant, but ensures config doesn't override
92 # explicitly assigned values.
93 for key, value in kwargs.items():
94 setattr(self, key, value)
87 95
88 96 #-------------------------------------------------------------------------
89 97 # Static trait notifiations
@@ -130,17 +138,20 class Configurable(HasTraits):
130 138 section_names = self.section_names()
131 139
132 140 my_config = self._find_my_config(cfg)
133 for name, config_value in iteritems(my_config):
134 if name in traits:
135 if isinstance(config_value, LazyConfigValue):
136 # ConfigValue is a wrapper for using append / update on containers
137 # without having to copy the
138 initial = getattr(self, name)
139 config_value = config_value.get_value(initial)
140 # We have to do a deepcopy here if we don't deepcopy the entire
141 # config object. If we don't, a mutable config_value will be
142 # shared by all instances, effectively making it a class attribute.
143 setattr(self, name, deepcopy(config_value))
141
142 # hold trait notifications until after all config has been loaded
143 with self.hold_trait_notifications():
144 for name, config_value in iteritems(my_config):
145 if name in traits:
146 if isinstance(config_value, LazyConfigValue):
147 # ConfigValue is a wrapper for using append / update on containers
148 # without having to copy the initial value
149 initial = getattr(self, name)
150 config_value = config_value.get_value(initial)
151 # We have to do a deepcopy here if we don't deepcopy the entire
152 # config object. If we don't, a mutable config_value will be
153 # shared by all instances, effectively making it a class attribute.
154 setattr(self, name, deepcopy(config_value))
144 155
145 156 def _config_changed(self, name, old, new):
146 157 """Update all the class traits having ``config=True`` as metadata.
@@ -1330,6 +1330,78 def test_pickle_hastraits():
1330 1330 nt.assert_equal(c2.i, c.i)
1331 1331 nt.assert_equal(c2.j, c.j)
1332 1332
1333
1334 def test_hold_trait_notifications():
1335 changes = []
1336 class Test(HasTraits):
1337 a = Integer(0)
1338 def _a_changed(self, name, old, new):
1339 changes.append((old, new))
1340
1341 t = Test()
1342 with t.hold_trait_notifications():
1343 with t.hold_trait_notifications():
1344 t.a = 1
1345 nt.assert_equal(t.a, 1)
1346 nt.assert_equal(changes, [])
1347 t.a = 2
1348 nt.assert_equal(t.a, 2)
1349 with t.hold_trait_notifications():
1350 t.a = 3
1351 nt.assert_equal(t.a, 3)
1352 nt.assert_equal(changes, [])
1353 t.a = 4
1354 nt.assert_equal(t.a, 4)
1355 nt.assert_equal(changes, [])
1356 t.a = 4
1357 nt.assert_equal(t.a, 4)
1358 nt.assert_equal(changes, [])
1359 nt.assert_equal(changes, [(0,1), (1,2), (2,3), (3,4)])
1360
1361
1362 class OrderTraits(HasTraits):
1363 notified = Dict()
1364
1365 a = Unicode()
1366 b = Unicode()
1367 c = Unicode()
1368 d = Unicode()
1369 e = Unicode()
1370 f = Unicode()
1371 g = Unicode()
1372 h = Unicode()
1373 i = Unicode()
1374 j = Unicode()
1375 k = Unicode()
1376 l = Unicode()
1377
1378 def _notify(self, name, old, new):
1379 """check the value of all traits when each trait change is triggered
1380
1381 This verifies that the values are not sensitive
1382 to dict ordering when loaded from kwargs
1383 """
1384 # check the value of the other traits
1385 # when a given trait change notification fires
1386 self.notified[name] = {
1387 c: getattr(self, c) for c in 'abcdefghijkl'
1388 }
1389
1390 def __init__(self, **kwargs):
1391 self.on_trait_change(self._notify)
1392 super(OrderTraits, self).__init__(**kwargs)
1393
1394 def test_notification_order():
1395 d = {c:c for c in 'abcdefghijkl'}
1396 obj = OrderTraits()
1397 nt.assert_equal(obj.notified, {})
1398 obj = OrderTraits(**d)
1399 notifications = {
1400 c: d for c in 'abcdefghijkl'
1401 }
1402 nt.assert_equal(obj.notified, notifications)
1403
1404
1333 1405 class TestEventful(TestCase):
1334 1406
1335 1407 def test_list(self):
@@ -575,8 +575,36 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
575 575 # Allow trait values to be set using keyword arguments.
576 576 # We need to use setattr for this to trigger validation and
577 577 # notifications.
578 for key, value in iteritems(kw):
579 setattr(self, key, value)
578
579 with self.hold_trait_notifications():
580 for key, value in iteritems(kw):
581 setattr(self, key, value)
582
583 @contextlib.contextmanager
584 def hold_trait_notifications(self):
585 """Context manager for bundling trait change notifications
586
587 Use this when doing multiple trait assignments (init, config),
588 to avoid race conditions in trait notifiers requesting other trait values.
589 All trait notifications will fire after all values have been assigned.
590 """
591 _notify_trait = self._notify_trait
592 notifications = []
593 self._notify_trait = lambda *a: notifications.append(a)
594
595 try:
596 yield
597 finally:
598 self._notify_trait = _notify_trait
599 if isinstance(_notify_trait, types.MethodType):
600 # FIXME: remove when support is bumped to 3.4.
601 # when original method is restored,
602 # remove the redundant value from __dict__
603 # (only used to preserve pickleability on Python < 3.4)
604 self.__dict__.pop('_notify_trait', None)
605 # trigger delayed notifications
606 for args in notifications:
607 self._notify_trait(*args)
580 608
581 609 def _notify_trait(self, name, old_value, new_value):
582 610
General Comments 0
You need to be logged in to leave comments. Login now