Show More
@@ -15,7 +15,8 b' Represents an unbounded float using a widget.' | |||||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from .widget import DOMWidget, register |
|
16 | from .widget import DOMWidget, register | |
17 | from .trait_types import Color |
|
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 | from IPython.utils.warn import DeprecatedClass |
|
20 | from IPython.utils.warn import DeprecatedClass | |
20 |
|
21 | |||
21 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
@@ -31,39 +32,37 b' class _Float(DOMWidget):' | |||||
31 | kwargs['value'] = value |
|
32 | kwargs['value'] = value | |
32 | super(_Float, self).__init__(**kwargs) |
|
33 | super(_Float, self).__init__(**kwargs) | |
33 |
|
34 | |||
|
35 | ||||
34 | class _BoundedFloat(_Float): |
|
36 | class _BoundedFloat(_Float): | |
35 | max = CFloat(100.0, help="Max value", sync=True) |
|
37 | max = CFloat(100.0, help="Max value", sync=True) | |
36 | min = CFloat(0.0, help="Min value", sync=True) |
|
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 | def __init__(self, *pargs, **kwargs): |
|
41 | def __init__(self, *pargs, **kwargs): | |
40 | """Constructor""" |
|
42 | """Constructor""" | |
41 | super(_BoundedFloat, self).__init__(*pargs, **kwargs) |
|
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 _ |
|
45 | def _value_validate(self, value, trait): | |
62 | """Make sure the max is always >= the min.""" |
|
46 | """Cap and floor value""" | |
63 |
if n |
|
47 | if self.min > value or self.max < value: | |
64 | raise ValueError("setting min > max") |
|
48 | value = min(max(value, self.min), self.max) | |
65 | if new > self.value: |
|
49 | return value | |
66 | self.value = new |
|
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 | @register('IPython.FloatText') |
|
68 | @register('IPython.FloatText') |
@@ -15,7 +15,8 b' Represents an unbounded int using a widget.' | |||||
15 | #----------------------------------------------------------------------------- |
|
15 | #----------------------------------------------------------------------------- | |
16 | from .widget import DOMWidget, register |
|
16 | from .widget import DOMWidget, register | |
17 | from .trait_types import Color |
|
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 | from IPython.utils.warn import DeprecatedClass |
|
20 | from IPython.utils.warn import DeprecatedClass | |
20 |
|
21 | |||
21 | #----------------------------------------------------------------------------- |
|
22 | #----------------------------------------------------------------------------- | |
@@ -32,41 +33,39 b' class _Int(DOMWidget):' | |||||
32 | kwargs['value'] = value |
|
33 | kwargs['value'] = value | |
33 | super(_Int, self).__init__(**kwargs) |
|
34 | super(_Int, self).__init__(**kwargs) | |
34 |
|
35 | |||
|
36 | ||||
35 | class _BoundedInt(_Int): |
|
37 | class _BoundedInt(_Int): | |
36 | """Base class used to create widgets that represent a int that is bounded |
|
38 | """Base class used to create widgets that represent a int that is bounded | |
37 | by a minium and maximum.""" |
|
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 | max = CInt(100, help="Max value", sync=True) |
|
41 | max = CInt(100, help="Max value", sync=True) | |
40 | min = CInt(0, help="Min value", sync=True) |
|
42 | min = CInt(0, help="Min value", sync=True) | |
41 |
|
43 | |||
42 | def __init__(self, *pargs, **kwargs): |
|
44 | def __init__(self, *pargs, **kwargs): | |
43 | """Constructor""" |
|
45 | """Constructor""" | |
44 | super(_BoundedInt, self).__init__(*pargs, **kwargs) |
|
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 _ |
|
48 | def _value_validate(self, value, trait): | |
65 | """Make sure the max is always >= the min.""" |
|
49 | """Cap and floor value""" | |
66 |
if n |
|
50 | if self.min > value or self.max < value: | |
67 | raise ValueError("setting min > max") |
|
51 | value = min(max(value, self.min), self.max) | |
68 | if new > self.value: |
|
52 | return value | |
69 | self.value = new |
|
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 | @register('IPython.IntText') |
|
70 | @register('IPython.IntText') | |
72 | class IntText(_Int): |
|
71 | class IntText(_Int): |
@@ -319,7 +319,6 b' class TraitType(object):' | |||||
319 | accept superclasses for :class:`This` values. |
|
319 | accept superclasses for :class:`This` values. | |
320 | """ |
|
320 | """ | |
321 |
|
321 | |||
322 |
|
||||
323 | metadata = {} |
|
322 | metadata = {} | |
324 | default_value = Undefined |
|
323 | default_value = Undefined | |
325 | allow_none = False |
|
324 | allow_none = False | |
@@ -447,7 +446,7 b' class TraitType(object):' | |||||
447 | try: |
|
446 | try: | |
448 | old_value = obj._trait_values[self.name] |
|
447 | old_value = obj._trait_values[self.name] | |
449 | except KeyError: |
|
448 | except KeyError: | |
450 |
old_value = |
|
449 | old_value = Undefined | |
451 |
|
450 | |||
452 | obj._trait_values[self.name] = new_value |
|
451 | obj._trait_values[self.name] = new_value | |
453 | try: |
|
452 | try: | |
@@ -465,13 +464,14 b' class TraitType(object):' | |||||
465 | return value |
|
464 | return value | |
466 | if hasattr(self, 'validate'): |
|
465 | if hasattr(self, 'validate'): | |
467 | value = self.validate(obj, value) |
|
466 | value = self.validate(obj, value) | |
468 | try: |
|
467 | if obj._cross_validation_lock is False: | |
469 |
|
|
468 | value = self._cross_validate(obj, value) | |
470 | except (AttributeError, RuntimeError): |
|
469 | return value | |
471 | # Qt mixins raise RuntimeError on missing attrs accessed before __init__ |
|
470 | ||
472 | pass |
|
471 | def _cross_validate(self, obj, value): | |
473 | else: |
|
472 | if hasattr(obj, '_%s_validate' % self.name): | |
474 | value = obj_validate(value, self) |
|
473 | cross_validate = getattr(obj, '_%s_validate' % self.name) | |
|
474 | value = cross_validate(value, self) | |||
475 | return value |
|
475 | return value | |
476 |
|
476 | |||
477 | def __or__(self, other): |
|
477 | def __or__(self, other): | |
@@ -542,6 +542,7 b' class MetaHasTraits(type):' | |||||
542 | v.this_class = cls |
|
542 | v.this_class = cls | |
543 | super(MetaHasTraits, cls).__init__(name, bases, classdict) |
|
543 | super(MetaHasTraits, cls).__init__(name, bases, classdict) | |
544 |
|
544 | |||
|
545 | ||||
545 | class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): |
|
546 | class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)): | |
546 |
|
547 | |||
547 | def __new__(cls, *args, **kw): |
|
548 | def __new__(cls, *args, **kw): | |
@@ -555,6 +556,7 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):' | |||||
555 | inst._trait_values = {} |
|
556 | inst._trait_values = {} | |
556 | inst._trait_notifiers = {} |
|
557 | inst._trait_notifiers = {} | |
557 | inst._trait_dyn_inits = {} |
|
558 | inst._trait_dyn_inits = {} | |
|
559 | inst._cross_validation_lock = True | |||
558 | # Here we tell all the TraitType instances to set their default |
|
560 | # Here we tell all the TraitType instances to set their default | |
559 | # values on the instance. |
|
561 | # values on the instance. | |
560 | for key in dir(cls): |
|
562 | for key in dir(cls): | |
@@ -570,33 +572,60 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):' | |||||
570 | value.instance_init() |
|
572 | value.instance_init() | |
571 | if key not in kw: |
|
573 | if key not in kw: | |
572 | value.set_default_value(inst) |
|
574 | value.set_default_value(inst) | |
573 |
|
575 | inst._cross_validation_lock = False | ||
574 | return inst |
|
576 | return inst | |
575 |
|
577 | |||
576 | def __init__(self, *args, **kw): |
|
578 | def __init__(self, *args, **kw): | |
577 | # Allow trait values to be set using keyword arguments. |
|
579 | # Allow trait values to be set using keyword arguments. | |
578 | # We need to use setattr for this to trigger validation and |
|
580 | # We need to use setattr for this to trigger validation and | |
579 | # notifications. |
|
581 | # notifications. | |
580 |
|
||||
581 | with self.hold_trait_notifications(): |
|
582 | with self.hold_trait_notifications(): | |
582 | for key, value in iteritems(kw): |
|
583 | for key, value in iteritems(kw): | |
583 | setattr(self, key, value) |
|
584 | setattr(self, key, value) | |
584 |
|
585 | |||
585 | @contextlib.contextmanager |
|
586 | @contextlib.contextmanager | |
586 | def hold_trait_notifications(self): |
|
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), |
|
591 | Use this when doing multiple trait assignments (init, config), to avoid | |
590 |
|
|
592 | race conditions in trait notifiers requesting other trait values. | |
591 | All trait notifications will fire after all values have been assigned. |
|
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 | _notify_trait = self._notify_trait |
|
601 | _notify_trait = self._notify_trait | |
594 | notifications = [] |
|
602 | ||
595 | self._notify_trait = lambda *a: notifications.append(a) |
|
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 | try: |
|
611 | try: | |
598 | yield |
|
612 | yield | |
599 | finally: |
|
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 | self._notify_trait = _notify_trait |
|
629 | self._notify_trait = _notify_trait | |
601 | if isinstance(_notify_trait, types.MethodType): |
|
630 | if isinstance(_notify_trait, types.MethodType): | |
602 | # FIXME: remove when support is bumped to 3.4. |
|
631 | # FIXME: remove when support is bumped to 3.4. | |
@@ -604,9 +633,10 b' class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):' | |||||
604 | # remove the redundant value from __dict__ |
|
633 | # remove the redundant value from __dict__ | |
605 | # (only used to preserve pickleability on Python < 3.4) |
|
634 | # (only used to preserve pickleability on Python < 3.4) | |
606 | self.__dict__.pop('_notify_trait', None) |
|
635 | self.__dict__.pop('_notify_trait', None) | |
|
636 | ||||
607 | # trigger delayed notifications |
|
637 | # trigger delayed notifications | |
608 |
for |
|
638 | for name in notifications: | |
609 |
self._notify_trait(* |
|
639 | self._notify_trait(*(notifications[name])) | |
610 |
|
640 | |||
611 | def _notify_trait(self, name, old_value, new_value): |
|
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