##// END OF EJS Templates
Hold validation with context manager and validate slider
Sylvain Corlay -
Show More
@@ -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, Tuple
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 that the value can take (ignored by some views)", sync=True)
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
49 def _handle_value_changed(self, name, old, new):
50 """Validate value."""
51 if self.min > new or new > self.max:
52 self.value = min(max(new, self.min), self.max)
53
54 def _handle_max_changed(self, name, old, new):
55 """Make sure the min is always <= the max."""
56 if new < self.min:
57 raise ValueError("setting max < min")
58 if new < self.value:
59 self.value = new
60 44
61 def _handle_min_changed(self, name, old, new):
62 """Make sure the max is always >= the min."""
63 if new > self.max:
64 raise ValueError("setting min > max")
65 if new > self.value:
66 self.value = new
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
50
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
58
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')
@@ -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, Tuple
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 that the value can take (ignored by some views)", sync=True)
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 """Validate value."""
54 if self.min > new or new > self.max:
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 self.value = new
63 47
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
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):
@@ -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 = None
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 obj_validate = getattr(obj, '_%s_validate' % self.name)
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,33 +572,60 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 """Context manager for bundling trait change notifications and cross
589 validation.
588 590
589 Use this when doing multiple trait assignments (init, config),
590 to avoid race conditions in trait notifiers requesting other trait values.
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 """
595 if self._cross_validation_lock is True:
596 yield
597 else:
598 self._cross_validation_lock = True
599 cache = {}
600 notifications = {}
593 601 _notify_trait = self._notify_trait
594 notifications = []
595 self._notify_trait = lambda *a: notifications.append(a)
602
603 def cache_values(*a):
604 cache[a[0]] = a
605
606 def hold_notifications(*a):
607 notifications[a[0]] = a
608
609 self._notify_trait = cache_values
596 610
597 611 try:
598 612 yield
599 613 finally:
614 try:
615 self._notify_trait = hold_notifications
616 for name in cache:
617 if hasattr(self, '_%s_validate' % name):
618 cross_validate = getattr(self, '_%s_validate' % name)
619 setattr(self, name, cross_validate(getattr(self, name), self))
620 except TraitError as e:
621 self._notify_trait = lambda *x: None
622 for name in cache:
623 if cache[name][1] is not Undefined:
624 setattr(self, name, cache[name][1])
625 notifications = {}
626 raise e
627 finally:
628 self._cross_validation_lock = False
600 629 self._notify_trait = _notify_trait
601 630 if isinstance(_notify_trait, types.MethodType):
602 631 # FIXME: remove when support is bumped to 3.4.
@@ -604,9 +633,10 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):'
604 633 # remove the redundant value from __dict__
605 634 # (only used to preserve pickleability on Python < 3.4)
606 635 self.__dict__.pop('_notify_trait', None)
636
607 637 # trigger delayed notifications
608 for args in notifications:
609 self._notify_trait(*args)
638 for name in notifications:
639 self._notify_trait(*(notifications[name]))
610 640
611 641 def _notify_trait(self, name, old_value, new_value):
612 642
General Comments 0
You need to be logged in to leave comments. Login now