Show More
@@ -25,7 +25,7 b' from IPython.core import release, crashhandler' | |||
|
25 | 25 | from IPython.core.profiledir import ProfileDir, ProfileDirError |
|
26 | 26 | from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists |
|
27 | 27 | from IPython.utils import py3compat |
|
28 | from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance | |
|
28 | from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance, Undefined | |
|
29 | 29 | |
|
30 | 30 | if os.name == 'nt': |
|
31 | 31 | programdata = os.environ.get('PROGRAMDATA', None) |
@@ -215,7 +215,7 b' class BaseIPythonApplication(Application):' | |||
|
215 | 215 | return crashhandler.crash_handler_lite(etype, evalue, tb) |
|
216 | 216 | |
|
217 | 217 | def _ipython_dir_changed(self, name, old, new): |
|
218 | if old is not None: | |
|
218 | if old is not None and old is not Undefined: | |
|
219 | 219 | str_old = py3compat.cast_bytes_py2(os.path.abspath(old), |
|
220 | 220 | sys.getfilesystemencoding() |
|
221 | 221 | ) |
@@ -1,4 +1,4 b'' | |||
|
1 |
"""Float class. |
|
|
1 | """Float class. | |
|
2 | 2 | |
|
3 | 3 | Represents an unbounded float using a widget. |
|
4 | 4 | """ |
@@ -15,7 +15,8 b' Represents an unbounded float using a widget.' | |||
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from .widget import DOMWidget, register |
|
17 | 17 | from .trait_types import Color |
|
18 |
from IPython.utils.traitlets import Unicode, CFloat, Bool, CaselessStrEnum, |
|
|
18 | from IPython.utils.traitlets import (Unicode, CFloat, Bool, CaselessStrEnum, | |
|
19 | Tuple, TraitError) | |
|
19 | 20 | from IPython.utils.warn import DeprecatedClass |
|
20 | 21 | |
|
21 | 22 | #----------------------------------------------------------------------------- |
@@ -31,39 +32,37 b' class _Float(DOMWidget):' | |||
|
31 | 32 | kwargs['value'] = value |
|
32 | 33 | super(_Float, self).__init__(**kwargs) |
|
33 | 34 | |
|
35 | ||
|
34 | 36 | class _BoundedFloat(_Float): |
|
35 | 37 | max = CFloat(100.0, help="Max value", sync=True) |
|
36 | 38 | min = CFloat(0.0, help="Min value", sync=True) |
|
37 |
step = CFloat(0.1, help="Minimum step t |
|
|
39 | step = CFloat(0.1, help="Minimum step to increment the value (ignored by some views)", sync=True) | |
|
38 | 40 | |
|
39 | 41 | def __init__(self, *pargs, **kwargs): |
|
40 | 42 | """Constructor""" |
|
41 | 43 | super(_BoundedFloat, self).__init__(*pargs, **kwargs) |
|
42 | self._handle_value_changed('value', None, self.value) | |
|
43 | self._handle_max_changed('max', None, self.max) | |
|
44 | self._handle_min_changed('min', None, self.min) | |
|
45 | self.on_trait_change(self._handle_value_changed, 'value') | |
|
46 | self.on_trait_change(self._handle_max_changed, 'max') | |
|
47 | self.on_trait_change(self._handle_min_changed, 'min') | |
|
48 | 44 | |
|
49 |
def _ |
|
|
50 |
""" |
|
|
51 |
if self.min > |
|
|
52 |
|
|
|
45 | def _value_validate(self, value, trait): | |
|
46 | """Cap and floor value""" | |
|
47 | if self.min > value or self.max < value: | |
|
48 | value = min(max(value, self.min), self.max) | |
|
49 | return value | |
|
53 | 50 | |
|
54 | def _handle_max_changed(self, name, old, new): | |
|
55 |
""" |
|
|
56 |
if n |
|
|
57 |
raise |
|
|
58 |
if n |
|
|
59 |
self.value = n |
|
|
51 | def _min_validate(self, min, trait): | |
|
52 | """Enforce min <= value <= max""" | |
|
53 | if min > self.max: | |
|
54 | raise TraitError("Setting min > max") | |
|
55 | if min > self.value: | |
|
56 | self.value = min | |
|
57 | return min | |
|
60 | 58 | |
|
61 | def _handle_min_changed(self, name, old, new): | |
|
62 | """Make sure the max is always >= the min.""" | |
|
63 |
if |
|
|
64 |
raise |
|
|
65 |
if |
|
|
66 |
self.value = |
|
|
59 | def _max_validate(self, max, trait): | |
|
60 | """Enforce min <= value <= max""" | |
|
61 | if max < self.min: | |
|
62 | raise TraitError("setting max < min") | |
|
63 | if max < self.value: | |
|
64 | self.value = max | |
|
65 | return max | |
|
67 | 66 | |
|
68 | 67 | |
|
69 | 68 | @register('IPython.FloatText') |
@@ -76,9 +75,9 b' class FloatText(_Float):' | |||
|
76 | 75 | value : float |
|
77 | 76 | value displayed |
|
78 | 77 | description : str |
|
79 |
description displayed next to the textbox |
|
|
78 | description displayed next to the text box | |
|
80 | 79 | color : str Unicode color code (eg. '#C13535'), optional |
|
81 |
color of the value displayed |
|
|
80 | color of the value displayed | |
|
82 | 81 | """ |
|
83 | 82 | _view_name = Unicode('FloatTextView', sync=True) |
|
84 | 83 |
@@ -1,4 +1,4 b'' | |||
|
1 |
"""Int class. |
|
|
1 | """Int class. | |
|
2 | 2 | |
|
3 | 3 | Represents an unbounded int using a widget. |
|
4 | 4 | """ |
@@ -15,7 +15,8 b' Represents an unbounded int using a widget.' | |||
|
15 | 15 | #----------------------------------------------------------------------------- |
|
16 | 16 | from .widget import DOMWidget, register |
|
17 | 17 | from .trait_types import Color |
|
18 |
from IPython.utils.traitlets import Unicode, CInt, Bool, CaselessStrEnum, |
|
|
18 | from IPython.utils.traitlets import (Unicode, CInt, Bool, CaselessStrEnum, | |
|
19 | Tuple, TraitError) | |
|
19 | 20 | from IPython.utils.warn import DeprecatedClass |
|
20 | 21 | |
|
21 | 22 | #----------------------------------------------------------------------------- |
@@ -32,41 +33,39 b' class _Int(DOMWidget):' | |||
|
32 | 33 | kwargs['value'] = value |
|
33 | 34 | super(_Int, self).__init__(**kwargs) |
|
34 | 35 | |
|
36 | ||
|
35 | 37 | class _BoundedInt(_Int): |
|
36 | 38 | """Base class used to create widgets that represent a int that is bounded |
|
37 | 39 | by a minium and maximum.""" |
|
38 |
step = CInt(1, help="Minimum step t |
|
|
40 | step = CInt(1, help="Minimum step to increment the value (ignored by some views)", sync=True) | |
|
39 | 41 | max = CInt(100, help="Max value", sync=True) |
|
40 | 42 | min = CInt(0, help="Min value", sync=True) |
|
41 | 43 | |
|
42 | 44 | def __init__(self, *pargs, **kwargs): |
|
43 | 45 | """Constructor""" |
|
44 | 46 | super(_BoundedInt, self).__init__(*pargs, **kwargs) |
|
45 | self._handle_value_changed('value', None, self.value) | |
|
46 | self._handle_max_changed('max', None, self.max) | |
|
47 | self._handle_min_changed('min', None, self.min) | |
|
48 | self.on_trait_change(self._handle_value_changed, 'value') | |
|
49 | self.on_trait_change(self._handle_max_changed, 'max') | |
|
50 | self.on_trait_change(self._handle_min_changed, 'min') | |
|
51 | ||
|
52 | def _handle_value_changed(self, name, old, new): | |
|
53 |
""" |
|
|
54 |
if |
|
|
55 | self.value = min(max(new, self.min), self.max) | |
|
56 | ||
|
57 | def _handle_max_changed(self, name, old, new): | |
|
58 | """Make sure the min is always <= the max.""" | |
|
59 | if new < self.min: | |
|
60 | raise ValueError("setting max < min") | |
|
61 | if new < self.value: | |
|
62 |
|
|
|
63 | ||
|
64 | def _handle_min_changed(self, name, old, new): | |
|
65 | """Make sure the max is always >= the min.""" | |
|
66 | if new > self.max: | |
|
67 | raise ValueError("setting min > max") | |
|
68 | if new > self.value: | |
|
69 | self.value = new | |
|
47 | ||
|
48 | def _value_validate(self, value, trait): | |
|
49 | """Cap and floor value""" | |
|
50 | if self.min > value or self.max < value: | |
|
51 | value = min(max(value, self.min), self.max) | |
|
52 | return value | |
|
53 | ||
|
54 | def _min_validate(self, min, trait): | |
|
55 | """Enforce min <= value <= max""" | |
|
56 | if min > self.max: | |
|
57 | raise TraitError("Setting min > max") | |
|
58 | if min > self.value: | |
|
59 | self.value = min | |
|
60 | return min | |
|
61 | ||
|
62 | def _max_validate(self, max, trait): | |
|
63 | """Enforce min <= value <= max""" | |
|
64 | if max < self.min: | |
|
65 | raise TraitError("setting max < min") | |
|
66 | if max < self.value: | |
|
67 | self.value = max | |
|
68 | return max | |
|
70 | 69 | |
|
71 | 70 | @register('IPython.IntText') |
|
72 | 71 | class IntText(_Int): |
@@ -1333,11 +1333,20 b' def test_pickle_hastraits():' | |||
|
1333 | 1333 | |
|
1334 | 1334 | def test_hold_trait_notifications(): |
|
1335 | 1335 | changes = [] |
|
1336 | ||
|
1336 | 1337 | class Test(HasTraits): |
|
1337 | 1338 | a = Integer(0) |
|
1339 | b = Integer(0) | |
|
1340 | ||
|
1338 | 1341 | def _a_changed(self, name, old, new): |
|
1339 | 1342 | changes.append((old, new)) |
|
1340 | ||
|
1343 | ||
|
1344 | def _b_validate(self, value, trait): | |
|
1345 | if value != 0: | |
|
1346 | raise TraitError('Only 0 is a valid value') | |
|
1347 | return value | |
|
1348 | ||
|
1349 | # Test context manager and nesting | |
|
1341 | 1350 | t = Test() |
|
1342 | 1351 | with t.hold_trait_notifications(): |
|
1343 | 1352 | with t.hold_trait_notifications(): |
@@ -1356,8 +1365,16 b' def test_hold_trait_notifications():' | |||
|
1356 | 1365 | t.a = 4 |
|
1357 | 1366 | nt.assert_equal(t.a, 4) |
|
1358 | 1367 | nt.assert_equal(changes, []) |
|
1359 | nt.assert_equal(changes, [(0,1), (1,2), (2,3), (3,4)]) | |
|
1360 | 1368 | |
|
1369 | nt.assert_equal(changes, [(3,4)]) | |
|
1370 | # Test roll-back | |
|
1371 | try: | |
|
1372 | with t.hold_trait_notifications(): | |
|
1373 | t.b = 1 # raises a Trait error | |
|
1374 | except: | |
|
1375 | pass | |
|
1376 | nt.assert_equal(t.b, 0) | |
|
1377 | ||
|
1361 | 1378 | |
|
1362 | 1379 | class OrderTraits(HasTraits): |
|
1363 | 1380 | notified = Dict() |
@@ -319,7 +319,6 b' class TraitType(object):' | |||
|
319 | 319 | accept superclasses for :class:`This` values. |
|
320 | 320 | """ |
|
321 | 321 | |
|
322 | ||
|
323 | 322 | metadata = {} |
|
324 | 323 | default_value = Undefined |
|
325 | 324 | allow_none = False |
@@ -447,7 +446,7 b' class TraitType(object):' | |||
|
447 | 446 | try: |
|
448 | 447 | old_value = obj._trait_values[self.name] |
|
449 | 448 | except KeyError: |
|
450 |
old_value = |
|
|
449 | old_value = Undefined | |
|
451 | 450 | |
|
452 | 451 | obj._trait_values[self.name] = new_value |
|
453 | 452 | try: |
@@ -465,13 +464,14 b' class TraitType(object):' | |||
|
465 | 464 | return value |
|
466 | 465 | if hasattr(self, 'validate'): |
|
467 | 466 | value = self.validate(obj, value) |
|
468 | try: | |
|
469 |
|
|
|
470 | except (AttributeError, RuntimeError): | |
|
471 | # Qt mixins raise RuntimeError on missing attrs accessed before __init__ | |
|
472 | pass | |
|
473 | else: | |
|
474 | value = obj_validate(value, self) | |
|
467 | if obj._cross_validation_lock is False: | |
|
468 | value = self._cross_validate(obj, value) | |
|
469 | return value | |
|
470 | ||
|
471 | def _cross_validate(self, obj, value): | |
|
472 | if hasattr(obj, '_%s_validate' % self.name): | |
|
473 | cross_validate = getattr(obj, '_%s_validate' % self.name) | |
|
474 | value = cross_validate(value, self) | |
|
475 | 475 | return value |
|
476 | 476 | |
|
477 | 477 | def __or__(self, other): |
@@ -542,6 +542,7 b' class MetaHasTraits(type):' | |||
|
542 | 542 | v.this_class = cls |
|
543 | 543 | super(MetaHasTraits, cls).__init__(name, bases, classdict) |
|
544 | 544 | |
|
545 | ||
|
545 | 546 | class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): |
|
546 | 547 | |
|
547 | 548 | def __new__(cls, *args, **kw): |
@@ -555,6 +556,7 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):' | |||
|
555 | 556 | inst._trait_values = {} |
|
556 | 557 | inst._trait_notifiers = {} |
|
557 | 558 | inst._trait_dyn_inits = {} |
|
559 | inst._cross_validation_lock = True | |
|
558 | 560 | # Here we tell all the TraitType instances to set their default |
|
559 | 561 | # values on the instance. |
|
560 | 562 | for key in dir(cls): |
@@ -570,43 +572,74 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):' | |||
|
570 | 572 | value.instance_init() |
|
571 | 573 | if key not in kw: |
|
572 | 574 | value.set_default_value(inst) |
|
573 | ||
|
575 | inst._cross_validation_lock = False | |
|
574 | 576 | return inst |
|
575 | 577 | |
|
576 | 578 | def __init__(self, *args, **kw): |
|
577 | 579 | # Allow trait values to be set using keyword arguments. |
|
578 | 580 | # We need to use setattr for this to trigger validation and |
|
579 | 581 | # notifications. |
|
580 | ||
|
581 | 582 | with self.hold_trait_notifications(): |
|
582 | 583 | for key, value in iteritems(kw): |
|
583 | 584 | setattr(self, key, value) |
|
584 | ||
|
585 | ||
|
585 | 586 | @contextlib.contextmanager |
|
586 | 587 | def hold_trait_notifications(self): |
|
587 | """Context manager for bundling trait change notifications | |
|
588 | ||
|
589 | Use this when doing multiple trait assignments (init, config), | |
|
590 | to avoid race conditions in trait notifiers requesting other trait values. | |
|
588 | """Context manager for bundling trait change notifications and cross | |
|
589 | validation. | |
|
590 | ||
|
591 | Use this when doing multiple trait assignments (init, config), to avoid | |
|
592 | race conditions in trait notifiers requesting other trait values. | |
|
591 | 593 | All trait notifications will fire after all values have been assigned. |
|
592 | 594 | """ |
|
593 | _notify_trait = self._notify_trait | |
|
594 | notifications = [] | |
|
595 | self._notify_trait = lambda *a: notifications.append(a) | |
|
596 | ||
|
597 | try: | |
|
595 | if self._cross_validation_lock is True: | |
|
598 | 596 | yield |
|
599 | finally: | |
|
600 | self._notify_trait = _notify_trait | |
|
601 | if isinstance(_notify_trait, types.MethodType): | |
|
602 | # FIXME: remove when support is bumped to 3.4. | |
|
603 | # when original method is restored, | |
|
604 | # remove the redundant value from __dict__ | |
|
605 | # (only used to preserve pickleability on Python < 3.4) | |
|
606 | self.__dict__.pop('_notify_trait', None) | |
|
607 | # trigger delayed notifications | |
|
608 | for args in notifications: | |
|
609 |
|
|
|
597 | return | |
|
598 | else: | |
|
599 | self._cross_validation_lock = True | |
|
600 | cache = {} | |
|
601 | notifications = {} | |
|
602 | _notify_trait = self._notify_trait | |
|
603 | ||
|
604 | def cache_values(*a): | |
|
605 | cache[a[0]] = a | |
|
606 | ||
|
607 | def hold_notifications(*a): | |
|
608 | notifications[a[0]] = a | |
|
609 | ||
|
610 | self._notify_trait = cache_values | |
|
611 | ||
|
612 | try: | |
|
613 | yield | |
|
614 | finally: | |
|
615 | try: | |
|
616 | self._notify_trait = hold_notifications | |
|
617 | for name in cache: | |
|
618 | if hasattr(self, '_%s_validate' % name): | |
|
619 | cross_validate = getattr(self, '_%s_validate' % name) | |
|
620 | setattr(self, name, cross_validate(getattr(self, name), self)) | |
|
621 | except TraitError as e: | |
|
622 | self._notify_trait = lambda *x: None | |
|
623 | for name in cache: | |
|
624 | if cache[name][1] is not Undefined: | |
|
625 | setattr(self, name, cache[name][1]) | |
|
626 | else: | |
|
627 | delattr(self, name) | |
|
628 | cache = {} | |
|
629 | notifications = {} | |
|
630 | raise e | |
|
631 | finally: | |
|
632 | self._notify_trait = _notify_trait | |
|
633 | self._cross_validation_lock = False | |
|
634 | if isinstance(_notify_trait, types.MethodType): | |
|
635 | # FIXME: remove when support is bumped to 3.4. | |
|
636 | # when original method is restored, | |
|
637 | # remove the redundant value from __dict__ | |
|
638 | # (only used to preserve pickleability on Python < 3.4) | |
|
639 | self.__dict__.pop('_notify_trait', None) | |
|
640 | # trigger delayed notifications | |
|
641 | for v in dict(cache, **notifications).values(): | |
|
642 | self._notify_trait(*v) | |
|
610 | 643 | |
|
611 | 644 | def _notify_trait(self, name, old_value, new_value): |
|
612 | 645 |
General Comments 0
You need to be logged in to leave comments.
Login now