##// 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 b' class Configurable(HasTraits):'
69 self.parent = parent
69 self.parent = parent
70
70
71 config = kwargs.pop('config', None)
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 if config is not None:
77 if config is not None:
73 # We used to deepcopy, but for now we are trying to just save
78 # We used to deepcopy, but for now we are trying to just save
74 # by reference. This *could* have side effects as all components
79 # by reference. This *could* have side effects as all components
@@ -81,9 +86,12 b' class Configurable(HasTraits):'
81 else:
86 else:
82 # allow _config_default to return something
87 # allow _config_default to return something
83 self._load_config(self.config)
88 self._load_config(self.config)
84 # This should go second so individual keyword arguments override
89
85 # the values in config.
90 # Ensure explicit kwargs are applied after loading config.
86 super(Configurable, self).__init__(**kwargs)
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 # Static trait notifiations
97 # Static trait notifiations
@@ -130,17 +138,20 b' class Configurable(HasTraits):'
130 section_names = self.section_names()
138 section_names = self.section_names()
131
139
132 my_config = self._find_my_config(cfg)
140 my_config = self._find_my_config(cfg)
133 for name, config_value in iteritems(my_config):
141
134 if name in traits:
142 # hold trait notifications until after all config has been loaded
135 if isinstance(config_value, LazyConfigValue):
143 with self.hold_trait_notifications():
136 # ConfigValue is a wrapper for using append / update on containers
144 for name, config_value in iteritems(my_config):
137 # without having to copy the
145 if name in traits:
138 initial = getattr(self, name)
146 if isinstance(config_value, LazyConfigValue):
139 config_value = config_value.get_value(initial)
147 # ConfigValue is a wrapper for using append / update on containers
140 # We have to do a deepcopy here if we don't deepcopy the entire
148 # without having to copy the initial value
141 # config object. If we don't, a mutable config_value will be
149 initial = getattr(self, name)
142 # shared by all instances, effectively making it a class attribute.
150 config_value = config_value.get_value(initial)
143 setattr(self, name, deepcopy(config_value))
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 def _config_changed(self, name, old, new):
156 def _config_changed(self, name, old, new):
146 """Update all the class traits having ``config=True`` as metadata.
157 """Update all the class traits having ``config=True`` as metadata.
@@ -1330,6 +1330,78 b' def test_pickle_hastraits():'
1330 nt.assert_equal(c2.i, c.i)
1330 nt.assert_equal(c2.i, c.i)
1331 nt.assert_equal(c2.j, c.j)
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 class TestEventful(TestCase):
1405 class TestEventful(TestCase):
1334
1406
1335 def test_list(self):
1407 def test_list(self):
@@ -575,8 +575,36 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):'
575 # Allow trait values to be set using keyword arguments.
575 # Allow trait values to be set using keyword arguments.
576 # We need to use setattr for this to trigger validation and
576 # We need to use setattr for this to trigger validation and
577 # notifications.
577 # notifications.
578 for key, value in iteritems(kw):
578
579 setattr(self, key, value)
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 def _notify_trait(self, name, old_value, new_value):
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