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