##// END OF EJS Templates
move eventful to html.widgets.eventful
Min RK -
Show More
@@ -1,40 +1,40 b''
1 from .widget import Widget, DOMWidget, CallbackDispatcher, register, widget_serialization
1 from .widget import Widget, DOMWidget, CallbackDispatcher, register, widget_serialization
2
2
3 from .trait_types import Color
3 from .trait_types import Color, EventfulDict, EventfulList
4
4
5 from .widget_bool import Checkbox, ToggleButton, Valid
5 from .widget_bool import Checkbox, ToggleButton, Valid
6 from .widget_button import Button
6 from .widget_button import Button
7 from .widget_box import Box, FlexBox, HBox, VBox
7 from .widget_box import Box, FlexBox, HBox, VBox
8 from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
8 from .widget_float import FloatText, BoundedFloatText, FloatSlider, FloatProgress, FloatRangeSlider
9 from .widget_image import Image
9 from .widget_image import Image
10 from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider
10 from .widget_int import IntText, BoundedIntText, IntSlider, IntProgress, IntRangeSlider
11 from .widget_output import Output
11 from .widget_output import Output
12 from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple
12 from .widget_selection import RadioButtons, ToggleButtons, Dropdown, Select, SelectMultiple
13 from .widget_selectioncontainer import Tab, Accordion
13 from .widget_selectioncontainer import Tab, Accordion
14 from .widget_string import HTML, Latex, Text, Textarea
14 from .widget_string import HTML, Latex, Text, Textarea
15 from .interaction import interact, interactive, fixed, interact_manual
15 from .interaction import interact, interactive, fixed, interact_manual
16 from .widget_link import jslink, jsdlink
16 from .widget_link import jslink, jsdlink
17
17
18 # Deprecated classes
18 # Deprecated classes
19 from .widget_bool import CheckboxWidget, ToggleButtonWidget
19 from .widget_bool import CheckboxWidget, ToggleButtonWidget
20 from .widget_button import ButtonWidget
20 from .widget_button import ButtonWidget
21 from .widget_box import ContainerWidget
21 from .widget_box import ContainerWidget
22 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
22 from .widget_float import FloatTextWidget, BoundedFloatTextWidget, FloatSliderWidget, FloatProgressWidget
23 from .widget_image import ImageWidget
23 from .widget_image import ImageWidget
24 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
24 from .widget_int import IntTextWidget, BoundedIntTextWidget, IntSliderWidget, IntProgressWidget
25 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
25 from .widget_selection import RadioButtonsWidget, ToggleButtonsWidget, DropdownWidget, SelectWidget
26 from .widget_selectioncontainer import TabWidget, AccordionWidget
26 from .widget_selectioncontainer import TabWidget, AccordionWidget
27 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
27 from .widget_string import HTMLWidget, LatexWidget, TextWidget, TextareaWidget
28
28
29 # We use warn_explicit so we have very brief messages without file or line numbers.
29 # We use warn_explicit so we have very brief messages without file or line numbers.
30 # The concern is that file or line numbers will confuse the interactive user.
30 # The concern is that file or line numbers will confuse the interactive user.
31 # To ignore this warning, do:
31 # To ignore this warning, do:
32 #
32 #
33 # from warnings import filterwarnings
33 # from warnings import filterwarnings
34 # filterwarnings('ignore', module='IPython.html.widgets')
34 # filterwarnings('ignore', module='IPython.html.widgets')
35
35
36 from warnings import warn_explicit
36 from warnings import warn_explicit
37 __warningregistry__ = {}
37 __warningregistry__ = {}
38 warn_explicit("IPython widgets are experimental and may change in the future.",
38 warn_explicit("IPython widgets are experimental and may change in the future.",
39 FutureWarning, '', 0, module = 'IPython.html.widgets',
39 FutureWarning, '', 0, module = 'IPython.html.widgets',
40 registry = __warningregistry__, module_globals = globals)
40 registry = __warningregistry__, module_globals = globals)
1 NO CONTENT: file renamed from traitlets/eventful.py to IPython/html/widgets/eventful.py
NO CONTENT: file renamed from traitlets/eventful.py to IPython/html/widgets/eventful.py
@@ -1,20 +1,83 b''
1 """Test trait types of the widget packages."""
1 """Test trait types of the widget packages."""
2
2
3 # Copyright (c) IPython Development Team.
3 # Copyright (c) IPython Development Team.
4 # Distributed under the terms of the Modified BSD License.
4 # Distributed under the terms of the Modified BSD License.
5
5
6 from unittest import TestCase
6 from unittest import TestCase
7 from IPython.utils.traitlets import HasTraits
7 from IPython.utils.traitlets import HasTraits
8 from traitlets.tests.test_traitlets import TraitTestBase
8 from traitlets.tests.test_traitlets import TraitTestBase
9 from IPython.html.widgets import Color
9 from IPython.html.widgets import Color, EventfulDict, EventfulList
10
10
11
11
12 class ColorTrait(HasTraits):
12 class ColorTrait(HasTraits):
13 value = Color("black")
13 value = Color("black")
14
14
15
15
16 class TestColor(TraitTestBase):
16 class TestColor(TraitTestBase):
17 obj = ColorTrait()
17 obj = ColorTrait()
18
18
19 _good_values = ["blue", "#AA0", "#FFFFFF"]
19 _good_values = ["blue", "#AA0", "#FFFFFF"]
20 _bad_values = ["vanilla", "blues"]
20 _bad_values = ["vanilla", "blues"]
21
22
23 class TestEventful(TestCase):
24
25 def test_list(self):
26 """Does the EventfulList work?"""
27 event_cache = []
28
29 class A(HasTraits):
30 x = EventfulList([c for c in 'abc'])
31 a = A()
32 a.x.on_events(lambda i, x: event_cache.append('insert'), \
33 lambda i, x: event_cache.append('set'), \
34 lambda i: event_cache.append('del'), \
35 lambda: event_cache.append('reverse'), \
36 lambda *p, **k: event_cache.append('sort'))
37
38 a.x.remove('c')
39 # ab
40 a.x.insert(0, 'z')
41 # zab
42 del a.x[1]
43 # zb
44 a.x.reverse()
45 # bz
46 a.x[1] = 'o'
47 # bo
48 a.x.append('a')
49 # boa
50 a.x.sort()
51 # abo
52
53 # Were the correct events captured?
54 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
55
56 # Is the output correct?
57 self.assertEqual(a.x, [c for c in 'abo'])
58
59 def test_dict(self):
60 """Does the EventfulDict work?"""
61 event_cache = []
62
63 class A(HasTraits):
64 x = EventfulDict({c: c for c in 'abc'})
65 a = A()
66 a.x.on_events(lambda k, v: event_cache.append('add'), \
67 lambda k, v: event_cache.append('set'), \
68 lambda k: event_cache.append('del'))
69
70 del a.x['c']
71 # ab
72 a.x['z'] = 1
73 # abz
74 a.x['z'] = 'z'
75 # abz
76 a.x.pop('a')
77 # bz
78
79 # Were the correct events captured?
80 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
81
82 # Is the output correct?
83 self.assertEqual(a.x, {c: c for c in 'bz'})
@@ -1,28 +1,70 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Trait types for html widgets.
3 Trait types for html widgets.
4 """
4 """
5
5
6 # Copyright (c) IPython Development Team.
6 # Copyright (c) IPython Development Team.
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8
8
9 import re
9 import re
10 from IPython.utils import traitlets
10 from IPython.utils import traitlets
11
11
12 #-----------------------------------------------------------------------------
12 from . import eventful
13 # Utilities
14 #-----------------------------------------------------------------------------
15
13
16 _color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
14 _color_names = ['aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'cyan', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred ', 'indigo ', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgray', 'lightgreen', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'magenta', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'rebeccapurple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violet', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']
17 _color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
15 _color_re = re.compile(r'#[a-fA-F0-9]{3}(?:[a-fA-F0-9]{3})?$')
18
16
19
17
20 class Color(traitlets.Unicode):
18 class Color(traitlets.Unicode):
21 """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
19 """A string holding a valid HTML color such as 'blue', '#060482', '#A80'"""
22
20
23 info_text = 'a valid HTML color'
21 info_text = 'a valid HTML color'
24
22
25 def validate(self, obj, value):
23 def validate(self, obj, value):
26 if value.lower() in _color_names or _color_re.match(value):
24 if value.lower() in _color_names or _color_re.match(value):
27 return value
25 return value
28 self.error(obj, value)
26 self.error(obj, value)
27
28
29 class EventfulDict(traitlets.Instance):
30 """An instance of an EventfulDict."""
31
32 def __init__(self, default_value={}, **metadata):
33 """Create a EventfulDict trait type from a dict.
34
35 The default value is created by doing
36 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
37 ``default_value``.
38 """
39 if default_value is None:
40 args = None
41 elif isinstance(default_value, dict):
42 args = (default_value,)
43 elif isinstance(default_value, SequenceTypes):
44 args = (default_value,)
45 else:
46 raise TypeError('default value of EventfulDict was %s' % default_value)
47
48 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
49 **metadata)
50
51
52 class EventfulList(traitlets.Instance):
53 """An instance of an EventfulList."""
54
55 def __init__(self, default_value=None, **metadata):
56 """Create a EventfulList trait type from a dict.
57
58 The default value is created by doing
59 ``eventful.EvenfulList(default_value)``, which creates a copy of the
60 ``default_value``.
61 """
62 if default_value is None:
63 args = ((),)
64 else:
65 args = (default_value,)
66
67 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
68 **metadata)
69
70
@@ -1,1634 +1,1573 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.traitlets."""
2 """Tests for IPython.utils.traitlets."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 #
6 #
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
8 # also under the terms of the Modified BSD License.
8 # also under the terms of the Modified BSD License.
9
9
10 import pickle
10 import pickle
11 import re
11 import re
12 import sys
12 import sys
13 from unittest import TestCase
13 from unittest import TestCase
14
14
15 import nose.tools as nt
15 import nose.tools as nt
16 from nose import SkipTest
16 from nose import SkipTest
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
23 ForwardDeclaredType, ForwardDeclaredInstance,
24 )
24 )
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.testing.decorators import skipif
26 from IPython.testing.decorators import skipif
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Helper classes for testing
29 # Helper classes for testing
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32
32
33 class HasTraitsStub(HasTraits):
33 class HasTraitsStub(HasTraits):
34
34
35 def _notify_trait(self, name, old, new):
35 def _notify_trait(self, name, old, new):
36 self._notify_name = name
36 self._notify_name = name
37 self._notify_old = old
37 self._notify_old = old
38 self._notify_new = new
38 self._notify_new = new
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Test classes
42 # Test classes
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45
45
46 class TestTraitType(TestCase):
46 class TestTraitType(TestCase):
47
47
48 def test_get_undefined(self):
48 def test_get_undefined(self):
49 class A(HasTraits):
49 class A(HasTraits):
50 a = TraitType
50 a = TraitType
51 a = A()
51 a = A()
52 self.assertEqual(a.a, Undefined)
52 self.assertEqual(a.a, Undefined)
53
53
54 def test_set(self):
54 def test_set(self):
55 class A(HasTraitsStub):
55 class A(HasTraitsStub):
56 a = TraitType
56 a = TraitType
57
57
58 a = A()
58 a = A()
59 a.a = 10
59 a.a = 10
60 self.assertEqual(a.a, 10)
60 self.assertEqual(a.a, 10)
61 self.assertEqual(a._notify_name, 'a')
61 self.assertEqual(a._notify_name, 'a')
62 self.assertEqual(a._notify_old, Undefined)
62 self.assertEqual(a._notify_old, Undefined)
63 self.assertEqual(a._notify_new, 10)
63 self.assertEqual(a._notify_new, 10)
64
64
65 def test_validate(self):
65 def test_validate(self):
66 class MyTT(TraitType):
66 class MyTT(TraitType):
67 def validate(self, inst, value):
67 def validate(self, inst, value):
68 return -1
68 return -1
69 class A(HasTraitsStub):
69 class A(HasTraitsStub):
70 tt = MyTT
70 tt = MyTT
71
71
72 a = A()
72 a = A()
73 a.tt = 10
73 a.tt = 10
74 self.assertEqual(a.tt, -1)
74 self.assertEqual(a.tt, -1)
75
75
76 def test_default_validate(self):
76 def test_default_validate(self):
77 class MyIntTT(TraitType):
77 class MyIntTT(TraitType):
78 def validate(self, obj, value):
78 def validate(self, obj, value):
79 if isinstance(value, int):
79 if isinstance(value, int):
80 return value
80 return value
81 self.error(obj, value)
81 self.error(obj, value)
82 class A(HasTraits):
82 class A(HasTraits):
83 tt = MyIntTT(10)
83 tt = MyIntTT(10)
84 a = A()
84 a = A()
85 self.assertEqual(a.tt, 10)
85 self.assertEqual(a.tt, 10)
86
86
87 # Defaults are validated when the HasTraits is instantiated
87 # Defaults are validated when the HasTraits is instantiated
88 class B(HasTraits):
88 class B(HasTraits):
89 tt = MyIntTT('bad default')
89 tt = MyIntTT('bad default')
90 self.assertRaises(TraitError, B)
90 self.assertRaises(TraitError, B)
91
91
92 def test_info(self):
92 def test_info(self):
93 class A(HasTraits):
93 class A(HasTraits):
94 tt = TraitType
94 tt = TraitType
95 a = A()
95 a = A()
96 self.assertEqual(A.tt.info(), 'any value')
96 self.assertEqual(A.tt.info(), 'any value')
97
97
98 def test_error(self):
98 def test_error(self):
99 class A(HasTraits):
99 class A(HasTraits):
100 tt = TraitType
100 tt = TraitType
101 a = A()
101 a = A()
102 self.assertRaises(TraitError, A.tt.error, a, 10)
102 self.assertRaises(TraitError, A.tt.error, a, 10)
103
103
104 def test_dynamic_initializer(self):
104 def test_dynamic_initializer(self):
105 class A(HasTraits):
105 class A(HasTraits):
106 x = Int(10)
106 x = Int(10)
107 def _x_default(self):
107 def _x_default(self):
108 return 11
108 return 11
109 class B(A):
109 class B(A):
110 x = Int(20)
110 x = Int(20)
111 class C(A):
111 class C(A):
112 def _x_default(self):
112 def _x_default(self):
113 return 21
113 return 21
114
114
115 a = A()
115 a = A()
116 self.assertEqual(a._trait_values, {})
116 self.assertEqual(a._trait_values, {})
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
118 self.assertEqual(a.x, 11)
118 self.assertEqual(a.x, 11)
119 self.assertEqual(a._trait_values, {'x': 11})
119 self.assertEqual(a._trait_values, {'x': 11})
120 b = B()
120 b = B()
121 self.assertEqual(b._trait_values, {'x': 20})
121 self.assertEqual(b._trait_values, {'x': 20})
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
123 self.assertEqual(b.x, 20)
123 self.assertEqual(b.x, 20)
124 c = C()
124 c = C()
125 self.assertEqual(c._trait_values, {})
125 self.assertEqual(c._trait_values, {})
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
127 self.assertEqual(c.x, 21)
127 self.assertEqual(c.x, 21)
128 self.assertEqual(c._trait_values, {'x': 21})
128 self.assertEqual(c._trait_values, {'x': 21})
129 # Ensure that the base class remains unmolested when the _default
129 # Ensure that the base class remains unmolested when the _default
130 # initializer gets overridden in a subclass.
130 # initializer gets overridden in a subclass.
131 a = A()
131 a = A()
132 c = C()
132 c = C()
133 self.assertEqual(a._trait_values, {})
133 self.assertEqual(a._trait_values, {})
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
135 self.assertEqual(a.x, 11)
135 self.assertEqual(a.x, 11)
136 self.assertEqual(a._trait_values, {'x': 11})
136 self.assertEqual(a._trait_values, {'x': 11})
137
137
138
138
139
139
140 class TestHasTraitsMeta(TestCase):
140 class TestHasTraitsMeta(TestCase):
141
141
142 def test_metaclass(self):
142 def test_metaclass(self):
143 self.assertEqual(type(HasTraits), MetaHasTraits)
143 self.assertEqual(type(HasTraits), MetaHasTraits)
144
144
145 class A(HasTraits):
145 class A(HasTraits):
146 a = Int
146 a = Int
147
147
148 a = A()
148 a = A()
149 self.assertEqual(type(a.__class__), MetaHasTraits)
149 self.assertEqual(type(a.__class__), MetaHasTraits)
150 self.assertEqual(a.a,0)
150 self.assertEqual(a.a,0)
151 a.a = 10
151 a.a = 10
152 self.assertEqual(a.a,10)
152 self.assertEqual(a.a,10)
153
153
154 class B(HasTraits):
154 class B(HasTraits):
155 b = Int()
155 b = Int()
156
156
157 b = B()
157 b = B()
158 self.assertEqual(b.b,0)
158 self.assertEqual(b.b,0)
159 b.b = 10
159 b.b = 10
160 self.assertEqual(b.b,10)
160 self.assertEqual(b.b,10)
161
161
162 class C(HasTraits):
162 class C(HasTraits):
163 c = Int(30)
163 c = Int(30)
164
164
165 c = C()
165 c = C()
166 self.assertEqual(c.c,30)
166 self.assertEqual(c.c,30)
167 c.c = 10
167 c.c = 10
168 self.assertEqual(c.c,10)
168 self.assertEqual(c.c,10)
169
169
170 def test_this_class(self):
170 def test_this_class(self):
171 class A(HasTraits):
171 class A(HasTraits):
172 t = This()
172 t = This()
173 tt = This()
173 tt = This()
174 class B(A):
174 class B(A):
175 tt = This()
175 tt = This()
176 ttt = This()
176 ttt = This()
177 self.assertEqual(A.t.this_class, A)
177 self.assertEqual(A.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
179 self.assertEqual(B.tt.this_class, B)
179 self.assertEqual(B.tt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
181
181
182 class TestHasTraitsNotify(TestCase):
182 class TestHasTraitsNotify(TestCase):
183
183
184 def setUp(self):
184 def setUp(self):
185 self._notify1 = []
185 self._notify1 = []
186 self._notify2 = []
186 self._notify2 = []
187
187
188 def notify1(self, name, old, new):
188 def notify1(self, name, old, new):
189 self._notify1.append((name, old, new))
189 self._notify1.append((name, old, new))
190
190
191 def notify2(self, name, old, new):
191 def notify2(self, name, old, new):
192 self._notify2.append((name, old, new))
192 self._notify2.append((name, old, new))
193
193
194 def test_notify_all(self):
194 def test_notify_all(self):
195
195
196 class A(HasTraits):
196 class A(HasTraits):
197 a = Int
197 a = Int
198 b = Float
198 b = Float
199
199
200 a = A()
200 a = A()
201 a.on_trait_change(self.notify1)
201 a.on_trait_change(self.notify1)
202 a.a = 0
202 a.a = 0
203 self.assertEqual(len(self._notify1),0)
203 self.assertEqual(len(self._notify1),0)
204 a.b = 0.0
204 a.b = 0.0
205 self.assertEqual(len(self._notify1),0)
205 self.assertEqual(len(self._notify1),0)
206 a.a = 10
206 a.a = 10
207 self.assertTrue(('a',0,10) in self._notify1)
207 self.assertTrue(('a',0,10) in self._notify1)
208 a.b = 10.0
208 a.b = 10.0
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
212 self._notify1 = []
212 self._notify1 = []
213 a.on_trait_change(self.notify1,remove=True)
213 a.on_trait_change(self.notify1,remove=True)
214 a.a = 20
214 a.a = 20
215 a.b = 20.0
215 a.b = 20.0
216 self.assertEqual(len(self._notify1),0)
216 self.assertEqual(len(self._notify1),0)
217
217
218 def test_notify_one(self):
218 def test_notify_one(self):
219
219
220 class A(HasTraits):
220 class A(HasTraits):
221 a = Int
221 a = Int
222 b = Float
222 b = Float
223
223
224 a = A()
224 a = A()
225 a.on_trait_change(self.notify1, 'a')
225 a.on_trait_change(self.notify1, 'a')
226 a.a = 0
226 a.a = 0
227 self.assertEqual(len(self._notify1),0)
227 self.assertEqual(len(self._notify1),0)
228 a.a = 10
228 a.a = 10
229 self.assertTrue(('a',0,10) in self._notify1)
229 self.assertTrue(('a',0,10) in self._notify1)
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
231
231
232 def test_subclass(self):
232 def test_subclass(self):
233
233
234 class A(HasTraits):
234 class A(HasTraits):
235 a = Int
235 a = Int
236
236
237 class B(A):
237 class B(A):
238 b = Float
238 b = Float
239
239
240 b = B()
240 b = B()
241 self.assertEqual(b.a,0)
241 self.assertEqual(b.a,0)
242 self.assertEqual(b.b,0.0)
242 self.assertEqual(b.b,0.0)
243 b.a = 100
243 b.a = 100
244 b.b = 100.0
244 b.b = 100.0
245 self.assertEqual(b.a,100)
245 self.assertEqual(b.a,100)
246 self.assertEqual(b.b,100.0)
246 self.assertEqual(b.b,100.0)
247
247
248 def test_notify_subclass(self):
248 def test_notify_subclass(self):
249
249
250 class A(HasTraits):
250 class A(HasTraits):
251 a = Int
251 a = Int
252
252
253 class B(A):
253 class B(A):
254 b = Float
254 b = Float
255
255
256 b = B()
256 b = B()
257 b.on_trait_change(self.notify1, 'a')
257 b.on_trait_change(self.notify1, 'a')
258 b.on_trait_change(self.notify2, 'b')
258 b.on_trait_change(self.notify2, 'b')
259 b.a = 0
259 b.a = 0
260 b.b = 0.0
260 b.b = 0.0
261 self.assertEqual(len(self._notify1),0)
261 self.assertEqual(len(self._notify1),0)
262 self.assertEqual(len(self._notify2),0)
262 self.assertEqual(len(self._notify2),0)
263 b.a = 10
263 b.a = 10
264 b.b = 10.0
264 b.b = 10.0
265 self.assertTrue(('a',0,10) in self._notify1)
265 self.assertTrue(('a',0,10) in self._notify1)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
267
267
268 def test_static_notify(self):
268 def test_static_notify(self):
269
269
270 class A(HasTraits):
270 class A(HasTraits):
271 a = Int
271 a = Int
272 _notify1 = []
272 _notify1 = []
273 def _a_changed(self, name, old, new):
273 def _a_changed(self, name, old, new):
274 self._notify1.append((name, old, new))
274 self._notify1.append((name, old, new))
275
275
276 a = A()
276 a = A()
277 a.a = 0
277 a.a = 0
278 # This is broken!!!
278 # This is broken!!!
279 self.assertEqual(len(a._notify1),0)
279 self.assertEqual(len(a._notify1),0)
280 a.a = 10
280 a.a = 10
281 self.assertTrue(('a',0,10) in a._notify1)
281 self.assertTrue(('a',0,10) in a._notify1)
282
282
283 class B(A):
283 class B(A):
284 b = Float
284 b = Float
285 _notify2 = []
285 _notify2 = []
286 def _b_changed(self, name, old, new):
286 def _b_changed(self, name, old, new):
287 self._notify2.append((name, old, new))
287 self._notify2.append((name, old, new))
288
288
289 b = B()
289 b = B()
290 b.a = 10
290 b.a = 10
291 b.b = 10.0
291 b.b = 10.0
292 self.assertTrue(('a',0,10) in b._notify1)
292 self.assertTrue(('a',0,10) in b._notify1)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
294
294
295 def test_notify_args(self):
295 def test_notify_args(self):
296
296
297 def callback0():
297 def callback0():
298 self.cb = ()
298 self.cb = ()
299 def callback1(name):
299 def callback1(name):
300 self.cb = (name,)
300 self.cb = (name,)
301 def callback2(name, new):
301 def callback2(name, new):
302 self.cb = (name, new)
302 self.cb = (name, new)
303 def callback3(name, old, new):
303 def callback3(name, old, new):
304 self.cb = (name, old, new)
304 self.cb = (name, old, new)
305
305
306 class A(HasTraits):
306 class A(HasTraits):
307 a = Int
307 a = Int
308
308
309 a = A()
309 a = A()
310 a.on_trait_change(callback0, 'a')
310 a.on_trait_change(callback0, 'a')
311 a.a = 10
311 a.a = 10
312 self.assertEqual(self.cb,())
312 self.assertEqual(self.cb,())
313 a.on_trait_change(callback0, 'a', remove=True)
313 a.on_trait_change(callback0, 'a', remove=True)
314
314
315 a.on_trait_change(callback1, 'a')
315 a.on_trait_change(callback1, 'a')
316 a.a = 100
316 a.a = 100
317 self.assertEqual(self.cb,('a',))
317 self.assertEqual(self.cb,('a',))
318 a.on_trait_change(callback1, 'a', remove=True)
318 a.on_trait_change(callback1, 'a', remove=True)
319
319
320 a.on_trait_change(callback2, 'a')
320 a.on_trait_change(callback2, 'a')
321 a.a = 1000
321 a.a = 1000
322 self.assertEqual(self.cb,('a',1000))
322 self.assertEqual(self.cb,('a',1000))
323 a.on_trait_change(callback2, 'a', remove=True)
323 a.on_trait_change(callback2, 'a', remove=True)
324
324
325 a.on_trait_change(callback3, 'a')
325 a.on_trait_change(callback3, 'a')
326 a.a = 10000
326 a.a = 10000
327 self.assertEqual(self.cb,('a',1000,10000))
327 self.assertEqual(self.cb,('a',1000,10000))
328 a.on_trait_change(callback3, 'a', remove=True)
328 a.on_trait_change(callback3, 'a', remove=True)
329
329
330 self.assertEqual(len(a._trait_notifiers['a']),0)
330 self.assertEqual(len(a._trait_notifiers['a']),0)
331
331
332 def test_notify_only_once(self):
332 def test_notify_only_once(self):
333
333
334 class A(HasTraits):
334 class A(HasTraits):
335 listen_to = ['a']
335 listen_to = ['a']
336
336
337 a = Int(0)
337 a = Int(0)
338 b = 0
338 b = 0
339
339
340 def __init__(self, **kwargs):
340 def __init__(self, **kwargs):
341 super(A, self).__init__(**kwargs)
341 super(A, self).__init__(**kwargs)
342 self.on_trait_change(self.listener1, ['a'])
342 self.on_trait_change(self.listener1, ['a'])
343
343
344 def listener1(self, name, old, new):
344 def listener1(self, name, old, new):
345 self.b += 1
345 self.b += 1
346
346
347 class B(A):
347 class B(A):
348
348
349 c = 0
349 c = 0
350 d = 0
350 d = 0
351
351
352 def __init__(self, **kwargs):
352 def __init__(self, **kwargs):
353 super(B, self).__init__(**kwargs)
353 super(B, self).__init__(**kwargs)
354 self.on_trait_change(self.listener2)
354 self.on_trait_change(self.listener2)
355
355
356 def listener2(self, name, old, new):
356 def listener2(self, name, old, new):
357 self.c += 1
357 self.c += 1
358
358
359 def _a_changed(self, name, old, new):
359 def _a_changed(self, name, old, new):
360 self.d += 1
360 self.d += 1
361
361
362 b = B()
362 b = B()
363 b.a += 1
363 b.a += 1
364 self.assertEqual(b.b, b.c)
364 self.assertEqual(b.b, b.c)
365 self.assertEqual(b.b, b.d)
365 self.assertEqual(b.b, b.d)
366 b.a += 1
366 b.a += 1
367 self.assertEqual(b.b, b.c)
367 self.assertEqual(b.b, b.c)
368 self.assertEqual(b.b, b.d)
368 self.assertEqual(b.b, b.d)
369
369
370
370
371 class TestHasTraits(TestCase):
371 class TestHasTraits(TestCase):
372
372
373 def test_trait_names(self):
373 def test_trait_names(self):
374 class A(HasTraits):
374 class A(HasTraits):
375 i = Int
375 i = Int
376 f = Float
376 f = Float
377 a = A()
377 a = A()
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
380
380
381 def test_trait_metadata(self):
381 def test_trait_metadata(self):
382 class A(HasTraits):
382 class A(HasTraits):
383 i = Int(config_key='MY_VALUE')
383 i = Int(config_key='MY_VALUE')
384 a = A()
384 a = A()
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
386
386
387 def test_trait_metadata_default(self):
387 def test_trait_metadata_default(self):
388 class A(HasTraits):
388 class A(HasTraits):
389 i = Int()
389 i = Int()
390 a = A()
390 a = A()
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
393
393
394 def test_traits(self):
394 def test_traits(self):
395 class A(HasTraits):
395 class A(HasTraits):
396 i = Int
396 i = Int
397 f = Float
397 f = Float
398 a = A()
398 a = A()
399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
401
401
402 def test_traits_metadata(self):
402 def test_traits_metadata(self):
403 class A(HasTraits):
403 class A(HasTraits):
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
406 j = Int(0)
406 j = Int(0)
407 a = A()
407 a = A()
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
410 self.assertEqual(traits, dict(i=A.i))
410 self.assertEqual(traits, dict(i=A.i))
411
411
412 # This passes, but it shouldn't because I am replicating a bug in
412 # This passes, but it shouldn't because I am replicating a bug in
413 # traits.
413 # traits.
414 traits = a.traits(config_key=lambda v: True)
414 traits = a.traits(config_key=lambda v: True)
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
416
416
417 def test_init(self):
417 def test_init(self):
418 class A(HasTraits):
418 class A(HasTraits):
419 i = Int()
419 i = Int()
420 x = Float()
420 x = Float()
421 a = A(i=1, x=10.0)
421 a = A(i=1, x=10.0)
422 self.assertEqual(a.i, 1)
422 self.assertEqual(a.i, 1)
423 self.assertEqual(a.x, 10.0)
423 self.assertEqual(a.x, 10.0)
424
424
425 def test_positional_args(self):
425 def test_positional_args(self):
426 class A(HasTraits):
426 class A(HasTraits):
427 i = Int(0)
427 i = Int(0)
428 def __init__(self, i):
428 def __init__(self, i):
429 super(A, self).__init__()
429 super(A, self).__init__()
430 self.i = i
430 self.i = i
431
431
432 a = A(5)
432 a = A(5)
433 self.assertEqual(a.i, 5)
433 self.assertEqual(a.i, 5)
434 # should raise TypeError if no positional arg given
434 # should raise TypeError if no positional arg given
435 self.assertRaises(TypeError, A)
435 self.assertRaises(TypeError, A)
436
436
437 #-----------------------------------------------------------------------------
437 #-----------------------------------------------------------------------------
438 # Tests for specific trait types
438 # Tests for specific trait types
439 #-----------------------------------------------------------------------------
439 #-----------------------------------------------------------------------------
440
440
441
441
442 class TestType(TestCase):
442 class TestType(TestCase):
443
443
444 def test_default(self):
444 def test_default(self):
445
445
446 class B(object): pass
446 class B(object): pass
447 class A(HasTraits):
447 class A(HasTraits):
448 klass = Type(allow_none=True)
448 klass = Type(allow_none=True)
449
449
450 a = A()
450 a = A()
451 self.assertEqual(a.klass, None)
451 self.assertEqual(a.klass, None)
452
452
453 a.klass = B
453 a.klass = B
454 self.assertEqual(a.klass, B)
454 self.assertEqual(a.klass, B)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
456
456
457 def test_value(self):
457 def test_value(self):
458
458
459 class B(object): pass
459 class B(object): pass
460 class C(object): pass
460 class C(object): pass
461 class A(HasTraits):
461 class A(HasTraits):
462 klass = Type(B)
462 klass = Type(B)
463
463
464 a = A()
464 a = A()
465 self.assertEqual(a.klass, B)
465 self.assertEqual(a.klass, B)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
468 a.klass = B
468 a.klass = B
469
469
470 def test_allow_none(self):
470 def test_allow_none(self):
471
471
472 class B(object): pass
472 class B(object): pass
473 class C(B): pass
473 class C(B): pass
474 class A(HasTraits):
474 class A(HasTraits):
475 klass = Type(B)
475 klass = Type(B)
476
476
477 a = A()
477 a = A()
478 self.assertEqual(a.klass, B)
478 self.assertEqual(a.klass, B)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
480 a.klass = C
480 a.klass = C
481 self.assertEqual(a.klass, C)
481 self.assertEqual(a.klass, C)
482
482
483 def test_validate_klass(self):
483 def test_validate_klass(self):
484
484
485 class A(HasTraits):
485 class A(HasTraits):
486 klass = Type('no strings allowed')
486 klass = Type('no strings allowed')
487
487
488 self.assertRaises(ImportError, A)
488 self.assertRaises(ImportError, A)
489
489
490 class A(HasTraits):
490 class A(HasTraits):
491 klass = Type('rub.adub.Duck')
491 klass = Type('rub.adub.Duck')
492
492
493 self.assertRaises(ImportError, A)
493 self.assertRaises(ImportError, A)
494
494
495 def test_validate_default(self):
495 def test_validate_default(self):
496
496
497 class B(object): pass
497 class B(object): pass
498 class A(HasTraits):
498 class A(HasTraits):
499 klass = Type('bad default', B)
499 klass = Type('bad default', B)
500
500
501 self.assertRaises(ImportError, A)
501 self.assertRaises(ImportError, A)
502
502
503 class C(HasTraits):
503 class C(HasTraits):
504 klass = Type(None, B)
504 klass = Type(None, B)
505
505
506 self.assertRaises(TraitError, C)
506 self.assertRaises(TraitError, C)
507
507
508 def test_str_klass(self):
508 def test_str_klass(self):
509
509
510 class A(HasTraits):
510 class A(HasTraits):
511 klass = Type('IPython.utils.ipstruct.Struct')
511 klass = Type('IPython.utils.ipstruct.Struct')
512
512
513 from IPython.utils.ipstruct import Struct
513 from IPython.utils.ipstruct import Struct
514 a = A()
514 a = A()
515 a.klass = Struct
515 a.klass = Struct
516 self.assertEqual(a.klass, Struct)
516 self.assertEqual(a.klass, Struct)
517
517
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
519
519
520 def test_set_str_klass(self):
520 def test_set_str_klass(self):
521
521
522 class A(HasTraits):
522 class A(HasTraits):
523 klass = Type()
523 klass = Type()
524
524
525 a = A(klass='IPython.utils.ipstruct.Struct')
525 a = A(klass='IPython.utils.ipstruct.Struct')
526 from IPython.utils.ipstruct import Struct
526 from IPython.utils.ipstruct import Struct
527 self.assertEqual(a.klass, Struct)
527 self.assertEqual(a.klass, Struct)
528
528
529 class TestInstance(TestCase):
529 class TestInstance(TestCase):
530
530
531 def test_basic(self):
531 def test_basic(self):
532 class Foo(object): pass
532 class Foo(object): pass
533 class Bar(Foo): pass
533 class Bar(Foo): pass
534 class Bah(object): pass
534 class Bah(object): pass
535
535
536 class A(HasTraits):
536 class A(HasTraits):
537 inst = Instance(Foo, allow_none=True)
537 inst = Instance(Foo, allow_none=True)
538
538
539 a = A()
539 a = A()
540 self.assertTrue(a.inst is None)
540 self.assertTrue(a.inst is None)
541 a.inst = Foo()
541 a.inst = Foo()
542 self.assertTrue(isinstance(a.inst, Foo))
542 self.assertTrue(isinstance(a.inst, Foo))
543 a.inst = Bar()
543 a.inst = Bar()
544 self.assertTrue(isinstance(a.inst, Foo))
544 self.assertTrue(isinstance(a.inst, Foo))
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
548
548
549 def test_default_klass(self):
549 def test_default_klass(self):
550 class Foo(object): pass
550 class Foo(object): pass
551 class Bar(Foo): pass
551 class Bar(Foo): pass
552 class Bah(object): pass
552 class Bah(object): pass
553
553
554 class FooInstance(Instance):
554 class FooInstance(Instance):
555 klass = Foo
555 klass = Foo
556
556
557 class A(HasTraits):
557 class A(HasTraits):
558 inst = FooInstance(allow_none=True)
558 inst = FooInstance(allow_none=True)
559
559
560 a = A()
560 a = A()
561 self.assertTrue(a.inst is None)
561 self.assertTrue(a.inst is None)
562 a.inst = Foo()
562 a.inst = Foo()
563 self.assertTrue(isinstance(a.inst, Foo))
563 self.assertTrue(isinstance(a.inst, Foo))
564 a.inst = Bar()
564 a.inst = Bar()
565 self.assertTrue(isinstance(a.inst, Foo))
565 self.assertTrue(isinstance(a.inst, Foo))
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
569
569
570 def test_unique_default_value(self):
570 def test_unique_default_value(self):
571 class Foo(object): pass
571 class Foo(object): pass
572 class A(HasTraits):
572 class A(HasTraits):
573 inst = Instance(Foo,(),{})
573 inst = Instance(Foo,(),{})
574
574
575 a = A()
575 a = A()
576 b = A()
576 b = A()
577 self.assertTrue(a.inst is not b.inst)
577 self.assertTrue(a.inst is not b.inst)
578
578
579 def test_args_kw(self):
579 def test_args_kw(self):
580 class Foo(object):
580 class Foo(object):
581 def __init__(self, c): self.c = c
581 def __init__(self, c): self.c = c
582 class Bar(object): pass
582 class Bar(object): pass
583 class Bah(object):
583 class Bah(object):
584 def __init__(self, c, d):
584 def __init__(self, c, d):
585 self.c = c; self.d = d
585 self.c = c; self.d = d
586
586
587 class A(HasTraits):
587 class A(HasTraits):
588 inst = Instance(Foo, (10,))
588 inst = Instance(Foo, (10,))
589 a = A()
589 a = A()
590 self.assertEqual(a.inst.c, 10)
590 self.assertEqual(a.inst.c, 10)
591
591
592 class B(HasTraits):
592 class B(HasTraits):
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
594 b = B()
594 b = B()
595 self.assertEqual(b.inst.c, 10)
595 self.assertEqual(b.inst.c, 10)
596 self.assertEqual(b.inst.d, 20)
596 self.assertEqual(b.inst.d, 20)
597
597
598 class C(HasTraits):
598 class C(HasTraits):
599 inst = Instance(Foo, allow_none=True)
599 inst = Instance(Foo, allow_none=True)
600 c = C()
600 c = C()
601 self.assertTrue(c.inst is None)
601 self.assertTrue(c.inst is None)
602
602
603 def test_bad_default(self):
603 def test_bad_default(self):
604 class Foo(object): pass
604 class Foo(object): pass
605
605
606 class A(HasTraits):
606 class A(HasTraits):
607 inst = Instance(Foo)
607 inst = Instance(Foo)
608
608
609 self.assertRaises(TraitError, A)
609 self.assertRaises(TraitError, A)
610
610
611 def test_instance(self):
611 def test_instance(self):
612 class Foo(object): pass
612 class Foo(object): pass
613
613
614 def inner():
614 def inner():
615 class A(HasTraits):
615 class A(HasTraits):
616 inst = Instance(Foo())
616 inst = Instance(Foo())
617
617
618 self.assertRaises(TraitError, inner)
618 self.assertRaises(TraitError, inner)
619
619
620
620
621 class TestThis(TestCase):
621 class TestThis(TestCase):
622
622
623 def test_this_class(self):
623 def test_this_class(self):
624 class Foo(HasTraits):
624 class Foo(HasTraits):
625 this = This
625 this = This
626
626
627 f = Foo()
627 f = Foo()
628 self.assertEqual(f.this, None)
628 self.assertEqual(f.this, None)
629 g = Foo()
629 g = Foo()
630 f.this = g
630 f.this = g
631 self.assertEqual(f.this, g)
631 self.assertEqual(f.this, g)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
633
633
634 def test_this_inst(self):
634 def test_this_inst(self):
635 class Foo(HasTraits):
635 class Foo(HasTraits):
636 this = This()
636 this = This()
637
637
638 f = Foo()
638 f = Foo()
639 f.this = Foo()
639 f.this = Foo()
640 self.assertTrue(isinstance(f.this, Foo))
640 self.assertTrue(isinstance(f.this, Foo))
641
641
642 def test_subclass(self):
642 def test_subclass(self):
643 class Foo(HasTraits):
643 class Foo(HasTraits):
644 t = This()
644 t = This()
645 class Bar(Foo):
645 class Bar(Foo):
646 pass
646 pass
647 f = Foo()
647 f = Foo()
648 b = Bar()
648 b = Bar()
649 f.t = b
649 f.t = b
650 b.t = f
650 b.t = f
651 self.assertEqual(f.t, b)
651 self.assertEqual(f.t, b)
652 self.assertEqual(b.t, f)
652 self.assertEqual(b.t, f)
653
653
654 def test_subclass_override(self):
654 def test_subclass_override(self):
655 class Foo(HasTraits):
655 class Foo(HasTraits):
656 t = This()
656 t = This()
657 class Bar(Foo):
657 class Bar(Foo):
658 t = This()
658 t = This()
659 f = Foo()
659 f = Foo()
660 b = Bar()
660 b = Bar()
661 f.t = b
661 f.t = b
662 self.assertEqual(f.t, b)
662 self.assertEqual(f.t, b)
663 self.assertRaises(TraitError, setattr, b, 't', f)
663 self.assertRaises(TraitError, setattr, b, 't', f)
664
664
665 def test_this_in_container(self):
665 def test_this_in_container(self):
666
666
667 class Tree(HasTraits):
667 class Tree(HasTraits):
668 value = Unicode()
668 value = Unicode()
669 leaves = List(This())
669 leaves = List(This())
670
670
671 tree = Tree(
671 tree = Tree(
672 value='foo',
672 value='foo',
673 leaves=[Tree('bar'), Tree('buzz')]
673 leaves=[Tree('bar'), Tree('buzz')]
674 )
674 )
675
675
676 with self.assertRaises(TraitError):
676 with self.assertRaises(TraitError):
677 tree.leaves = [1, 2]
677 tree.leaves = [1, 2]
678
678
679 class TraitTestBase(TestCase):
679 class TraitTestBase(TestCase):
680 """A best testing class for basic trait types."""
680 """A best testing class for basic trait types."""
681
681
682 def assign(self, value):
682 def assign(self, value):
683 self.obj.value = value
683 self.obj.value = value
684
684
685 def coerce(self, value):
685 def coerce(self, value):
686 return value
686 return value
687
687
688 def test_good_values(self):
688 def test_good_values(self):
689 if hasattr(self, '_good_values'):
689 if hasattr(self, '_good_values'):
690 for value in self._good_values:
690 for value in self._good_values:
691 self.assign(value)
691 self.assign(value)
692 self.assertEqual(self.obj.value, self.coerce(value))
692 self.assertEqual(self.obj.value, self.coerce(value))
693
693
694 def test_bad_values(self):
694 def test_bad_values(self):
695 if hasattr(self, '_bad_values'):
695 if hasattr(self, '_bad_values'):
696 for value in self._bad_values:
696 for value in self._bad_values:
697 try:
697 try:
698 self.assertRaises(TraitError, self.assign, value)
698 self.assertRaises(TraitError, self.assign, value)
699 except AssertionError:
699 except AssertionError:
700 assert False, value
700 assert False, value
701
701
702 def test_default_value(self):
702 def test_default_value(self):
703 if hasattr(self, '_default_value'):
703 if hasattr(self, '_default_value'):
704 self.assertEqual(self._default_value, self.obj.value)
704 self.assertEqual(self._default_value, self.obj.value)
705
705
706 def test_allow_none(self):
706 def test_allow_none(self):
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
708 None in self._bad_values):
708 None in self._bad_values):
709 trait=self.obj.traits()['value']
709 trait=self.obj.traits()['value']
710 try:
710 try:
711 trait.allow_none = True
711 trait.allow_none = True
712 self._bad_values.remove(None)
712 self._bad_values.remove(None)
713 #skip coerce. Allow None casts None to None.
713 #skip coerce. Allow None casts None to None.
714 self.assign(None)
714 self.assign(None)
715 self.assertEqual(self.obj.value,None)
715 self.assertEqual(self.obj.value,None)
716 self.test_good_values()
716 self.test_good_values()
717 self.test_bad_values()
717 self.test_bad_values()
718 finally:
718 finally:
719 #tear down
719 #tear down
720 trait.allow_none = False
720 trait.allow_none = False
721 self._bad_values.append(None)
721 self._bad_values.append(None)
722
722
723 def tearDown(self):
723 def tearDown(self):
724 # restore default value after tests, if set
724 # restore default value after tests, if set
725 if hasattr(self, '_default_value'):
725 if hasattr(self, '_default_value'):
726 self.obj.value = self._default_value
726 self.obj.value = self._default_value
727
727
728
728
729 class AnyTrait(HasTraits):
729 class AnyTrait(HasTraits):
730
730
731 value = Any
731 value = Any
732
732
733 class AnyTraitTest(TraitTestBase):
733 class AnyTraitTest(TraitTestBase):
734
734
735 obj = AnyTrait()
735 obj = AnyTrait()
736
736
737 _default_value = None
737 _default_value = None
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
739 _bad_values = []
739 _bad_values = []
740
740
741 class UnionTrait(HasTraits):
741 class UnionTrait(HasTraits):
742
742
743 value = Union([Type(), Bool()])
743 value = Union([Type(), Bool()])
744
744
745 class UnionTraitTest(TraitTestBase):
745 class UnionTraitTest(TraitTestBase):
746
746
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
748 _good_values = [int, float, True]
748 _good_values = [int, float, True]
749 _bad_values = [[], (0,), 1j]
749 _bad_values = [[], (0,), 1j]
750
750
751 class OrTrait(HasTraits):
751 class OrTrait(HasTraits):
752
752
753 value = Bool() | Unicode()
753 value = Bool() | Unicode()
754
754
755 class OrTraitTest(TraitTestBase):
755 class OrTraitTest(TraitTestBase):
756
756
757 obj = OrTrait()
757 obj = OrTrait()
758 _good_values = [True, False, 'ten']
758 _good_values = [True, False, 'ten']
759 _bad_values = [[], (0,), 1j]
759 _bad_values = [[], (0,), 1j]
760
760
761 class IntTrait(HasTraits):
761 class IntTrait(HasTraits):
762
762
763 value = Int(99)
763 value = Int(99)
764
764
765 class TestInt(TraitTestBase):
765 class TestInt(TraitTestBase):
766
766
767 obj = IntTrait()
767 obj = IntTrait()
768 _default_value = 99
768 _default_value = 99
769 _good_values = [10, -10]
769 _good_values = [10, -10]
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
773 if not py3compat.PY3:
773 if not py3compat.PY3:
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775
775
776
776
777 class LongTrait(HasTraits):
777 class LongTrait(HasTraits):
778
778
779 value = Long(99 if py3compat.PY3 else long(99))
779 value = Long(99 if py3compat.PY3 else long(99))
780
780
781 class TestLong(TraitTestBase):
781 class TestLong(TraitTestBase):
782
782
783 obj = LongTrait()
783 obj = LongTrait()
784
784
785 _default_value = 99 if py3compat.PY3 else long(99)
785 _default_value = 99 if py3compat.PY3 else long(99)
786 _good_values = [10, -10]
786 _good_values = [10, -10]
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
790 u'-10.1']
790 u'-10.1']
791 if not py3compat.PY3:
791 if not py3compat.PY3:
792 # maxint undefined on py3, because int == long
792 # maxint undefined on py3, because int == long
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
794 _bad_values.extend([[long(10)], (long(10),)])
794 _bad_values.extend([[long(10)], (long(10),)])
795
795
796 @skipif(py3compat.PY3, "not relevant on py3")
796 @skipif(py3compat.PY3, "not relevant on py3")
797 def test_cast_small(self):
797 def test_cast_small(self):
798 """Long casts ints to long"""
798 """Long casts ints to long"""
799 self.obj.value = 10
799 self.obj.value = 10
800 self.assertEqual(type(self.obj.value), long)
800 self.assertEqual(type(self.obj.value), long)
801
801
802
802
803 class IntegerTrait(HasTraits):
803 class IntegerTrait(HasTraits):
804 value = Integer(1)
804 value = Integer(1)
805
805
806 class TestInteger(TestLong):
806 class TestInteger(TestLong):
807 obj = IntegerTrait()
807 obj = IntegerTrait()
808 _default_value = 1
808 _default_value = 1
809
809
810 def coerce(self, n):
810 def coerce(self, n):
811 return int(n)
811 return int(n)
812
812
813 @skipif(py3compat.PY3, "not relevant on py3")
813 @skipif(py3compat.PY3, "not relevant on py3")
814 def test_cast_small(self):
814 def test_cast_small(self):
815 """Integer casts small longs to int"""
815 """Integer casts small longs to int"""
816 if py3compat.PY3:
816 if py3compat.PY3:
817 raise SkipTest("not relevant on py3")
817 raise SkipTest("not relevant on py3")
818
818
819 self.obj.value = long(100)
819 self.obj.value = long(100)
820 self.assertEqual(type(self.obj.value), int)
820 self.assertEqual(type(self.obj.value), int)
821
821
822
822
823 class FloatTrait(HasTraits):
823 class FloatTrait(HasTraits):
824
824
825 value = Float(99.0)
825 value = Float(99.0)
826
826
827 class TestFloat(TraitTestBase):
827 class TestFloat(TraitTestBase):
828
828
829 obj = FloatTrait()
829 obj = FloatTrait()
830
830
831 _default_value = 99.0
831 _default_value = 99.0
832 _good_values = [10, -10, 10.1, -10.1]
832 _good_values = [10, -10, 10.1, -10.1]
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
836 if not py3compat.PY3:
836 if not py3compat.PY3:
837 _bad_values.extend([long(10), long(-10)])
837 _bad_values.extend([long(10), long(-10)])
838
838
839
839
840 class ComplexTrait(HasTraits):
840 class ComplexTrait(HasTraits):
841
841
842 value = Complex(99.0-99.0j)
842 value = Complex(99.0-99.0j)
843
843
844 class TestComplex(TraitTestBase):
844 class TestComplex(TraitTestBase):
845
845
846 obj = ComplexTrait()
846 obj = ComplexTrait()
847
847
848 _default_value = 99.0-99.0j
848 _default_value = 99.0-99.0j
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
850 10.1j, 10.1+10.1j, 10.1-10.1j]
850 10.1j, 10.1+10.1j, 10.1-10.1j]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
852 if not py3compat.PY3:
852 if not py3compat.PY3:
853 _bad_values.extend([long(10), long(-10)])
853 _bad_values.extend([long(10), long(-10)])
854
854
855
855
856 class BytesTrait(HasTraits):
856 class BytesTrait(HasTraits):
857
857
858 value = Bytes(b'string')
858 value = Bytes(b'string')
859
859
860 class TestBytes(TraitTestBase):
860 class TestBytes(TraitTestBase):
861
861
862 obj = BytesTrait()
862 obj = BytesTrait()
863
863
864 _default_value = b'string'
864 _default_value = b'string'
865 _good_values = [b'10', b'-10', b'10L',
865 _good_values = [b'10', b'-10', b'10L',
866 b'-10L', b'10.1', b'-10.1', b'string']
866 b'-10L', b'10.1', b'-10.1', b'string']
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
868 ['ten'],{'ten': 10},(10,), None, u'string']
868 ['ten'],{'ten': 10},(10,), None, u'string']
869 if not py3compat.PY3:
869 if not py3compat.PY3:
870 _bad_values.extend([long(10), long(-10)])
870 _bad_values.extend([long(10), long(-10)])
871
871
872
872
873 class UnicodeTrait(HasTraits):
873 class UnicodeTrait(HasTraits):
874
874
875 value = Unicode(u'unicode')
875 value = Unicode(u'unicode')
876
876
877 class TestUnicode(TraitTestBase):
877 class TestUnicode(TraitTestBase):
878
878
879 obj = UnicodeTrait()
879 obj = UnicodeTrait()
880
880
881 _default_value = u'unicode'
881 _default_value = u'unicode'
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
883 '-10.1', '', u'', 'string', u'string', u"€"]
883 '-10.1', '', u'', 'string', u'string', u"€"]
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
886 if not py3compat.PY3:
886 if not py3compat.PY3:
887 _bad_values.extend([long(10), long(-10)])
887 _bad_values.extend([long(10), long(-10)])
888
888
889
889
890 class ObjectNameTrait(HasTraits):
890 class ObjectNameTrait(HasTraits):
891 value = ObjectName("abc")
891 value = ObjectName("abc")
892
892
893 class TestObjectName(TraitTestBase):
893 class TestObjectName(TraitTestBase):
894 obj = ObjectNameTrait()
894 obj = ObjectNameTrait()
895
895
896 _default_value = "abc"
896 _default_value = "abc"
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
899 None, object(), object]
899 None, object(), object]
900 if sys.version_info[0] < 3:
900 if sys.version_info[0] < 3:
901 _bad_values.append(u"ΓΎ")
901 _bad_values.append(u"ΓΎ")
902 else:
902 else:
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
904
904
905
905
906 class DottedObjectNameTrait(HasTraits):
906 class DottedObjectNameTrait(HasTraits):
907 value = DottedObjectName("a.b")
907 value = DottedObjectName("a.b")
908
908
909 class TestDottedObjectName(TraitTestBase):
909 class TestDottedObjectName(TraitTestBase):
910 obj = DottedObjectNameTrait()
910 obj = DottedObjectNameTrait()
911
911
912 _default_value = "a.b"
912 _default_value = "a.b"
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
915 if sys.version_info[0] < 3:
915 if sys.version_info[0] < 3:
916 _bad_values.append(u"t.ΓΎ")
916 _bad_values.append(u"t.ΓΎ")
917 else:
917 else:
918 _good_values.append(u"t.ΓΎ")
918 _good_values.append(u"t.ΓΎ")
919
919
920
920
921 class TCPAddressTrait(HasTraits):
921 class TCPAddressTrait(HasTraits):
922 value = TCPAddress()
922 value = TCPAddress()
923
923
924 class TestTCPAddress(TraitTestBase):
924 class TestTCPAddress(TraitTestBase):
925
925
926 obj = TCPAddressTrait()
926 obj = TCPAddressTrait()
927
927
928 _default_value = ('127.0.0.1',0)
928 _default_value = ('127.0.0.1',0)
929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
931
931
932 class ListTrait(HasTraits):
932 class ListTrait(HasTraits):
933
933
934 value = List(Int)
934 value = List(Int)
935
935
936 class TestList(TraitTestBase):
936 class TestList(TraitTestBase):
937
937
938 obj = ListTrait()
938 obj = ListTrait()
939
939
940 _default_value = []
940 _default_value = []
941 _good_values = [[], [1], list(range(10)), (1,2)]
941 _good_values = [[], [1], list(range(10)), (1,2)]
942 _bad_values = [10, [1,'a'], 'a']
942 _bad_values = [10, [1,'a'], 'a']
943
943
944 def coerce(self, value):
944 def coerce(self, value):
945 if value is not None:
945 if value is not None:
946 value = list(value)
946 value = list(value)
947 return value
947 return value
948
948
949 class Foo(object):
949 class Foo(object):
950 pass
950 pass
951
951
952 class NoneInstanceListTrait(HasTraits):
952 class NoneInstanceListTrait(HasTraits):
953
953
954 value = List(Instance(Foo))
954 value = List(Instance(Foo))
955
955
956 class TestNoneInstanceList(TraitTestBase):
956 class TestNoneInstanceList(TraitTestBase):
957
957
958 obj = NoneInstanceListTrait()
958 obj = NoneInstanceListTrait()
959
959
960 _default_value = []
960 _default_value = []
961 _good_values = [[Foo(), Foo()], []]
961 _good_values = [[Foo(), Foo()], []]
962 _bad_values = [[None], [Foo(), None]]
962 _bad_values = [[None], [Foo(), None]]
963
963
964
964
965 class InstanceListTrait(HasTraits):
965 class InstanceListTrait(HasTraits):
966
966
967 value = List(Instance(__name__+'.Foo'))
967 value = List(Instance(__name__+'.Foo'))
968
968
969 class TestInstanceList(TraitTestBase):
969 class TestInstanceList(TraitTestBase):
970
970
971 obj = InstanceListTrait()
971 obj = InstanceListTrait()
972
972
973 def test_klass(self):
973 def test_klass(self):
974 """Test that the instance klass is properly assigned."""
974 """Test that the instance klass is properly assigned."""
975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
976
976
977 _default_value = []
977 _default_value = []
978 _good_values = [[Foo(), Foo()], []]
978 _good_values = [[Foo(), Foo()], []]
979 _bad_values = [['1', 2,], '1', [Foo], None]
979 _bad_values = [['1', 2,], '1', [Foo], None]
980
980
981 class UnionListTrait(HasTraits):
981 class UnionListTrait(HasTraits):
982
982
983 value = List(Int() | Bool())
983 value = List(Int() | Bool())
984
984
985 class TestUnionListTrait(HasTraits):
985 class TestUnionListTrait(HasTraits):
986
986
987 obj = UnionListTrait()
987 obj = UnionListTrait()
988
988
989 _default_value = []
989 _default_value = []
990 _good_values = [[True, 1], [False, True]]
990 _good_values = [[True, 1], [False, True]]
991 _bad_values = [[1, 'True'], False]
991 _bad_values = [[1, 'True'], False]
992
992
993
993
994 class LenListTrait(HasTraits):
994 class LenListTrait(HasTraits):
995
995
996 value = List(Int, [0], minlen=1, maxlen=2)
996 value = List(Int, [0], minlen=1, maxlen=2)
997
997
998 class TestLenList(TraitTestBase):
998 class TestLenList(TraitTestBase):
999
999
1000 obj = LenListTrait()
1000 obj = LenListTrait()
1001
1001
1002 _default_value = [0]
1002 _default_value = [0]
1003 _good_values = [[1], [1,2], (1,2)]
1003 _good_values = [[1], [1,2], (1,2)]
1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1005
1005
1006 def coerce(self, value):
1006 def coerce(self, value):
1007 if value is not None:
1007 if value is not None:
1008 value = list(value)
1008 value = list(value)
1009 return value
1009 return value
1010
1010
1011 class TupleTrait(HasTraits):
1011 class TupleTrait(HasTraits):
1012
1012
1013 value = Tuple(Int(allow_none=True), default_value=(1,))
1013 value = Tuple(Int(allow_none=True), default_value=(1,))
1014
1014
1015 class TestTupleTrait(TraitTestBase):
1015 class TestTupleTrait(TraitTestBase):
1016
1016
1017 obj = TupleTrait()
1017 obj = TupleTrait()
1018
1018
1019 _default_value = (1,)
1019 _default_value = (1,)
1020 _good_values = [(1,), (0,), [1]]
1020 _good_values = [(1,), (0,), [1]]
1021 _bad_values = [10, (1, 2), ('a'), (), None]
1021 _bad_values = [10, (1, 2), ('a'), (), None]
1022
1022
1023 def coerce(self, value):
1023 def coerce(self, value):
1024 if value is not None:
1024 if value is not None:
1025 value = tuple(value)
1025 value = tuple(value)
1026 return value
1026 return value
1027
1027
1028 def test_invalid_args(self):
1028 def test_invalid_args(self):
1029 self.assertRaises(TypeError, Tuple, 5)
1029 self.assertRaises(TypeError, Tuple, 5)
1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1031 t = Tuple(Int, CBytes, default_value=(1,5))
1031 t = Tuple(Int, CBytes, default_value=(1,5))
1032
1032
1033 class LooseTupleTrait(HasTraits):
1033 class LooseTupleTrait(HasTraits):
1034
1034
1035 value = Tuple((1,2,3))
1035 value = Tuple((1,2,3))
1036
1036
1037 class TestLooseTupleTrait(TraitTestBase):
1037 class TestLooseTupleTrait(TraitTestBase):
1038
1038
1039 obj = LooseTupleTrait()
1039 obj = LooseTupleTrait()
1040
1040
1041 _default_value = (1,2,3)
1041 _default_value = (1,2,3)
1042 _good_values = [(1,), [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1042 _good_values = [(1,), [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1043 _bad_values = [10, 'hello', {}, None]
1043 _bad_values = [10, 'hello', {}, None]
1044
1044
1045 def coerce(self, value):
1045 def coerce(self, value):
1046 if value is not None:
1046 if value is not None:
1047 value = tuple(value)
1047 value = tuple(value)
1048 return value
1048 return value
1049
1049
1050 def test_invalid_args(self):
1050 def test_invalid_args(self):
1051 self.assertRaises(TypeError, Tuple, 5)
1051 self.assertRaises(TypeError, Tuple, 5)
1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1053 t = Tuple(Int, CBytes, default_value=(1,5))
1053 t = Tuple(Int, CBytes, default_value=(1,5))
1054
1054
1055
1055
1056 class MultiTupleTrait(HasTraits):
1056 class MultiTupleTrait(HasTraits):
1057
1057
1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1059
1059
1060 class TestMultiTuple(TraitTestBase):
1060 class TestMultiTuple(TraitTestBase):
1061
1061
1062 obj = MultiTupleTrait()
1062 obj = MultiTupleTrait()
1063
1063
1064 _default_value = (99,b'bottles')
1064 _default_value = (99,b'bottles')
1065 _good_values = [(1,b'a'), (2,b'b')]
1065 _good_values = [(1,b'a'), (2,b'b')]
1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1067
1067
1068 class CRegExpTrait(HasTraits):
1068 class CRegExpTrait(HasTraits):
1069
1069
1070 value = CRegExp(r'')
1070 value = CRegExp(r'')
1071
1071
1072 class TestCRegExp(TraitTestBase):
1072 class TestCRegExp(TraitTestBase):
1073
1073
1074 def coerce(self, value):
1074 def coerce(self, value):
1075 return re.compile(value)
1075 return re.compile(value)
1076
1076
1077 obj = CRegExpTrait()
1077 obj = CRegExpTrait()
1078
1078
1079 _default_value = re.compile(r'')
1079 _default_value = re.compile(r'')
1080 _good_values = [r'\d+', re.compile(r'\d+')]
1080 _good_values = [r'\d+', re.compile(r'\d+')]
1081 _bad_values = ['(', None, ()]
1081 _bad_values = ['(', None, ()]
1082
1082
1083 class DictTrait(HasTraits):
1083 class DictTrait(HasTraits):
1084 value = Dict()
1084 value = Dict()
1085
1085
1086 def test_dict_assignment():
1086 def test_dict_assignment():
1087 d = dict()
1087 d = dict()
1088 c = DictTrait()
1088 c = DictTrait()
1089 c.value = d
1089 c.value = d
1090 d['a'] = 5
1090 d['a'] = 5
1091 nt.assert_equal(d, c.value)
1091 nt.assert_equal(d, c.value)
1092 nt.assert_true(c.value is d)
1092 nt.assert_true(c.value is d)
1093
1093
1094 class ValidatedDictTrait(HasTraits):
1094 class ValidatedDictTrait(HasTraits):
1095
1095
1096 value = Dict(Unicode())
1096 value = Dict(Unicode())
1097
1097
1098 class TestInstanceDict(TraitTestBase):
1098 class TestInstanceDict(TraitTestBase):
1099
1099
1100 obj = ValidatedDictTrait()
1100 obj = ValidatedDictTrait()
1101
1101
1102 _default_value = {}
1102 _default_value = {}
1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1104 _bad_values = [{'0': 0}, {'1': 1}]
1104 _bad_values = [{'0': 0}, {'1': 1}]
1105
1105
1106
1106
1107 def test_dict_default_value():
1107 def test_dict_default_value():
1108 """Check that the `{}` default value of the Dict traitlet constructor is
1108 """Check that the `{}` default value of the Dict traitlet constructor is
1109 actually copied."""
1109 actually copied."""
1110
1110
1111 d1, d2 = Dict(), Dict()
1111 d1, d2 = Dict(), Dict()
1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1113
1113
1114
1114
1115 class TestValidationHook(TestCase):
1115 class TestValidationHook(TestCase):
1116
1116
1117 def test_parity_trait(self):
1117 def test_parity_trait(self):
1118 """Verify that the early validation hook is effective"""
1118 """Verify that the early validation hook is effective"""
1119
1119
1120 class Parity(HasTraits):
1120 class Parity(HasTraits):
1121
1121
1122 value = Int(0)
1122 value = Int(0)
1123 parity = Enum(['odd', 'even'], default_value='even')
1123 parity = Enum(['odd', 'even'], default_value='even')
1124
1124
1125 def _value_validate(self, value, trait):
1125 def _value_validate(self, value, trait):
1126 if self.parity == 'even' and value % 2:
1126 if self.parity == 'even' and value % 2:
1127 raise TraitError('Expected an even number')
1127 raise TraitError('Expected an even number')
1128 if self.parity == 'odd' and (value % 2 == 0):
1128 if self.parity == 'odd' and (value % 2 == 0):
1129 raise TraitError('Expected an odd number')
1129 raise TraitError('Expected an odd number')
1130 return value
1130 return value
1131
1131
1132 u = Parity()
1132 u = Parity()
1133 u.parity = 'odd'
1133 u.parity = 'odd'
1134 u.value = 1 # OK
1134 u.value = 1 # OK
1135 with self.assertRaises(TraitError):
1135 with self.assertRaises(TraitError):
1136 u.value = 2 # Trait Error
1136 u.value = 2 # Trait Error
1137
1137
1138 u.parity = 'even'
1138 u.parity = 'even'
1139 u.value = 2 # OK
1139 u.value = 2 # OK
1140
1140
1141
1141
1142 class TestLink(TestCase):
1142 class TestLink(TestCase):
1143
1143
1144 def test_connect_same(self):
1144 def test_connect_same(self):
1145 """Verify two traitlets of the same type can be linked together using link."""
1145 """Verify two traitlets of the same type can be linked together using link."""
1146
1146
1147 # Create two simple classes with Int traitlets.
1147 # Create two simple classes with Int traitlets.
1148 class A(HasTraits):
1148 class A(HasTraits):
1149 value = Int()
1149 value = Int()
1150 a = A(value=9)
1150 a = A(value=9)
1151 b = A(value=8)
1151 b = A(value=8)
1152
1152
1153 # Conenct the two classes.
1153 # Conenct the two classes.
1154 c = link((a, 'value'), (b, 'value'))
1154 c = link((a, 'value'), (b, 'value'))
1155
1155
1156 # Make sure the values are the same at the point of linking.
1156 # Make sure the values are the same at the point of linking.
1157 self.assertEqual(a.value, b.value)
1157 self.assertEqual(a.value, b.value)
1158
1158
1159 # Change one of the values to make sure they stay in sync.
1159 # Change one of the values to make sure they stay in sync.
1160 a.value = 5
1160 a.value = 5
1161 self.assertEqual(a.value, b.value)
1161 self.assertEqual(a.value, b.value)
1162 b.value = 6
1162 b.value = 6
1163 self.assertEqual(a.value, b.value)
1163 self.assertEqual(a.value, b.value)
1164
1164
1165 def test_link_different(self):
1165 def test_link_different(self):
1166 """Verify two traitlets of different types can be linked together using link."""
1166 """Verify two traitlets of different types can be linked together using link."""
1167
1167
1168 # Create two simple classes with Int traitlets.
1168 # Create two simple classes with Int traitlets.
1169 class A(HasTraits):
1169 class A(HasTraits):
1170 value = Int()
1170 value = Int()
1171 class B(HasTraits):
1171 class B(HasTraits):
1172 count = Int()
1172 count = Int()
1173 a = A(value=9)
1173 a = A(value=9)
1174 b = B(count=8)
1174 b = B(count=8)
1175
1175
1176 # Conenct the two classes.
1176 # Conenct the two classes.
1177 c = link((a, 'value'), (b, 'count'))
1177 c = link((a, 'value'), (b, 'count'))
1178
1178
1179 # Make sure the values are the same at the point of linking.
1179 # Make sure the values are the same at the point of linking.
1180 self.assertEqual(a.value, b.count)
1180 self.assertEqual(a.value, b.count)
1181
1181
1182 # Change one of the values to make sure they stay in sync.
1182 # Change one of the values to make sure they stay in sync.
1183 a.value = 5
1183 a.value = 5
1184 self.assertEqual(a.value, b.count)
1184 self.assertEqual(a.value, b.count)
1185 b.count = 4
1185 b.count = 4
1186 self.assertEqual(a.value, b.count)
1186 self.assertEqual(a.value, b.count)
1187
1187
1188 def test_unlink(self):
1188 def test_unlink(self):
1189 """Verify two linked traitlets can be unlinked."""
1189 """Verify two linked traitlets can be unlinked."""
1190
1190
1191 # Create two simple classes with Int traitlets.
1191 # Create two simple classes with Int traitlets.
1192 class A(HasTraits):
1192 class A(HasTraits):
1193 value = Int()
1193 value = Int()
1194 a = A(value=9)
1194 a = A(value=9)
1195 b = A(value=8)
1195 b = A(value=8)
1196
1196
1197 # Connect the two classes.
1197 # Connect the two classes.
1198 c = link((a, 'value'), (b, 'value'))
1198 c = link((a, 'value'), (b, 'value'))
1199 a.value = 4
1199 a.value = 4
1200 c.unlink()
1200 c.unlink()
1201
1201
1202 # Change one of the values to make sure they don't stay in sync.
1202 # Change one of the values to make sure they don't stay in sync.
1203 a.value = 5
1203 a.value = 5
1204 self.assertNotEqual(a.value, b.value)
1204 self.assertNotEqual(a.value, b.value)
1205
1205
1206 def test_callbacks(self):
1206 def test_callbacks(self):
1207 """Verify two linked traitlets have their callbacks called once."""
1207 """Verify two linked traitlets have their callbacks called once."""
1208
1208
1209 # Create two simple classes with Int traitlets.
1209 # Create two simple classes with Int traitlets.
1210 class A(HasTraits):
1210 class A(HasTraits):
1211 value = Int()
1211 value = Int()
1212 class B(HasTraits):
1212 class B(HasTraits):
1213 count = Int()
1213 count = Int()
1214 a = A(value=9)
1214 a = A(value=9)
1215 b = B(count=8)
1215 b = B(count=8)
1216
1216
1217 # Register callbacks that count.
1217 # Register callbacks that count.
1218 callback_count = []
1218 callback_count = []
1219 def a_callback(name, old, new):
1219 def a_callback(name, old, new):
1220 callback_count.append('a')
1220 callback_count.append('a')
1221 a.on_trait_change(a_callback, 'value')
1221 a.on_trait_change(a_callback, 'value')
1222 def b_callback(name, old, new):
1222 def b_callback(name, old, new):
1223 callback_count.append('b')
1223 callback_count.append('b')
1224 b.on_trait_change(b_callback, 'count')
1224 b.on_trait_change(b_callback, 'count')
1225
1225
1226 # Connect the two classes.
1226 # Connect the two classes.
1227 c = link((a, 'value'), (b, 'count'))
1227 c = link((a, 'value'), (b, 'count'))
1228
1228
1229 # Make sure b's count was set to a's value once.
1229 # Make sure b's count was set to a's value once.
1230 self.assertEqual(''.join(callback_count), 'b')
1230 self.assertEqual(''.join(callback_count), 'b')
1231 del callback_count[:]
1231 del callback_count[:]
1232
1232
1233 # Make sure a's value was set to b's count once.
1233 # Make sure a's value was set to b's count once.
1234 b.count = 5
1234 b.count = 5
1235 self.assertEqual(''.join(callback_count), 'ba')
1235 self.assertEqual(''.join(callback_count), 'ba')
1236 del callback_count[:]
1236 del callback_count[:]
1237
1237
1238 # Make sure b's count was set to a's value once.
1238 # Make sure b's count was set to a's value once.
1239 a.value = 4
1239 a.value = 4
1240 self.assertEqual(''.join(callback_count), 'ab')
1240 self.assertEqual(''.join(callback_count), 'ab')
1241 del callback_count[:]
1241 del callback_count[:]
1242
1242
1243 class TestDirectionalLink(TestCase):
1243 class TestDirectionalLink(TestCase):
1244 def test_connect_same(self):
1244 def test_connect_same(self):
1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1246
1246
1247 # Create two simple classes with Int traitlets.
1247 # Create two simple classes with Int traitlets.
1248 class A(HasTraits):
1248 class A(HasTraits):
1249 value = Int()
1249 value = Int()
1250 a = A(value=9)
1250 a = A(value=9)
1251 b = A(value=8)
1251 b = A(value=8)
1252
1252
1253 # Conenct the two classes.
1253 # Conenct the two classes.
1254 c = directional_link((a, 'value'), (b, 'value'))
1254 c = directional_link((a, 'value'), (b, 'value'))
1255
1255
1256 # Make sure the values are the same at the point of linking.
1256 # Make sure the values are the same at the point of linking.
1257 self.assertEqual(a.value, b.value)
1257 self.assertEqual(a.value, b.value)
1258
1258
1259 # Change one the value of the source and check that it synchronizes the target.
1259 # Change one the value of the source and check that it synchronizes the target.
1260 a.value = 5
1260 a.value = 5
1261 self.assertEqual(b.value, 5)
1261 self.assertEqual(b.value, 5)
1262 # Change one the value of the target and check that it has no impact on the source
1262 # Change one the value of the target and check that it has no impact on the source
1263 b.value = 6
1263 b.value = 6
1264 self.assertEqual(a.value, 5)
1264 self.assertEqual(a.value, 5)
1265
1265
1266 def test_link_different(self):
1266 def test_link_different(self):
1267 """Verify two traitlets of different types can be linked together using link."""
1267 """Verify two traitlets of different types can be linked together using link."""
1268
1268
1269 # Create two simple classes with Int traitlets.
1269 # Create two simple classes with Int traitlets.
1270 class A(HasTraits):
1270 class A(HasTraits):
1271 value = Int()
1271 value = Int()
1272 class B(HasTraits):
1272 class B(HasTraits):
1273 count = Int()
1273 count = Int()
1274 a = A(value=9)
1274 a = A(value=9)
1275 b = B(count=8)
1275 b = B(count=8)
1276
1276
1277 # Conenct the two classes.
1277 # Conenct the two classes.
1278 c = directional_link((a, 'value'), (b, 'count'))
1278 c = directional_link((a, 'value'), (b, 'count'))
1279
1279
1280 # Make sure the values are the same at the point of linking.
1280 # Make sure the values are the same at the point of linking.
1281 self.assertEqual(a.value, b.count)
1281 self.assertEqual(a.value, b.count)
1282
1282
1283 # Change one the value of the source and check that it synchronizes the target.
1283 # Change one the value of the source and check that it synchronizes the target.
1284 a.value = 5
1284 a.value = 5
1285 self.assertEqual(b.count, 5)
1285 self.assertEqual(b.count, 5)
1286 # Change one the value of the target and check that it has no impact on the source
1286 # Change one the value of the target and check that it has no impact on the source
1287 b.value = 6
1287 b.value = 6
1288 self.assertEqual(a.value, 5)
1288 self.assertEqual(a.value, 5)
1289
1289
1290 def test_unlink(self):
1290 def test_unlink(self):
1291 """Verify two linked traitlets can be unlinked."""
1291 """Verify two linked traitlets can be unlinked."""
1292
1292
1293 # Create two simple classes with Int traitlets.
1293 # Create two simple classes with Int traitlets.
1294 class A(HasTraits):
1294 class A(HasTraits):
1295 value = Int()
1295 value = Int()
1296 a = A(value=9)
1296 a = A(value=9)
1297 b = A(value=8)
1297 b = A(value=8)
1298
1298
1299 # Connect the two classes.
1299 # Connect the two classes.
1300 c = directional_link((a, 'value'), (b, 'value'))
1300 c = directional_link((a, 'value'), (b, 'value'))
1301 a.value = 4
1301 a.value = 4
1302 c.unlink()
1302 c.unlink()
1303
1303
1304 # Change one of the values to make sure they don't stay in sync.
1304 # Change one of the values to make sure they don't stay in sync.
1305 a.value = 5
1305 a.value = 5
1306 self.assertNotEqual(a.value, b.value)
1306 self.assertNotEqual(a.value, b.value)
1307
1307
1308 class Pickleable(HasTraits):
1308 class Pickleable(HasTraits):
1309 i = Int()
1309 i = Int()
1310 j = Int()
1310 j = Int()
1311
1311
1312 def _i_default(self):
1312 def _i_default(self):
1313 return 1
1313 return 1
1314
1314
1315 def _i_changed(self, name, old, new):
1315 def _i_changed(self, name, old, new):
1316 self.j = new
1316 self.j = new
1317
1317
1318 def test_pickle_hastraits():
1318 def test_pickle_hastraits():
1319 c = Pickleable()
1319 c = Pickleable()
1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1321 p = pickle.dumps(c, protocol)
1321 p = pickle.dumps(c, protocol)
1322 c2 = pickle.loads(p)
1322 c2 = pickle.loads(p)
1323 nt.assert_equal(c2.i, c.i)
1323 nt.assert_equal(c2.i, c.i)
1324 nt.assert_equal(c2.j, c.j)
1324 nt.assert_equal(c2.j, c.j)
1325
1325
1326 c.i = 5
1326 c.i = 5
1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1328 p = pickle.dumps(c, protocol)
1328 p = pickle.dumps(c, protocol)
1329 c2 = pickle.loads(p)
1329 c2 = pickle.loads(p)
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
1333
1334 def test_hold_trait_notifications():
1334 def test_hold_trait_notifications():
1335 changes = []
1335 changes = []
1336
1336
1337 class Test(HasTraits):
1337 class Test(HasTraits):
1338 a = Integer(0)
1338 a = Integer(0)
1339 b = Integer(0)
1339 b = Integer(0)
1340
1340
1341 def _a_changed(self, name, old, new):
1341 def _a_changed(self, name, old, new):
1342 changes.append((old, new))
1342 changes.append((old, new))
1343
1343
1344 def _b_validate(self, value, trait):
1344 def _b_validate(self, value, trait):
1345 if value != 0:
1345 if value != 0:
1346 raise TraitError('Only 0 is a valid value')
1346 raise TraitError('Only 0 is a valid value')
1347 return value
1347 return value
1348
1348
1349 # Test context manager and nesting
1349 # Test context manager and nesting
1350 t = Test()
1350 t = Test()
1351 with t.hold_trait_notifications():
1351 with t.hold_trait_notifications():
1352 with t.hold_trait_notifications():
1352 with t.hold_trait_notifications():
1353 t.a = 1
1353 t.a = 1
1354 nt.assert_equal(t.a, 1)
1354 nt.assert_equal(t.a, 1)
1355 nt.assert_equal(changes, [])
1355 nt.assert_equal(changes, [])
1356 t.a = 2
1356 t.a = 2
1357 nt.assert_equal(t.a, 2)
1357 nt.assert_equal(t.a, 2)
1358 with t.hold_trait_notifications():
1358 with t.hold_trait_notifications():
1359 t.a = 3
1359 t.a = 3
1360 nt.assert_equal(t.a, 3)
1360 nt.assert_equal(t.a, 3)
1361 nt.assert_equal(changes, [])
1361 nt.assert_equal(changes, [])
1362 t.a = 4
1362 t.a = 4
1363 nt.assert_equal(t.a, 4)
1363 nt.assert_equal(t.a, 4)
1364 nt.assert_equal(changes, [])
1364 nt.assert_equal(changes, [])
1365 t.a = 4
1365 t.a = 4
1366 nt.assert_equal(t.a, 4)
1366 nt.assert_equal(t.a, 4)
1367 nt.assert_equal(changes, [])
1367 nt.assert_equal(changes, [])
1368
1368
1369 nt.assert_equal(changes, [(0, 4)])
1369 nt.assert_equal(changes, [(0, 4)])
1370 # Test roll-back
1370 # Test roll-back
1371 try:
1371 try:
1372 with t.hold_trait_notifications():
1372 with t.hold_trait_notifications():
1373 t.b = 1 # raises a Trait error
1373 t.b = 1 # raises a Trait error
1374 except:
1374 except:
1375 pass
1375 pass
1376 nt.assert_equal(t.b, 0)
1376 nt.assert_equal(t.b, 0)
1377
1377
1378
1378
1379 class OrderTraits(HasTraits):
1379 class OrderTraits(HasTraits):
1380 notified = Dict()
1380 notified = Dict()
1381
1381
1382 a = Unicode()
1382 a = Unicode()
1383 b = Unicode()
1383 b = Unicode()
1384 c = Unicode()
1384 c = Unicode()
1385 d = Unicode()
1385 d = Unicode()
1386 e = Unicode()
1386 e = Unicode()
1387 f = Unicode()
1387 f = Unicode()
1388 g = Unicode()
1388 g = Unicode()
1389 h = Unicode()
1389 h = Unicode()
1390 i = Unicode()
1390 i = Unicode()
1391 j = Unicode()
1391 j = Unicode()
1392 k = Unicode()
1392 k = Unicode()
1393 l = Unicode()
1393 l = Unicode()
1394
1394
1395 def _notify(self, name, old, new):
1395 def _notify(self, name, old, new):
1396 """check the value of all traits when each trait change is triggered
1396 """check the value of all traits when each trait change is triggered
1397
1397
1398 This verifies that the values are not sensitive
1398 This verifies that the values are not sensitive
1399 to dict ordering when loaded from kwargs
1399 to dict ordering when loaded from kwargs
1400 """
1400 """
1401 # check the value of the other traits
1401 # check the value of the other traits
1402 # when a given trait change notification fires
1402 # when a given trait change notification fires
1403 self.notified[name] = {
1403 self.notified[name] = {
1404 c: getattr(self, c) for c in 'abcdefghijkl'
1404 c: getattr(self, c) for c in 'abcdefghijkl'
1405 }
1405 }
1406
1406
1407 def __init__(self, **kwargs):
1407 def __init__(self, **kwargs):
1408 self.on_trait_change(self._notify)
1408 self.on_trait_change(self._notify)
1409 super(OrderTraits, self).__init__(**kwargs)
1409 super(OrderTraits, self).__init__(**kwargs)
1410
1410
1411 def test_notification_order():
1411 def test_notification_order():
1412 d = {c:c for c in 'abcdefghijkl'}
1412 d = {c:c for c in 'abcdefghijkl'}
1413 obj = OrderTraits()
1413 obj = OrderTraits()
1414 nt.assert_equal(obj.notified, {})
1414 nt.assert_equal(obj.notified, {})
1415 obj = OrderTraits(**d)
1415 obj = OrderTraits(**d)
1416 notifications = {
1416 notifications = {
1417 c: d for c in 'abcdefghijkl'
1417 c: d for c in 'abcdefghijkl'
1418 }
1418 }
1419 nt.assert_equal(obj.notified, notifications)
1419 nt.assert_equal(obj.notified, notifications)
1420
1420
1421
1421
1422 class TestEventful(TestCase):
1423
1424 def test_list(self):
1425 """Does the EventfulList work?"""
1426 event_cache = []
1427
1428 class A(HasTraits):
1429 x = EventfulList([c for c in 'abc'])
1430 a = A()
1431 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1432 lambda i, x: event_cache.append('set'), \
1433 lambda i: event_cache.append('del'), \
1434 lambda: event_cache.append('reverse'), \
1435 lambda *p, **k: event_cache.append('sort'))
1436
1437 a.x.remove('c')
1438 # ab
1439 a.x.insert(0, 'z')
1440 # zab
1441 del a.x[1]
1442 # zb
1443 a.x.reverse()
1444 # bz
1445 a.x[1] = 'o'
1446 # bo
1447 a.x.append('a')
1448 # boa
1449 a.x.sort()
1450 # abo
1451
1452 # Were the correct events captured?
1453 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1454
1455 # Is the output correct?
1456 self.assertEqual(a.x, [c for c in 'abo'])
1457
1458 def test_dict(self):
1459 """Does the EventfulDict work?"""
1460 event_cache = []
1461
1462 class A(HasTraits):
1463 x = EventfulDict({c: c for c in 'abc'})
1464 a = A()
1465 a.x.on_events(lambda k, v: event_cache.append('add'), \
1466 lambda k, v: event_cache.append('set'), \
1467 lambda k: event_cache.append('del'))
1468
1469 del a.x['c']
1470 # ab
1471 a.x['z'] = 1
1472 # abz
1473 a.x['z'] = 'z'
1474 # abz
1475 a.x.pop('a')
1476 # bz
1477
1478 # Were the correct events captured?
1479 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1480
1481 # Is the output correct?
1482 self.assertEqual(a.x, {c: c for c in 'bz'})
1483
1422
1484 ###
1423 ###
1485 # Traits for Forward Declaration Tests
1424 # Traits for Forward Declaration Tests
1486 ###
1425 ###
1487 class ForwardDeclaredInstanceTrait(HasTraits):
1426 class ForwardDeclaredInstanceTrait(HasTraits):
1488
1427
1489 value = ForwardDeclaredInstance('ForwardDeclaredBar', allow_none=True)
1428 value = ForwardDeclaredInstance('ForwardDeclaredBar', allow_none=True)
1490
1429
1491 class ForwardDeclaredTypeTrait(HasTraits):
1430 class ForwardDeclaredTypeTrait(HasTraits):
1492
1431
1493 value = ForwardDeclaredType('ForwardDeclaredBar', allow_none=True)
1432 value = ForwardDeclaredType('ForwardDeclaredBar', allow_none=True)
1494
1433
1495 class ForwardDeclaredInstanceListTrait(HasTraits):
1434 class ForwardDeclaredInstanceListTrait(HasTraits):
1496
1435
1497 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1436 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1498
1437
1499 class ForwardDeclaredTypeListTrait(HasTraits):
1438 class ForwardDeclaredTypeListTrait(HasTraits):
1500
1439
1501 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1440 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1502 ###
1441 ###
1503 # End Traits for Forward Declaration Tests
1442 # End Traits for Forward Declaration Tests
1504 ###
1443 ###
1505
1444
1506 ###
1445 ###
1507 # Classes for Forward Declaration Tests
1446 # Classes for Forward Declaration Tests
1508 ###
1447 ###
1509 class ForwardDeclaredBar(object):
1448 class ForwardDeclaredBar(object):
1510 pass
1449 pass
1511
1450
1512 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1451 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1513 pass
1452 pass
1514 ###
1453 ###
1515 # End Classes for Forward Declaration Tests
1454 # End Classes for Forward Declaration Tests
1516 ###
1455 ###
1517
1456
1518 ###
1457 ###
1519 # Forward Declaration Tests
1458 # Forward Declaration Tests
1520 ###
1459 ###
1521 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1460 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1522
1461
1523 obj = ForwardDeclaredInstanceTrait()
1462 obj = ForwardDeclaredInstanceTrait()
1524 _default_value = None
1463 _default_value = None
1525 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1464 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1526 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1465 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1527
1466
1528 class TestForwardDeclaredTypeTrait(TraitTestBase):
1467 class TestForwardDeclaredTypeTrait(TraitTestBase):
1529
1468
1530 obj = ForwardDeclaredTypeTrait()
1469 obj = ForwardDeclaredTypeTrait()
1531 _default_value = None
1470 _default_value = None
1532 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1471 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1533 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1472 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1534
1473
1535 class TestForwardDeclaredInstanceList(TraitTestBase):
1474 class TestForwardDeclaredInstanceList(TraitTestBase):
1536
1475
1537 obj = ForwardDeclaredInstanceListTrait()
1476 obj = ForwardDeclaredInstanceListTrait()
1538
1477
1539 def test_klass(self):
1478 def test_klass(self):
1540 """Test that the instance klass is properly assigned."""
1479 """Test that the instance klass is properly assigned."""
1541 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1480 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1542
1481
1543 _default_value = []
1482 _default_value = []
1544 _good_values = [
1483 _good_values = [
1545 [ForwardDeclaredBar(), ForwardDeclaredBarSub()],
1484 [ForwardDeclaredBar(), ForwardDeclaredBarSub()],
1546 [],
1485 [],
1547 ]
1486 ]
1548 _bad_values = [
1487 _bad_values = [
1549 ForwardDeclaredBar(),
1488 ForwardDeclaredBar(),
1550 [ForwardDeclaredBar(), 3, None],
1489 [ForwardDeclaredBar(), 3, None],
1551 '1',
1490 '1',
1552 # Note that this is the type, not an instance.
1491 # Note that this is the type, not an instance.
1553 [ForwardDeclaredBar],
1492 [ForwardDeclaredBar],
1554 [None],
1493 [None],
1555 None,
1494 None,
1556 ]
1495 ]
1557
1496
1558 class TestForwardDeclaredTypeList(TraitTestBase):
1497 class TestForwardDeclaredTypeList(TraitTestBase):
1559
1498
1560 obj = ForwardDeclaredTypeListTrait()
1499 obj = ForwardDeclaredTypeListTrait()
1561
1500
1562 def test_klass(self):
1501 def test_klass(self):
1563 """Test that the instance klass is properly assigned."""
1502 """Test that the instance klass is properly assigned."""
1564 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1503 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1565
1504
1566 _default_value = []
1505 _default_value = []
1567 _good_values = [
1506 _good_values = [
1568 [ForwardDeclaredBar, ForwardDeclaredBarSub],
1507 [ForwardDeclaredBar, ForwardDeclaredBarSub],
1569 [],
1508 [],
1570 ]
1509 ]
1571 _bad_values = [
1510 _bad_values = [
1572 ForwardDeclaredBar,
1511 ForwardDeclaredBar,
1573 [ForwardDeclaredBar, 3],
1512 [ForwardDeclaredBar, 3],
1574 '1',
1513 '1',
1575 # Note that this is an instance, not the type.
1514 # Note that this is an instance, not the type.
1576 [ForwardDeclaredBar()],
1515 [ForwardDeclaredBar()],
1577 [None],
1516 [None],
1578 None,
1517 None,
1579 ]
1518 ]
1580 ###
1519 ###
1581 # End Forward Declaration Tests
1520 # End Forward Declaration Tests
1582 ###
1521 ###
1583
1522
1584 class TestDynamicTraits(TestCase):
1523 class TestDynamicTraits(TestCase):
1585
1524
1586 def setUp(self):
1525 def setUp(self):
1587 self._notify1 = []
1526 self._notify1 = []
1588
1527
1589 def notify1(self, name, old, new):
1528 def notify1(self, name, old, new):
1590 self._notify1.append((name, old, new))
1529 self._notify1.append((name, old, new))
1591
1530
1592 def test_notify_all(self):
1531 def test_notify_all(self):
1593
1532
1594 class A(HasTraits):
1533 class A(HasTraits):
1595 pass
1534 pass
1596
1535
1597 a = A()
1536 a = A()
1598 self.assertTrue(not hasattr(a, 'x'))
1537 self.assertTrue(not hasattr(a, 'x'))
1599 self.assertTrue(not hasattr(a, 'y'))
1538 self.assertTrue(not hasattr(a, 'y'))
1600
1539
1601 # Dynamically add trait x.
1540 # Dynamically add trait x.
1602 a.add_trait('x', Int())
1541 a.add_trait('x', Int())
1603 self.assertTrue(hasattr(a, 'x'))
1542 self.assertTrue(hasattr(a, 'x'))
1604 self.assertTrue(isinstance(a, (A, )))
1543 self.assertTrue(isinstance(a, (A, )))
1605
1544
1606 # Dynamically add trait y.
1545 # Dynamically add trait y.
1607 a.add_trait('y', Float())
1546 a.add_trait('y', Float())
1608 self.assertTrue(hasattr(a, 'y'))
1547 self.assertTrue(hasattr(a, 'y'))
1609 self.assertTrue(isinstance(a, (A, )))
1548 self.assertTrue(isinstance(a, (A, )))
1610 self.assertEqual(a.__class__.__name__, A.__name__)
1549 self.assertEqual(a.__class__.__name__, A.__name__)
1611
1550
1612 # Create a new instance and verify that x and y
1551 # Create a new instance and verify that x and y
1613 # aren't defined.
1552 # aren't defined.
1614 b = A()
1553 b = A()
1615 self.assertTrue(not hasattr(b, 'x'))
1554 self.assertTrue(not hasattr(b, 'x'))
1616 self.assertTrue(not hasattr(b, 'y'))
1555 self.assertTrue(not hasattr(b, 'y'))
1617
1556
1618 # Verify that notification works like normal.
1557 # Verify that notification works like normal.
1619 a.on_trait_change(self.notify1)
1558 a.on_trait_change(self.notify1)
1620 a.x = 0
1559 a.x = 0
1621 self.assertEqual(len(self._notify1), 0)
1560 self.assertEqual(len(self._notify1), 0)
1622 a.y = 0.0
1561 a.y = 0.0
1623 self.assertEqual(len(self._notify1), 0)
1562 self.assertEqual(len(self._notify1), 0)
1624 a.x = 10
1563 a.x = 10
1625 self.assertTrue(('x', 0, 10) in self._notify1)
1564 self.assertTrue(('x', 0, 10) in self._notify1)
1626 a.y = 10.0
1565 a.y = 10.0
1627 self.assertTrue(('y', 0.0, 10.0) in self._notify1)
1566 self.assertTrue(('y', 0.0, 10.0) in self._notify1)
1628 self.assertRaises(TraitError, setattr, a, 'x', 'bad string')
1567 self.assertRaises(TraitError, setattr, a, 'x', 'bad string')
1629 self.assertRaises(TraitError, setattr, a, 'y', 'bad string')
1568 self.assertRaises(TraitError, setattr, a, 'y', 'bad string')
1630 self._notify1 = []
1569 self._notify1 = []
1631 a.on_trait_change(self.notify1, remove=True)
1570 a.on_trait_change(self.notify1, remove=True)
1632 a.x = 20
1571 a.x = 20
1633 a.y = 20.0
1572 a.y = 20.0
1634 self.assertEqual(len(self._notify1), 0)
1573 self.assertEqual(len(self._notify1), 0)
@@ -1,1868 +1,1825 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A lightweight Traits like module.
3 A lightweight Traits like module.
4
4
5 This is designed to provide a lightweight, simple, pure Python version of
5 This is designed to provide a lightweight, simple, pure Python version of
6 many of the capabilities of enthought.traits. This includes:
6 many of the capabilities of enthought.traits. This includes:
7
7
8 * Validation
8 * Validation
9 * Type specification with defaults
9 * Type specification with defaults
10 * Static and dynamic notification
10 * Static and dynamic notification
11 * Basic predefined types
11 * Basic predefined types
12 * An API that is similar to enthought.traits
12 * An API that is similar to enthought.traits
13
13
14 We don't support:
14 We don't support:
15
15
16 * Delegation
16 * Delegation
17 * Automatic GUI generation
17 * Automatic GUI generation
18 * A full set of trait types. Most importantly, we don't provide container
18 * A full set of trait types. Most importantly, we don't provide container
19 traits (list, dict, tuple) that can trigger notifications if their
19 traits (list, dict, tuple) that can trigger notifications if their
20 contents change.
20 contents change.
21 * API compatibility with enthought.traits
21 * API compatibility with enthought.traits
22
22
23 There are also some important difference in our design:
23 There are also some important difference in our design:
24
24
25 * enthought.traits does not validate default values. We do.
25 * enthought.traits does not validate default values. We do.
26
26
27 We choose to create this module because we need these capabilities, but
27 We choose to create this module because we need these capabilities, but
28 we need them to be pure Python so they work in all Python implementations,
28 we need them to be pure Python so they work in all Python implementations,
29 including Jython and IronPython.
29 including Jython and IronPython.
30
30
31 Inheritance diagram:
31 Inheritance diagram:
32
32
33 .. inheritance-diagram:: IPython.utils.traitlets
33 .. inheritance-diagram:: IPython.utils.traitlets
34 :parts: 3
34 :parts: 3
35 """
35 """
36
36
37 # Copyright (c) IPython Development Team.
37 # Copyright (c) IPython Development Team.
38 # Distributed under the terms of the Modified BSD License.
38 # Distributed under the terms of the Modified BSD License.
39 #
39 #
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 # also under the terms of the Modified BSD License.
41 # also under the terms of the Modified BSD License.
42
42
43 import contextlib
43 import contextlib
44 import inspect
44 import inspect
45 import re
45 import re
46 import sys
46 import sys
47 import types
47 import types
48 from types import FunctionType
48 from types import FunctionType
49 try:
49 try:
50 from types import ClassType, InstanceType
50 from types import ClassType, InstanceType
51 ClassTypes = (ClassType, type)
51 ClassTypes = (ClassType, type)
52 except:
52 except:
53 ClassTypes = (type,)
53 ClassTypes = (type,)
54 from warnings import warn
54 from warnings import warn
55
55
56 from IPython.utils import py3compat
56 from IPython.utils import py3compat
57 from IPython.utils.getargspec import getargspec
57 from IPython.utils.getargspec import getargspec
58 from IPython.utils.importstring import import_item
58 from IPython.utils.importstring import import_item
59 from IPython.utils.py3compat import iteritems, string_types
59 from IPython.utils.py3compat import iteritems, string_types
60
60
61 from . import eventful
62 from .sentinel import Sentinel
61 from .sentinel import Sentinel
63 SequenceTypes = (list, tuple, set, frozenset)
62 SequenceTypes = (list, tuple, set, frozenset)
64
63
65 #-----------------------------------------------------------------------------
64 #-----------------------------------------------------------------------------
66 # Basic classes
65 # Basic classes
67 #-----------------------------------------------------------------------------
66 #-----------------------------------------------------------------------------
68
67
69
68
70 NoDefaultSpecified = Sentinel('NoDefaultSpecified', __name__,
69 NoDefaultSpecified = Sentinel('NoDefaultSpecified', __name__,
71 '''
70 '''
72 Used in Traitlets to specify that no defaults are set in kwargs
71 Used in Traitlets to specify that no defaults are set in kwargs
73 '''
72 '''
74 )
73 )
75
74
76
75
77 class Undefined ( object ): pass
76 class Undefined ( object ): pass
78 Undefined = Undefined()
77 Undefined = Undefined()
79
78
80 class TraitError(Exception):
79 class TraitError(Exception):
81 pass
80 pass
82
81
83 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
84 # Utilities
83 # Utilities
85 #-----------------------------------------------------------------------------
84 #-----------------------------------------------------------------------------
86
85
87
86
88 def class_of ( object ):
87 def class_of ( object ):
89 """ Returns a string containing the class name of an object with the
88 """ Returns a string containing the class name of an object with the
90 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
89 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
91 'a PlotValue').
90 'a PlotValue').
92 """
91 """
93 if isinstance( object, py3compat.string_types ):
92 if isinstance( object, py3compat.string_types ):
94 return add_article( object )
93 return add_article( object )
95
94
96 return add_article( object.__class__.__name__ )
95 return add_article( object.__class__.__name__ )
97
96
98
97
99 def add_article ( name ):
98 def add_article ( name ):
100 """ Returns a string containing the correct indefinite article ('a' or 'an')
99 """ Returns a string containing the correct indefinite article ('a' or 'an')
101 prefixed to the specified string.
100 prefixed to the specified string.
102 """
101 """
103 if name[:1].lower() in 'aeiou':
102 if name[:1].lower() in 'aeiou':
104 return 'an ' + name
103 return 'an ' + name
105
104
106 return 'a ' + name
105 return 'a ' + name
107
106
108
107
109 def repr_type(obj):
108 def repr_type(obj):
110 """ Return a string representation of a value and its type for readable
109 """ Return a string representation of a value and its type for readable
111 error messages.
110 error messages.
112 """
111 """
113 the_type = type(obj)
112 the_type = type(obj)
114 if (not py3compat.PY3) and the_type is InstanceType:
113 if (not py3compat.PY3) and the_type is InstanceType:
115 # Old-style class.
114 # Old-style class.
116 the_type = obj.__class__
115 the_type = obj.__class__
117 msg = '%r %r' % (obj, the_type)
116 msg = '%r %r' % (obj, the_type)
118 return msg
117 return msg
119
118
120
119
121 def is_trait(t):
120 def is_trait(t):
122 """ Returns whether the given value is an instance or subclass of TraitType.
121 """ Returns whether the given value is an instance or subclass of TraitType.
123 """
122 """
124 return (isinstance(t, TraitType) or
123 return (isinstance(t, TraitType) or
125 (isinstance(t, type) and issubclass(t, TraitType)))
124 (isinstance(t, type) and issubclass(t, TraitType)))
126
125
127
126
128 def parse_notifier_name(name):
127 def parse_notifier_name(name):
129 """Convert the name argument to a list of names.
128 """Convert the name argument to a list of names.
130
129
131 Examples
130 Examples
132 --------
131 --------
133
132
134 >>> parse_notifier_name('a')
133 >>> parse_notifier_name('a')
135 ['a']
134 ['a']
136 >>> parse_notifier_name(['a','b'])
135 >>> parse_notifier_name(['a','b'])
137 ['a', 'b']
136 ['a', 'b']
138 >>> parse_notifier_name(None)
137 >>> parse_notifier_name(None)
139 ['anytrait']
138 ['anytrait']
140 """
139 """
141 if isinstance(name, string_types):
140 if isinstance(name, string_types):
142 return [name]
141 return [name]
143 elif name is None:
142 elif name is None:
144 return ['anytrait']
143 return ['anytrait']
145 elif isinstance(name, (list, tuple)):
144 elif isinstance(name, (list, tuple)):
146 for n in name:
145 for n in name:
147 assert isinstance(n, string_types), "names must be strings"
146 assert isinstance(n, string_types), "names must be strings"
148 return name
147 return name
149
148
150
149
151 class _SimpleTest:
150 class _SimpleTest:
152 def __init__ ( self, value ): self.value = value
151 def __init__ ( self, value ): self.value = value
153 def __call__ ( self, test ):
152 def __call__ ( self, test ):
154 return test == self.value
153 return test == self.value
155 def __repr__(self):
154 def __repr__(self):
156 return "<SimpleTest(%r)" % self.value
155 return "<SimpleTest(%r)" % self.value
157 def __str__(self):
156 def __str__(self):
158 return self.__repr__()
157 return self.__repr__()
159
158
160
159
161 def getmembers(object, predicate=None):
160 def getmembers(object, predicate=None):
162 """A safe version of inspect.getmembers that handles missing attributes.
161 """A safe version of inspect.getmembers that handles missing attributes.
163
162
164 This is useful when there are descriptor based attributes that for
163 This is useful when there are descriptor based attributes that for
165 some reason raise AttributeError even though they exist. This happens
164 some reason raise AttributeError even though they exist. This happens
166 in zope.inteface with the __provides__ attribute.
165 in zope.inteface with the __provides__ attribute.
167 """
166 """
168 results = []
167 results = []
169 for key in dir(object):
168 for key in dir(object):
170 try:
169 try:
171 value = getattr(object, key)
170 value = getattr(object, key)
172 except AttributeError:
171 except AttributeError:
173 pass
172 pass
174 else:
173 else:
175 if not predicate or predicate(value):
174 if not predicate or predicate(value):
176 results.append((key, value))
175 results.append((key, value))
177 results.sort()
176 results.sort()
178 return results
177 return results
179
178
180 def _validate_link(*tuples):
179 def _validate_link(*tuples):
181 """Validate arguments for traitlet link functions"""
180 """Validate arguments for traitlet link functions"""
182 for t in tuples:
181 for t in tuples:
183 if not len(t) == 2:
182 if not len(t) == 2:
184 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
183 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
185 obj, trait_name = t
184 obj, trait_name = t
186 if not isinstance(obj, HasTraits):
185 if not isinstance(obj, HasTraits):
187 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
186 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
188 if not trait_name in obj.traits():
187 if not trait_name in obj.traits():
189 raise TypeError("%r has no trait %r" % (obj, trait_name))
188 raise TypeError("%r has no trait %r" % (obj, trait_name))
190
189
191 class link(object):
190 class link(object):
192 """Link traits from different objects together so they remain in sync.
191 """Link traits from different objects together so they remain in sync.
193
192
194 Parameters
193 Parameters
195 ----------
194 ----------
196 *args : pairs of objects/attributes
195 *args : pairs of objects/attributes
197
196
198 Examples
197 Examples
199 --------
198 --------
200
199
201 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
200 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
202 >>> obj1.value = 5 # updates other objects as well
201 >>> obj1.value = 5 # updates other objects as well
203 """
202 """
204 updating = False
203 updating = False
205 def __init__(self, *args):
204 def __init__(self, *args):
206 if len(args) < 2:
205 if len(args) < 2:
207 raise TypeError('At least two traitlets must be provided.')
206 raise TypeError('At least two traitlets must be provided.')
208 _validate_link(*args)
207 _validate_link(*args)
209
208
210 self.objects = {}
209 self.objects = {}
211
210
212 initial = getattr(args[0][0], args[0][1])
211 initial = getattr(args[0][0], args[0][1])
213 for obj, attr in args:
212 for obj, attr in args:
214 setattr(obj, attr, initial)
213 setattr(obj, attr, initial)
215
214
216 callback = self._make_closure(obj, attr)
215 callback = self._make_closure(obj, attr)
217 obj.on_trait_change(callback, attr)
216 obj.on_trait_change(callback, attr)
218 self.objects[(obj, attr)] = callback
217 self.objects[(obj, attr)] = callback
219
218
220 @contextlib.contextmanager
219 @contextlib.contextmanager
221 def _busy_updating(self):
220 def _busy_updating(self):
222 self.updating = True
221 self.updating = True
223 try:
222 try:
224 yield
223 yield
225 finally:
224 finally:
226 self.updating = False
225 self.updating = False
227
226
228 def _make_closure(self, sending_obj, sending_attr):
227 def _make_closure(self, sending_obj, sending_attr):
229 def update(name, old, new):
228 def update(name, old, new):
230 self._update(sending_obj, sending_attr, new)
229 self._update(sending_obj, sending_attr, new)
231 return update
230 return update
232
231
233 def _update(self, sending_obj, sending_attr, new):
232 def _update(self, sending_obj, sending_attr, new):
234 if self.updating:
233 if self.updating:
235 return
234 return
236 with self._busy_updating():
235 with self._busy_updating():
237 for obj, attr in self.objects.keys():
236 for obj, attr in self.objects.keys():
238 setattr(obj, attr, new)
237 setattr(obj, attr, new)
239
238
240 def unlink(self):
239 def unlink(self):
241 for key, callback in self.objects.items():
240 for key, callback in self.objects.items():
242 (obj, attr) = key
241 (obj, attr) = key
243 obj.on_trait_change(callback, attr, remove=True)
242 obj.on_trait_change(callback, attr, remove=True)
244
243
245 class directional_link(object):
244 class directional_link(object):
246 """Link the trait of a source object with traits of target objects.
245 """Link the trait of a source object with traits of target objects.
247
246
248 Parameters
247 Parameters
249 ----------
248 ----------
250 source : pair of object, name
249 source : pair of object, name
251 targets : pairs of objects/attributes
250 targets : pairs of objects/attributes
252
251
253 Examples
252 Examples
254 --------
253 --------
255
254
256 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
255 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
257 >>> src.value = 5 # updates target objects
256 >>> src.value = 5 # updates target objects
258 >>> tgt1.value = 6 # does not update other objects
257 >>> tgt1.value = 6 # does not update other objects
259 """
258 """
260 updating = False
259 updating = False
261
260
262 def __init__(self, source, *targets):
261 def __init__(self, source, *targets):
263 if len(targets) < 1:
262 if len(targets) < 1:
264 raise TypeError('At least two traitlets must be provided.')
263 raise TypeError('At least two traitlets must be provided.')
265 _validate_link(source, *targets)
264 _validate_link(source, *targets)
266 self.source = source
265 self.source = source
267 self.targets = targets
266 self.targets = targets
268
267
269 # Update current value
268 # Update current value
270 src_attr_value = getattr(source[0], source[1])
269 src_attr_value = getattr(source[0], source[1])
271 for obj, attr in targets:
270 for obj, attr in targets:
272 setattr(obj, attr, src_attr_value)
271 setattr(obj, attr, src_attr_value)
273
272
274 # Wire
273 # Wire
275 self.source[0].on_trait_change(self._update, self.source[1])
274 self.source[0].on_trait_change(self._update, self.source[1])
276
275
277 @contextlib.contextmanager
276 @contextlib.contextmanager
278 def _busy_updating(self):
277 def _busy_updating(self):
279 self.updating = True
278 self.updating = True
280 try:
279 try:
281 yield
280 yield
282 finally:
281 finally:
283 self.updating = False
282 self.updating = False
284
283
285 def _update(self, name, old, new):
284 def _update(self, name, old, new):
286 if self.updating:
285 if self.updating:
287 return
286 return
288 with self._busy_updating():
287 with self._busy_updating():
289 for obj, attr in self.targets:
288 for obj, attr in self.targets:
290 setattr(obj, attr, new)
289 setattr(obj, attr, new)
291
290
292 def unlink(self):
291 def unlink(self):
293 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
292 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
294 self.source = None
293 self.source = None
295 self.targets = []
294 self.targets = []
296
295
297 dlink = directional_link
296 dlink = directional_link
298
297
299
298
300 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
301 # Base TraitType for all traits
300 # Base TraitType for all traits
302 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
303
302
304
303
305 class TraitType(object):
304 class TraitType(object):
306 """A base class for all trait descriptors.
305 """A base class for all trait descriptors.
307
306
308 Notes
307 Notes
309 -----
308 -----
310 Our implementation of traits is based on Python's descriptor
309 Our implementation of traits is based on Python's descriptor
311 prototol. This class is the base class for all such descriptors. The
310 prototol. This class is the base class for all such descriptors. The
312 only magic we use is a custom metaclass for the main :class:`HasTraits`
311 only magic we use is a custom metaclass for the main :class:`HasTraits`
313 class that does the following:
312 class that does the following:
314
313
315 1. Sets the :attr:`name` attribute of every :class:`TraitType`
314 1. Sets the :attr:`name` attribute of every :class:`TraitType`
316 instance in the class dict to the name of the attribute.
315 instance in the class dict to the name of the attribute.
317 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
316 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
318 instance in the class dict to the *class* that declared the trait.
317 instance in the class dict to the *class* that declared the trait.
319 This is used by the :class:`This` trait to allow subclasses to
318 This is used by the :class:`This` trait to allow subclasses to
320 accept superclasses for :class:`This` values.
319 accept superclasses for :class:`This` values.
321 """
320 """
322
321
323 metadata = {}
322 metadata = {}
324 default_value = Undefined
323 default_value = Undefined
325 allow_none = False
324 allow_none = False
326 info_text = 'any value'
325 info_text = 'any value'
327
326
328 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
327 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
329 """Create a TraitType.
328 """Create a TraitType.
330 """
329 """
331 if default_value is not NoDefaultSpecified:
330 if default_value is not NoDefaultSpecified:
332 self.default_value = default_value
331 self.default_value = default_value
333 if allow_none is not None:
332 if allow_none is not None:
334 self.allow_none = allow_none
333 self.allow_none = allow_none
335
334
336 if 'default' in metadata:
335 if 'default' in metadata:
337 # Warn the user that they probably meant default_value.
336 # Warn the user that they probably meant default_value.
338 warn(
337 warn(
339 "Parameter 'default' passed to TraitType. "
338 "Parameter 'default' passed to TraitType. "
340 "Did you mean 'default_value'?"
339 "Did you mean 'default_value'?"
341 )
340 )
342
341
343 if len(metadata) > 0:
342 if len(metadata) > 0:
344 if len(self.metadata) > 0:
343 if len(self.metadata) > 0:
345 self._metadata = self.metadata.copy()
344 self._metadata = self.metadata.copy()
346 self._metadata.update(metadata)
345 self._metadata.update(metadata)
347 else:
346 else:
348 self._metadata = metadata
347 self._metadata = metadata
349 else:
348 else:
350 self._metadata = self.metadata
349 self._metadata = self.metadata
351
350
352 self.init()
351 self.init()
353
352
354 def init(self):
353 def init(self):
355 pass
354 pass
356
355
357 def get_default_value(self):
356 def get_default_value(self):
358 """Create a new instance of the default value."""
357 """Create a new instance of the default value."""
359 return self.default_value
358 return self.default_value
360
359
361 def instance_init(self):
360 def instance_init(self):
362 """Part of the initialization which may depends on the underlying
361 """Part of the initialization which may depends on the underlying
363 HasTraits instance.
362 HasTraits instance.
364
363
365 It is typically overloaded for specific trait types.
364 It is typically overloaded for specific trait types.
366
365
367 This method is called by :meth:`HasTraits.__new__` and in the
366 This method is called by :meth:`HasTraits.__new__` and in the
368 :meth:`TraitType.instance_init` method of trait types holding
367 :meth:`TraitType.instance_init` method of trait types holding
369 other trait types.
368 other trait types.
370 """
369 """
371 pass
370 pass
372
371
373 def init_default_value(self, obj):
372 def init_default_value(self, obj):
374 """Instantiate the default value for the trait type.
373 """Instantiate the default value for the trait type.
375
374
376 This method is called by :meth:`TraitType.set_default_value` in the
375 This method is called by :meth:`TraitType.set_default_value` in the
377 case a default value is provided at construction time or later when
376 case a default value is provided at construction time or later when
378 accessing the trait value for the first time in
377 accessing the trait value for the first time in
379 :meth:`HasTraits.__get__`.
378 :meth:`HasTraits.__get__`.
380 """
379 """
381 value = self.get_default_value()
380 value = self.get_default_value()
382 value = self._validate(obj, value)
381 value = self._validate(obj, value)
383 obj._trait_values[self.name] = value
382 obj._trait_values[self.name] = value
384 return value
383 return value
385
384
386 def set_default_value(self, obj):
385 def set_default_value(self, obj):
387 """Set the default value on a per instance basis.
386 """Set the default value on a per instance basis.
388
387
389 This method is called by :meth:`HasTraits.__new__` to instantiate and
388 This method is called by :meth:`HasTraits.__new__` to instantiate and
390 validate the default value. The creation and validation of
389 validate the default value. The creation and validation of
391 default values must be delayed until the parent :class:`HasTraits`
390 default values must be delayed until the parent :class:`HasTraits`
392 class has been instantiated.
391 class has been instantiated.
393 Parameters
392 Parameters
394 ----------
393 ----------
395 obj : :class:`HasTraits` instance
394 obj : :class:`HasTraits` instance
396 The parent :class:`HasTraits` instance that has just been
395 The parent :class:`HasTraits` instance that has just been
397 created.
396 created.
398 """
397 """
399 # Check for a deferred initializer defined in the same class as the
398 # Check for a deferred initializer defined in the same class as the
400 # trait declaration or above.
399 # trait declaration or above.
401 mro = type(obj).mro()
400 mro = type(obj).mro()
402 meth_name = '_%s_default' % self.name
401 meth_name = '_%s_default' % self.name
403 for cls in mro[:mro.index(self.this_class)+1]:
402 for cls in mro[:mro.index(self.this_class)+1]:
404 if meth_name in cls.__dict__:
403 if meth_name in cls.__dict__:
405 break
404 break
406 else:
405 else:
407 # We didn't find one. Do static initialization.
406 # We didn't find one. Do static initialization.
408 self.init_default_value(obj)
407 self.init_default_value(obj)
409 return
408 return
410 # Complete the dynamic initialization.
409 # Complete the dynamic initialization.
411 obj._trait_dyn_inits[self.name] = meth_name
410 obj._trait_dyn_inits[self.name] = meth_name
412
411
413 def __get__(self, obj, cls=None):
412 def __get__(self, obj, cls=None):
414 """Get the value of the trait by self.name for the instance.
413 """Get the value of the trait by self.name for the instance.
415
414
416 Default values are instantiated when :meth:`HasTraits.__new__`
415 Default values are instantiated when :meth:`HasTraits.__new__`
417 is called. Thus by the time this method gets called either the
416 is called. Thus by the time this method gets called either the
418 default value or a user defined value (they called :meth:`__set__`)
417 default value or a user defined value (they called :meth:`__set__`)
419 is in the :class:`HasTraits` instance.
418 is in the :class:`HasTraits` instance.
420 """
419 """
421 if obj is None:
420 if obj is None:
422 return self
421 return self
423 else:
422 else:
424 try:
423 try:
425 value = obj._trait_values[self.name]
424 value = obj._trait_values[self.name]
426 except KeyError:
425 except KeyError:
427 # Check for a dynamic initializer.
426 # Check for a dynamic initializer.
428 if self.name in obj._trait_dyn_inits:
427 if self.name in obj._trait_dyn_inits:
429 method = getattr(obj, obj._trait_dyn_inits[self.name])
428 method = getattr(obj, obj._trait_dyn_inits[self.name])
430 value = method()
429 value = method()
431 # FIXME: Do we really validate here?
430 # FIXME: Do we really validate here?
432 value = self._validate(obj, value)
431 value = self._validate(obj, value)
433 obj._trait_values[self.name] = value
432 obj._trait_values[self.name] = value
434 return value
433 return value
435 else:
434 else:
436 return self.init_default_value(obj)
435 return self.init_default_value(obj)
437 except Exception:
436 except Exception:
438 # HasTraits should call set_default_value to populate
437 # HasTraits should call set_default_value to populate
439 # this. So this should never be reached.
438 # this. So this should never be reached.
440 raise TraitError('Unexpected error in TraitType: '
439 raise TraitError('Unexpected error in TraitType: '
441 'default value not set properly')
440 'default value not set properly')
442 else:
441 else:
443 return value
442 return value
444
443
445 def __set__(self, obj, value):
444 def __set__(self, obj, value):
446 new_value = self._validate(obj, value)
445 new_value = self._validate(obj, value)
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 = Undefined
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:
454 silent = bool(old_value == new_value)
453 silent = bool(old_value == new_value)
455 except:
454 except:
456 # if there is an error in comparing, default to notify
455 # if there is an error in comparing, default to notify
457 silent = False
456 silent = False
458 if silent is not True:
457 if silent is not True:
459 # we explicitly compare silent to True just in case the equality
458 # we explicitly compare silent to True just in case the equality
460 # comparison above returns something other than True/False
459 # comparison above returns something other than True/False
461 obj._notify_trait(self.name, old_value, new_value)
460 obj._notify_trait(self.name, old_value, new_value)
462
461
463 def _validate(self, obj, value):
462 def _validate(self, obj, value):
464 if value is None and self.allow_none:
463 if value is None and self.allow_none:
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 if obj._cross_validation_lock is False:
467 if obj._cross_validation_lock is False:
469 value = self._cross_validate(obj, value)
468 value = self._cross_validate(obj, value)
470 return value
469 return value
471
470
472 def _cross_validate(self, obj, value):
471 def _cross_validate(self, obj, value):
473 if hasattr(obj, '_%s_validate' % self.name):
472 if hasattr(obj, '_%s_validate' % self.name):
474 cross_validate = getattr(obj, '_%s_validate' % self.name)
473 cross_validate = getattr(obj, '_%s_validate' % self.name)
475 value = cross_validate(value, self)
474 value = cross_validate(value, self)
476 return value
475 return value
477
476
478 def __or__(self, other):
477 def __or__(self, other):
479 if isinstance(other, Union):
478 if isinstance(other, Union):
480 return Union([self] + other.trait_types)
479 return Union([self] + other.trait_types)
481 else:
480 else:
482 return Union([self, other])
481 return Union([self, other])
483
482
484 def info(self):
483 def info(self):
485 return self.info_text
484 return self.info_text
486
485
487 def error(self, obj, value):
486 def error(self, obj, value):
488 if obj is not None:
487 if obj is not None:
489 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
488 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
490 % (self.name, class_of(obj),
489 % (self.name, class_of(obj),
491 self.info(), repr_type(value))
490 self.info(), repr_type(value))
492 else:
491 else:
493 e = "The '%s' trait must be %s, but a value of %r was specified." \
492 e = "The '%s' trait must be %s, but a value of %r was specified." \
494 % (self.name, self.info(), repr_type(value))
493 % (self.name, self.info(), repr_type(value))
495 raise TraitError(e)
494 raise TraitError(e)
496
495
497 def get_metadata(self, key, default=None):
496 def get_metadata(self, key, default=None):
498 return getattr(self, '_metadata', {}).get(key, default)
497 return getattr(self, '_metadata', {}).get(key, default)
499
498
500 def set_metadata(self, key, value):
499 def set_metadata(self, key, value):
501 getattr(self, '_metadata', {})[key] = value
500 getattr(self, '_metadata', {})[key] = value
502
501
503
502
504 #-----------------------------------------------------------------------------
503 #-----------------------------------------------------------------------------
505 # The HasTraits implementation
504 # The HasTraits implementation
506 #-----------------------------------------------------------------------------
505 #-----------------------------------------------------------------------------
507
506
508
507
509 class MetaHasTraits(type):
508 class MetaHasTraits(type):
510 """A metaclass for HasTraits.
509 """A metaclass for HasTraits.
511
510
512 This metaclass makes sure that any TraitType class attributes are
511 This metaclass makes sure that any TraitType class attributes are
513 instantiated and sets their name attribute.
512 instantiated and sets their name attribute.
514 """
513 """
515
514
516 def __new__(mcls, name, bases, classdict):
515 def __new__(mcls, name, bases, classdict):
517 """Create the HasTraits class.
516 """Create the HasTraits class.
518
517
519 This instantiates all TraitTypes in the class dict and sets their
518 This instantiates all TraitTypes in the class dict and sets their
520 :attr:`name` attribute.
519 :attr:`name` attribute.
521 """
520 """
522 # print "MetaHasTraitlets (mcls, name): ", mcls, name
521 # print "MetaHasTraitlets (mcls, name): ", mcls, name
523 # print "MetaHasTraitlets (bases): ", bases
522 # print "MetaHasTraitlets (bases): ", bases
524 # print "MetaHasTraitlets (classdict): ", classdict
523 # print "MetaHasTraitlets (classdict): ", classdict
525 for k,v in iteritems(classdict):
524 for k,v in iteritems(classdict):
526 if isinstance(v, TraitType):
525 if isinstance(v, TraitType):
527 v.name = k
526 v.name = k
528 elif inspect.isclass(v):
527 elif inspect.isclass(v):
529 if issubclass(v, TraitType):
528 if issubclass(v, TraitType):
530 vinst = v()
529 vinst = v()
531 vinst.name = k
530 vinst.name = k
532 classdict[k] = vinst
531 classdict[k] = vinst
533 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
532 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
534
533
535 def __init__(cls, name, bases, classdict):
534 def __init__(cls, name, bases, classdict):
536 """Finish initializing the HasTraits class.
535 """Finish initializing the HasTraits class.
537
536
538 This sets the :attr:`this_class` attribute of each TraitType in the
537 This sets the :attr:`this_class` attribute of each TraitType in the
539 class dict to the newly created class ``cls``.
538 class dict to the newly created class ``cls``.
540 """
539 """
541 for k, v in iteritems(classdict):
540 for k, v in iteritems(classdict):
542 if isinstance(v, TraitType):
541 if isinstance(v, TraitType):
543 v.this_class = cls
542 v.this_class = cls
544 super(MetaHasTraits, cls).__init__(name, bases, classdict)
543 super(MetaHasTraits, cls).__init__(name, bases, classdict)
545
544
546
545
547 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
546 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
548
547
549 def __new__(cls, *args, **kw):
548 def __new__(cls, *args, **kw):
550 # This is needed because object.__new__ only accepts
549 # This is needed because object.__new__ only accepts
551 # the cls argument.
550 # the cls argument.
552 new_meth = super(HasTraits, cls).__new__
551 new_meth = super(HasTraits, cls).__new__
553 if new_meth is object.__new__:
552 if new_meth is object.__new__:
554 inst = new_meth(cls)
553 inst = new_meth(cls)
555 else:
554 else:
556 inst = new_meth(cls, **kw)
555 inst = new_meth(cls, **kw)
557 inst._trait_values = {}
556 inst._trait_values = {}
558 inst._trait_notifiers = {}
557 inst._trait_notifiers = {}
559 inst._trait_dyn_inits = {}
558 inst._trait_dyn_inits = {}
560 inst._cross_validation_lock = True
559 inst._cross_validation_lock = True
561 # Here we tell all the TraitType instances to set their default
560 # Here we tell all the TraitType instances to set their default
562 # values on the instance.
561 # values on the instance.
563 for key in dir(cls):
562 for key in dir(cls):
564 # Some descriptors raise AttributeError like zope.interface's
563 # Some descriptors raise AttributeError like zope.interface's
565 # __provides__ attributes even though they exist. This causes
564 # __provides__ attributes even though they exist. This causes
566 # AttributeErrors even though they are listed in dir(cls).
565 # AttributeErrors even though they are listed in dir(cls).
567 try:
566 try:
568 value = getattr(cls, key)
567 value = getattr(cls, key)
569 except AttributeError:
568 except AttributeError:
570 pass
569 pass
571 else:
570 else:
572 if isinstance(value, TraitType):
571 if isinstance(value, TraitType):
573 value.instance_init()
572 value.instance_init()
574 if key not in kw:
573 if key not in kw:
575 value.set_default_value(inst)
574 value.set_default_value(inst)
576 inst._cross_validation_lock = False
575 inst._cross_validation_lock = False
577 return inst
576 return inst
578
577
579 def __init__(self, *args, **kw):
578 def __init__(self, *args, **kw):
580 # Allow trait values to be set using keyword arguments.
579 # Allow trait values to be set using keyword arguments.
581 # We need to use setattr for this to trigger validation and
580 # We need to use setattr for this to trigger validation and
582 # notifications.
581 # notifications.
583 with self.hold_trait_notifications():
582 with self.hold_trait_notifications():
584 for key, value in iteritems(kw):
583 for key, value in iteritems(kw):
585 setattr(self, key, value)
584 setattr(self, key, value)
586
585
587 @contextlib.contextmanager
586 @contextlib.contextmanager
588 def hold_trait_notifications(self):
587 def hold_trait_notifications(self):
589 """Context manager for bundling trait change notifications and cross
588 """Context manager for bundling trait change notifications and cross
590 validation.
589 validation.
591
590
592 Use this when doing multiple trait assignments (init, config), to avoid
591 Use this when doing multiple trait assignments (init, config), to avoid
593 race conditions in trait notifiers requesting other trait values.
592 race conditions in trait notifiers requesting other trait values.
594 All trait notifications will fire after all values have been assigned.
593 All trait notifications will fire after all values have been assigned.
595 """
594 """
596 if self._cross_validation_lock is True:
595 if self._cross_validation_lock is True:
597 yield
596 yield
598 return
597 return
599 else:
598 else:
600 cache = {}
599 cache = {}
601 _notify_trait = self._notify_trait
600 _notify_trait = self._notify_trait
602
601
603 def merge(previous, current):
602 def merge(previous, current):
604 """merges notifications of the form (name, old, value)"""
603 """merges notifications of the form (name, old, value)"""
605 if previous is None:
604 if previous is None:
606 return current
605 return current
607 else:
606 else:
608 return (current[0], previous[1], current[2])
607 return (current[0], previous[1], current[2])
609
608
610 def hold(*a):
609 def hold(*a):
611 cache[a[0]] = merge(cache.get(a[0]), a)
610 cache[a[0]] = merge(cache.get(a[0]), a)
612
611
613 try:
612 try:
614 self._notify_trait = hold
613 self._notify_trait = hold
615 self._cross_validation_lock = True
614 self._cross_validation_lock = True
616 yield
615 yield
617 for name in cache:
616 for name in cache:
618 if hasattr(self, '_%s_validate' % name):
617 if hasattr(self, '_%s_validate' % name):
619 cross_validate = getattr(self, '_%s_validate' % name)
618 cross_validate = getattr(self, '_%s_validate' % name)
620 setattr(self, name, cross_validate(getattr(self, name), self))
619 setattr(self, name, cross_validate(getattr(self, name), self))
621 except TraitError as e:
620 except TraitError as e:
622 self._notify_trait = lambda *x: None
621 self._notify_trait = lambda *x: None
623 for name in cache:
622 for name in cache:
624 if cache[name][1] is not Undefined:
623 if cache[name][1] is not Undefined:
625 setattr(self, name, cache[name][1])
624 setattr(self, name, cache[name][1])
626 else:
625 else:
627 delattr(self, name)
626 delattr(self, name)
628 cache = {}
627 cache = {}
629 raise e
628 raise e
630 finally:
629 finally:
631 self._notify_trait = _notify_trait
630 self._notify_trait = _notify_trait
632 self._cross_validation_lock = False
631 self._cross_validation_lock = False
633 if isinstance(_notify_trait, types.MethodType):
632 if isinstance(_notify_trait, types.MethodType):
634 # FIXME: remove when support is bumped to 3.4.
633 # FIXME: remove when support is bumped to 3.4.
635 # when original method is restored,
634 # when original method is restored,
636 # remove the redundant value from __dict__
635 # remove the redundant value from __dict__
637 # (only used to preserve pickleability on Python < 3.4)
636 # (only used to preserve pickleability on Python < 3.4)
638 self.__dict__.pop('_notify_trait', None)
637 self.__dict__.pop('_notify_trait', None)
639
638
640 # trigger delayed notifications
639 # trigger delayed notifications
641 for v in cache.values():
640 for v in cache.values():
642 self._notify_trait(*v)
641 self._notify_trait(*v)
643
642
644 def _notify_trait(self, name, old_value, new_value):
643 def _notify_trait(self, name, old_value, new_value):
645
644
646 # First dynamic ones
645 # First dynamic ones
647 callables = []
646 callables = []
648 callables.extend(self._trait_notifiers.get(name,[]))
647 callables.extend(self._trait_notifiers.get(name,[]))
649 callables.extend(self._trait_notifiers.get('anytrait',[]))
648 callables.extend(self._trait_notifiers.get('anytrait',[]))
650
649
651 # Now static ones
650 # Now static ones
652 try:
651 try:
653 cb = getattr(self, '_%s_changed' % name)
652 cb = getattr(self, '_%s_changed' % name)
654 except:
653 except:
655 pass
654 pass
656 else:
655 else:
657 callables.append(cb)
656 callables.append(cb)
658
657
659 # Call them all now
658 # Call them all now
660 for c in callables:
659 for c in callables:
661 # Traits catches and logs errors here. I allow them to raise
660 # Traits catches and logs errors here. I allow them to raise
662 if callable(c):
661 if callable(c):
663 argspec = getargspec(c)
662 argspec = getargspec(c)
664
663
665 nargs = len(argspec[0])
664 nargs = len(argspec[0])
666 # Bound methods have an additional 'self' argument
665 # Bound methods have an additional 'self' argument
667 # I don't know how to treat unbound methods, but they
666 # I don't know how to treat unbound methods, but they
668 # can't really be used for callbacks.
667 # can't really be used for callbacks.
669 if isinstance(c, types.MethodType):
668 if isinstance(c, types.MethodType):
670 offset = -1
669 offset = -1
671 else:
670 else:
672 offset = 0
671 offset = 0
673 if nargs + offset == 0:
672 if nargs + offset == 0:
674 c()
673 c()
675 elif nargs + offset == 1:
674 elif nargs + offset == 1:
676 c(name)
675 c(name)
677 elif nargs + offset == 2:
676 elif nargs + offset == 2:
678 c(name, new_value)
677 c(name, new_value)
679 elif nargs + offset == 3:
678 elif nargs + offset == 3:
680 c(name, old_value, new_value)
679 c(name, old_value, new_value)
681 else:
680 else:
682 raise TraitError('a trait changed callback '
681 raise TraitError('a trait changed callback '
683 'must have 0-3 arguments.')
682 'must have 0-3 arguments.')
684 else:
683 else:
685 raise TraitError('a trait changed callback '
684 raise TraitError('a trait changed callback '
686 'must be callable.')
685 'must be callable.')
687
686
688
687
689 def _add_notifiers(self, handler, name):
688 def _add_notifiers(self, handler, name):
690 if name not in self._trait_notifiers:
689 if name not in self._trait_notifiers:
691 nlist = []
690 nlist = []
692 self._trait_notifiers[name] = nlist
691 self._trait_notifiers[name] = nlist
693 else:
692 else:
694 nlist = self._trait_notifiers[name]
693 nlist = self._trait_notifiers[name]
695 if handler not in nlist:
694 if handler not in nlist:
696 nlist.append(handler)
695 nlist.append(handler)
697
696
698 def _remove_notifiers(self, handler, name):
697 def _remove_notifiers(self, handler, name):
699 if name in self._trait_notifiers:
698 if name in self._trait_notifiers:
700 nlist = self._trait_notifiers[name]
699 nlist = self._trait_notifiers[name]
701 try:
700 try:
702 index = nlist.index(handler)
701 index = nlist.index(handler)
703 except ValueError:
702 except ValueError:
704 pass
703 pass
705 else:
704 else:
706 del nlist[index]
705 del nlist[index]
707
706
708 def on_trait_change(self, handler, name=None, remove=False):
707 def on_trait_change(self, handler, name=None, remove=False):
709 """Setup a handler to be called when a trait changes.
708 """Setup a handler to be called when a trait changes.
710
709
711 This is used to setup dynamic notifications of trait changes.
710 This is used to setup dynamic notifications of trait changes.
712
711
713 Static handlers can be created by creating methods on a HasTraits
712 Static handlers can be created by creating methods on a HasTraits
714 subclass with the naming convention '_[traitname]_changed'. Thus,
713 subclass with the naming convention '_[traitname]_changed'. Thus,
715 to create static handler for the trait 'a', create the method
714 to create static handler for the trait 'a', create the method
716 _a_changed(self, name, old, new) (fewer arguments can be used, see
715 _a_changed(self, name, old, new) (fewer arguments can be used, see
717 below).
716 below).
718
717
719 Parameters
718 Parameters
720 ----------
719 ----------
721 handler : callable
720 handler : callable
722 A callable that is called when a trait changes. Its
721 A callable that is called when a trait changes. Its
723 signature can be handler(), handler(name), handler(name, new)
722 signature can be handler(), handler(name), handler(name, new)
724 or handler(name, old, new).
723 or handler(name, old, new).
725 name : list, str, None
724 name : list, str, None
726 If None, the handler will apply to all traits. If a list
725 If None, the handler will apply to all traits. If a list
727 of str, handler will apply to all names in the list. If a
726 of str, handler will apply to all names in the list. If a
728 str, the handler will apply just to that name.
727 str, the handler will apply just to that name.
729 remove : bool
728 remove : bool
730 If False (the default), then install the handler. If True
729 If False (the default), then install the handler. If True
731 then unintall it.
730 then unintall it.
732 """
731 """
733 if remove:
732 if remove:
734 names = parse_notifier_name(name)
733 names = parse_notifier_name(name)
735 for n in names:
734 for n in names:
736 self._remove_notifiers(handler, n)
735 self._remove_notifiers(handler, n)
737 else:
736 else:
738 names = parse_notifier_name(name)
737 names = parse_notifier_name(name)
739 for n in names:
738 for n in names:
740 self._add_notifiers(handler, n)
739 self._add_notifiers(handler, n)
741
740
742 @classmethod
741 @classmethod
743 def class_trait_names(cls, **metadata):
742 def class_trait_names(cls, **metadata):
744 """Get a list of all the names of this class' traits.
743 """Get a list of all the names of this class' traits.
745
744
746 This method is just like the :meth:`trait_names` method,
745 This method is just like the :meth:`trait_names` method,
747 but is unbound.
746 but is unbound.
748 """
747 """
749 return cls.class_traits(**metadata).keys()
748 return cls.class_traits(**metadata).keys()
750
749
751 @classmethod
750 @classmethod
752 def class_traits(cls, **metadata):
751 def class_traits(cls, **metadata):
753 """Get a `dict` of all the traits of this class. The dictionary
752 """Get a `dict` of all the traits of this class. The dictionary
754 is keyed on the name and the values are the TraitType objects.
753 is keyed on the name and the values are the TraitType objects.
755
754
756 This method is just like the :meth:`traits` method, but is unbound.
755 This method is just like the :meth:`traits` method, but is unbound.
757
756
758 The TraitTypes returned don't know anything about the values
757 The TraitTypes returned don't know anything about the values
759 that the various HasTrait's instances are holding.
758 that the various HasTrait's instances are holding.
760
759
761 The metadata kwargs allow functions to be passed in which
760 The metadata kwargs allow functions to be passed in which
762 filter traits based on metadata values. The functions should
761 filter traits based on metadata values. The functions should
763 take a single value as an argument and return a boolean. If
762 take a single value as an argument and return a boolean. If
764 any function returns False, then the trait is not included in
763 any function returns False, then the trait is not included in
765 the output. This does not allow for any simple way of
764 the output. This does not allow for any simple way of
766 testing that a metadata name exists and has any
765 testing that a metadata name exists and has any
767 value because get_metadata returns None if a metadata key
766 value because get_metadata returns None if a metadata key
768 doesn't exist.
767 doesn't exist.
769 """
768 """
770 traits = dict([memb for memb in getmembers(cls) if
769 traits = dict([memb for memb in getmembers(cls) if
771 isinstance(memb[1], TraitType)])
770 isinstance(memb[1], TraitType)])
772
771
773 if len(metadata) == 0:
772 if len(metadata) == 0:
774 return traits
773 return traits
775
774
776 for meta_name, meta_eval in metadata.items():
775 for meta_name, meta_eval in metadata.items():
777 if type(meta_eval) is not FunctionType:
776 if type(meta_eval) is not FunctionType:
778 metadata[meta_name] = _SimpleTest(meta_eval)
777 metadata[meta_name] = _SimpleTest(meta_eval)
779
778
780 result = {}
779 result = {}
781 for name, trait in traits.items():
780 for name, trait in traits.items():
782 for meta_name, meta_eval in metadata.items():
781 for meta_name, meta_eval in metadata.items():
783 if not meta_eval(trait.get_metadata(meta_name)):
782 if not meta_eval(trait.get_metadata(meta_name)):
784 break
783 break
785 else:
784 else:
786 result[name] = trait
785 result[name] = trait
787
786
788 return result
787 return result
789
788
790 def trait_names(self, **metadata):
789 def trait_names(self, **metadata):
791 """Get a list of all the names of this class' traits."""
790 """Get a list of all the names of this class' traits."""
792 return self.traits(**metadata).keys()
791 return self.traits(**metadata).keys()
793
792
794 def traits(self, **metadata):
793 def traits(self, **metadata):
795 """Get a `dict` of all the traits of this class. The dictionary
794 """Get a `dict` of all the traits of this class. The dictionary
796 is keyed on the name and the values are the TraitType objects.
795 is keyed on the name and the values are the TraitType objects.
797
796
798 The TraitTypes returned don't know anything about the values
797 The TraitTypes returned don't know anything about the values
799 that the various HasTrait's instances are holding.
798 that the various HasTrait's instances are holding.
800
799
801 The metadata kwargs allow functions to be passed in which
800 The metadata kwargs allow functions to be passed in which
802 filter traits based on metadata values. The functions should
801 filter traits based on metadata values. The functions should
803 take a single value as an argument and return a boolean. If
802 take a single value as an argument and return a boolean. If
804 any function returns False, then the trait is not included in
803 any function returns False, then the trait is not included in
805 the output. This does not allow for any simple way of
804 the output. This does not allow for any simple way of
806 testing that a metadata name exists and has any
805 testing that a metadata name exists and has any
807 value because get_metadata returns None if a metadata key
806 value because get_metadata returns None if a metadata key
808 doesn't exist.
807 doesn't exist.
809 """
808 """
810 traits = dict([memb for memb in getmembers(self.__class__) if
809 traits = dict([memb for memb in getmembers(self.__class__) if
811 isinstance(memb[1], TraitType)])
810 isinstance(memb[1], TraitType)])
812
811
813 if len(metadata) == 0:
812 if len(metadata) == 0:
814 return traits
813 return traits
815
814
816 for meta_name, meta_eval in metadata.items():
815 for meta_name, meta_eval in metadata.items():
817 if type(meta_eval) is not FunctionType:
816 if type(meta_eval) is not FunctionType:
818 metadata[meta_name] = _SimpleTest(meta_eval)
817 metadata[meta_name] = _SimpleTest(meta_eval)
819
818
820 result = {}
819 result = {}
821 for name, trait in traits.items():
820 for name, trait in traits.items():
822 for meta_name, meta_eval in metadata.items():
821 for meta_name, meta_eval in metadata.items():
823 if not meta_eval(trait.get_metadata(meta_name)):
822 if not meta_eval(trait.get_metadata(meta_name)):
824 break
823 break
825 else:
824 else:
826 result[name] = trait
825 result[name] = trait
827
826
828 return result
827 return result
829
828
830 def trait_metadata(self, traitname, key, default=None):
829 def trait_metadata(self, traitname, key, default=None):
831 """Get metadata values for trait by key."""
830 """Get metadata values for trait by key."""
832 try:
831 try:
833 trait = getattr(self.__class__, traitname)
832 trait = getattr(self.__class__, traitname)
834 except AttributeError:
833 except AttributeError:
835 raise TraitError("Class %s does not have a trait named %s" %
834 raise TraitError("Class %s does not have a trait named %s" %
836 (self.__class__.__name__, traitname))
835 (self.__class__.__name__, traitname))
837 else:
836 else:
838 return trait.get_metadata(key, default)
837 return trait.get_metadata(key, default)
839
838
840 def add_trait(self, traitname, trait):
839 def add_trait(self, traitname, trait):
841 """Dynamically add a trait attribute to the HasTraits instance."""
840 """Dynamically add a trait attribute to the HasTraits instance."""
842 self.__class__ = type(self.__class__.__name__, (self.__class__,),
841 self.__class__ = type(self.__class__.__name__, (self.__class__,),
843 {traitname: trait})
842 {traitname: trait})
844 trait.set_default_value(self)
843 trait.set_default_value(self)
845
844
846 #-----------------------------------------------------------------------------
845 #-----------------------------------------------------------------------------
847 # Actual TraitTypes implementations/subclasses
846 # Actual TraitTypes implementations/subclasses
848 #-----------------------------------------------------------------------------
847 #-----------------------------------------------------------------------------
849
848
850 #-----------------------------------------------------------------------------
849 #-----------------------------------------------------------------------------
851 # TraitTypes subclasses for handling classes and instances of classes
850 # TraitTypes subclasses for handling classes and instances of classes
852 #-----------------------------------------------------------------------------
851 #-----------------------------------------------------------------------------
853
852
854
853
855 class ClassBasedTraitType(TraitType):
854 class ClassBasedTraitType(TraitType):
856 """
855 """
857 A trait with error reporting and string -> type resolution for Type,
856 A trait with error reporting and string -> type resolution for Type,
858 Instance and This.
857 Instance and This.
859 """
858 """
860
859
861 def _resolve_string(self, string):
860 def _resolve_string(self, string):
862 """
861 """
863 Resolve a string supplied for a type into an actual object.
862 Resolve a string supplied for a type into an actual object.
864 """
863 """
865 return import_item(string)
864 return import_item(string)
866
865
867 def error(self, obj, value):
866 def error(self, obj, value):
868 kind = type(value)
867 kind = type(value)
869 if (not py3compat.PY3) and kind is InstanceType:
868 if (not py3compat.PY3) and kind is InstanceType:
870 msg = 'class %s' % value.__class__.__name__
869 msg = 'class %s' % value.__class__.__name__
871 else:
870 else:
872 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
871 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
873
872
874 if obj is not None:
873 if obj is not None:
875 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
874 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
876 % (self.name, class_of(obj),
875 % (self.name, class_of(obj),
877 self.info(), msg)
876 self.info(), msg)
878 else:
877 else:
879 e = "The '%s' trait must be %s, but a value of %r was specified." \
878 e = "The '%s' trait must be %s, but a value of %r was specified." \
880 % (self.name, self.info(), msg)
879 % (self.name, self.info(), msg)
881
880
882 raise TraitError(e)
881 raise TraitError(e)
883
882
884
883
885 class Type(ClassBasedTraitType):
884 class Type(ClassBasedTraitType):
886 """A trait whose value must be a subclass of a specified class."""
885 """A trait whose value must be a subclass of a specified class."""
887
886
888 def __init__ (self, default_value=None, klass=None, **metadata):
887 def __init__ (self, default_value=None, klass=None, **metadata):
889 """Construct a Type trait
888 """Construct a Type trait
890
889
891 A Type trait specifies that its values must be subclasses of
890 A Type trait specifies that its values must be subclasses of
892 a particular class.
891 a particular class.
893
892
894 If only ``default_value`` is given, it is used for the ``klass`` as
893 If only ``default_value`` is given, it is used for the ``klass`` as
895 well.
894 well.
896
895
897 Parameters
896 Parameters
898 ----------
897 ----------
899 default_value : class, str or None
898 default_value : class, str or None
900 The default value must be a subclass of klass. If an str,
899 The default value must be a subclass of klass. If an str,
901 the str must be a fully specified class name, like 'foo.bar.Bah'.
900 the str must be a fully specified class name, like 'foo.bar.Bah'.
902 The string is resolved into real class, when the parent
901 The string is resolved into real class, when the parent
903 :class:`HasTraits` class is instantiated.
902 :class:`HasTraits` class is instantiated.
904 klass : class, str, None
903 klass : class, str, None
905 Values of this trait must be a subclass of klass. The klass
904 Values of this trait must be a subclass of klass. The klass
906 may be specified in a string like: 'foo.bar.MyClass'.
905 may be specified in a string like: 'foo.bar.MyClass'.
907 The string is resolved into real class, when the parent
906 The string is resolved into real class, when the parent
908 :class:`HasTraits` class is instantiated.
907 :class:`HasTraits` class is instantiated.
909 allow_none : bool [ default True ]
908 allow_none : bool [ default True ]
910 Indicates whether None is allowed as an assignable value. Even if
909 Indicates whether None is allowed as an assignable value. Even if
911 ``False``, the default value may be ``None``.
910 ``False``, the default value may be ``None``.
912 """
911 """
913 if default_value is None:
912 if default_value is None:
914 if klass is None:
913 if klass is None:
915 klass = object
914 klass = object
916 elif klass is None:
915 elif klass is None:
917 klass = default_value
916 klass = default_value
918
917
919 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
918 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
920 raise TraitError("A Type trait must specify a class.")
919 raise TraitError("A Type trait must specify a class.")
921
920
922 self.klass = klass
921 self.klass = klass
923
922
924 super(Type, self).__init__(default_value, **metadata)
923 super(Type, self).__init__(default_value, **metadata)
925
924
926 def validate(self, obj, value):
925 def validate(self, obj, value):
927 """Validates that the value is a valid object instance."""
926 """Validates that the value is a valid object instance."""
928 if isinstance(value, py3compat.string_types):
927 if isinstance(value, py3compat.string_types):
929 try:
928 try:
930 value = self._resolve_string(value)
929 value = self._resolve_string(value)
931 except ImportError:
930 except ImportError:
932 raise TraitError("The '%s' trait of %s instance must be a type, but "
931 raise TraitError("The '%s' trait of %s instance must be a type, but "
933 "%r could not be imported" % (self.name, obj, value))
932 "%r could not be imported" % (self.name, obj, value))
934 try:
933 try:
935 if issubclass(value, self.klass):
934 if issubclass(value, self.klass):
936 return value
935 return value
937 except:
936 except:
938 pass
937 pass
939
938
940 self.error(obj, value)
939 self.error(obj, value)
941
940
942 def info(self):
941 def info(self):
943 """ Returns a description of the trait."""
942 """ Returns a description of the trait."""
944 if isinstance(self.klass, py3compat.string_types):
943 if isinstance(self.klass, py3compat.string_types):
945 klass = self.klass
944 klass = self.klass
946 else:
945 else:
947 klass = self.klass.__name__
946 klass = self.klass.__name__
948 result = 'a subclass of ' + klass
947 result = 'a subclass of ' + klass
949 if self.allow_none:
948 if self.allow_none:
950 return result + ' or None'
949 return result + ' or None'
951 return result
950 return result
952
951
953 def instance_init(self):
952 def instance_init(self):
954 self._resolve_classes()
953 self._resolve_classes()
955 super(Type, self).instance_init()
954 super(Type, self).instance_init()
956
955
957 def _resolve_classes(self):
956 def _resolve_classes(self):
958 if isinstance(self.klass, py3compat.string_types):
957 if isinstance(self.klass, py3compat.string_types):
959 self.klass = self._resolve_string(self.klass)
958 self.klass = self._resolve_string(self.klass)
960 if isinstance(self.default_value, py3compat.string_types):
959 if isinstance(self.default_value, py3compat.string_types):
961 self.default_value = self._resolve_string(self.default_value)
960 self.default_value = self._resolve_string(self.default_value)
962
961
963 def get_default_value(self):
962 def get_default_value(self):
964 return self.default_value
963 return self.default_value
965
964
966
965
967 class DefaultValueGenerator(object):
966 class DefaultValueGenerator(object):
968 """A class for generating new default value instances."""
967 """A class for generating new default value instances."""
969
968
970 def __init__(self, *args, **kw):
969 def __init__(self, *args, **kw):
971 self.args = args
970 self.args = args
972 self.kw = kw
971 self.kw = kw
973
972
974 def generate(self, klass):
973 def generate(self, klass):
975 return klass(*self.args, **self.kw)
974 return klass(*self.args, **self.kw)
976
975
977
976
978 class Instance(ClassBasedTraitType):
977 class Instance(ClassBasedTraitType):
979 """A trait whose value must be an instance of a specified class.
978 """A trait whose value must be an instance of a specified class.
980
979
981 The value can also be an instance of a subclass of the specified class.
980 The value can also be an instance of a subclass of the specified class.
982
981
983 Subclasses can declare default classes by overriding the klass attribute
982 Subclasses can declare default classes by overriding the klass attribute
984 """
983 """
985
984
986 klass = None
985 klass = None
987
986
988 def __init__(self, klass=None, args=None, kw=None, **metadata):
987 def __init__(self, klass=None, args=None, kw=None, **metadata):
989 """Construct an Instance trait.
988 """Construct an Instance trait.
990
989
991 This trait allows values that are instances of a particular
990 This trait allows values that are instances of a particular
992 class or its subclasses. Our implementation is quite different
991 class or its subclasses. Our implementation is quite different
993 from that of enthough.traits as we don't allow instances to be used
992 from that of enthough.traits as we don't allow instances to be used
994 for klass and we handle the ``args`` and ``kw`` arguments differently.
993 for klass and we handle the ``args`` and ``kw`` arguments differently.
995
994
996 Parameters
995 Parameters
997 ----------
996 ----------
998 klass : class, str
997 klass : class, str
999 The class that forms the basis for the trait. Class names
998 The class that forms the basis for the trait. Class names
1000 can also be specified as strings, like 'foo.bar.Bar'.
999 can also be specified as strings, like 'foo.bar.Bar'.
1001 args : tuple
1000 args : tuple
1002 Positional arguments for generating the default value.
1001 Positional arguments for generating the default value.
1003 kw : dict
1002 kw : dict
1004 Keyword arguments for generating the default value.
1003 Keyword arguments for generating the default value.
1005 allow_none : bool [default True]
1004 allow_none : bool [default True]
1006 Indicates whether None is allowed as a value.
1005 Indicates whether None is allowed as a value.
1007
1006
1008 Notes
1007 Notes
1009 -----
1008 -----
1010 If both ``args`` and ``kw`` are None, then the default value is None.
1009 If both ``args`` and ``kw`` are None, then the default value is None.
1011 If ``args`` is a tuple and ``kw`` is a dict, then the default is
1010 If ``args`` is a tuple and ``kw`` is a dict, then the default is
1012 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
1011 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
1013 None, the None is replaced by ``()`` or ``{}``, respectively.
1012 None, the None is replaced by ``()`` or ``{}``, respectively.
1014 """
1013 """
1015 if klass is None:
1014 if klass is None:
1016 klass = self.klass
1015 klass = self.klass
1017
1016
1018 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
1017 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
1019 self.klass = klass
1018 self.klass = klass
1020 else:
1019 else:
1021 raise TraitError('The klass attribute must be a class'
1020 raise TraitError('The klass attribute must be a class'
1022 ' not: %r' % klass)
1021 ' not: %r' % klass)
1023
1022
1024 # self.klass is a class, so handle default_value
1023 # self.klass is a class, so handle default_value
1025 if args is None and kw is None:
1024 if args is None and kw is None:
1026 default_value = None
1025 default_value = None
1027 else:
1026 else:
1028 if args is None:
1027 if args is None:
1029 # kw is not None
1028 # kw is not None
1030 args = ()
1029 args = ()
1031 elif kw is None:
1030 elif kw is None:
1032 # args is not None
1031 # args is not None
1033 kw = {}
1032 kw = {}
1034
1033
1035 if not isinstance(kw, dict):
1034 if not isinstance(kw, dict):
1036 raise TraitError("The 'kw' argument must be a dict or None.")
1035 raise TraitError("The 'kw' argument must be a dict or None.")
1037 if not isinstance(args, tuple):
1036 if not isinstance(args, tuple):
1038 raise TraitError("The 'args' argument must be a tuple or None.")
1037 raise TraitError("The 'args' argument must be a tuple or None.")
1039
1038
1040 default_value = DefaultValueGenerator(*args, **kw)
1039 default_value = DefaultValueGenerator(*args, **kw)
1041
1040
1042 super(Instance, self).__init__(default_value, **metadata)
1041 super(Instance, self).__init__(default_value, **metadata)
1043
1042
1044 def validate(self, obj, value):
1043 def validate(self, obj, value):
1045 if isinstance(value, self.klass):
1044 if isinstance(value, self.klass):
1046 return value
1045 return value
1047 else:
1046 else:
1048 self.error(obj, value)
1047 self.error(obj, value)
1049
1048
1050 def info(self):
1049 def info(self):
1051 if isinstance(self.klass, py3compat.string_types):
1050 if isinstance(self.klass, py3compat.string_types):
1052 klass = self.klass
1051 klass = self.klass
1053 else:
1052 else:
1054 klass = self.klass.__name__
1053 klass = self.klass.__name__
1055 result = class_of(klass)
1054 result = class_of(klass)
1056 if self.allow_none:
1055 if self.allow_none:
1057 return result + ' or None'
1056 return result + ' or None'
1058
1057
1059 return result
1058 return result
1060
1059
1061 def instance_init(self):
1060 def instance_init(self):
1062 self._resolve_classes()
1061 self._resolve_classes()
1063 super(Instance, self).instance_init()
1062 super(Instance, self).instance_init()
1064
1063
1065 def _resolve_classes(self):
1064 def _resolve_classes(self):
1066 if isinstance(self.klass, py3compat.string_types):
1065 if isinstance(self.klass, py3compat.string_types):
1067 self.klass = self._resolve_string(self.klass)
1066 self.klass = self._resolve_string(self.klass)
1068
1067
1069 def get_default_value(self):
1068 def get_default_value(self):
1070 """Instantiate a default value instance.
1069 """Instantiate a default value instance.
1071
1070
1072 This is called when the containing HasTraits classes'
1071 This is called when the containing HasTraits classes'
1073 :meth:`__new__` method is called to ensure that a unique instance
1072 :meth:`__new__` method is called to ensure that a unique instance
1074 is created for each HasTraits instance.
1073 is created for each HasTraits instance.
1075 """
1074 """
1076 dv = self.default_value
1075 dv = self.default_value
1077 if isinstance(dv, DefaultValueGenerator):
1076 if isinstance(dv, DefaultValueGenerator):
1078 return dv.generate(self.klass)
1077 return dv.generate(self.klass)
1079 else:
1078 else:
1080 return dv
1079 return dv
1081
1080
1082
1081
1083 class ForwardDeclaredMixin(object):
1082 class ForwardDeclaredMixin(object):
1084 """
1083 """
1085 Mixin for forward-declared versions of Instance and Type.
1084 Mixin for forward-declared versions of Instance and Type.
1086 """
1085 """
1087 def _resolve_string(self, string):
1086 def _resolve_string(self, string):
1088 """
1087 """
1089 Find the specified class name by looking for it in the module in which
1088 Find the specified class name by looking for it in the module in which
1090 our this_class attribute was defined.
1089 our this_class attribute was defined.
1091 """
1090 """
1092 modname = self.this_class.__module__
1091 modname = self.this_class.__module__
1093 return import_item('.'.join([modname, string]))
1092 return import_item('.'.join([modname, string]))
1094
1093
1095
1094
1096 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1095 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1097 """
1096 """
1098 Forward-declared version of Type.
1097 Forward-declared version of Type.
1099 """
1098 """
1100 pass
1099 pass
1101
1100
1102
1101
1103 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1102 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1104 """
1103 """
1105 Forward-declared version of Instance.
1104 Forward-declared version of Instance.
1106 """
1105 """
1107 pass
1106 pass
1108
1107
1109
1108
1110 class This(ClassBasedTraitType):
1109 class This(ClassBasedTraitType):
1111 """A trait for instances of the class containing this trait.
1110 """A trait for instances of the class containing this trait.
1112
1111
1113 Because how how and when class bodies are executed, the ``This``
1112 Because how how and when class bodies are executed, the ``This``
1114 trait can only have a default value of None. This, and because we
1113 trait can only have a default value of None. This, and because we
1115 always validate default values, ``allow_none`` is *always* true.
1114 always validate default values, ``allow_none`` is *always* true.
1116 """
1115 """
1117
1116
1118 info_text = 'an instance of the same type as the receiver or None'
1117 info_text = 'an instance of the same type as the receiver or None'
1119
1118
1120 def __init__(self, **metadata):
1119 def __init__(self, **metadata):
1121 super(This, self).__init__(None, **metadata)
1120 super(This, self).__init__(None, **metadata)
1122
1121
1123 def validate(self, obj, value):
1122 def validate(self, obj, value):
1124 # What if value is a superclass of obj.__class__? This is
1123 # What if value is a superclass of obj.__class__? This is
1125 # complicated if it was the superclass that defined the This
1124 # complicated if it was the superclass that defined the This
1126 # trait.
1125 # trait.
1127 if isinstance(value, self.this_class) or (value is None):
1126 if isinstance(value, self.this_class) or (value is None):
1128 return value
1127 return value
1129 else:
1128 else:
1130 self.error(obj, value)
1129 self.error(obj, value)
1131
1130
1132
1131
1133 class Union(TraitType):
1132 class Union(TraitType):
1134 """A trait type representing a Union type."""
1133 """A trait type representing a Union type."""
1135
1134
1136 def __init__(self, trait_types, **metadata):
1135 def __init__(self, trait_types, **metadata):
1137 """Construct a Union trait.
1136 """Construct a Union trait.
1138
1137
1139 This trait allows values that are allowed by at least one of the
1138 This trait allows values that are allowed by at least one of the
1140 specified trait types. A Union traitlet cannot have metadata on
1139 specified trait types. A Union traitlet cannot have metadata on
1141 its own, besides the metadata of the listed types.
1140 its own, besides the metadata of the listed types.
1142
1141
1143 Parameters
1142 Parameters
1144 ----------
1143 ----------
1145 trait_types: sequence
1144 trait_types: sequence
1146 The list of trait types of length at least 1.
1145 The list of trait types of length at least 1.
1147
1146
1148 Notes
1147 Notes
1149 -----
1148 -----
1150 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1149 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1151 with the validation function of Float, then Bool, and finally Int.
1150 with the validation function of Float, then Bool, and finally Int.
1152 """
1151 """
1153 self.trait_types = trait_types
1152 self.trait_types = trait_types
1154 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1153 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1155 self.default_value = self.trait_types[0].get_default_value()
1154 self.default_value = self.trait_types[0].get_default_value()
1156 super(Union, self).__init__(**metadata)
1155 super(Union, self).__init__(**metadata)
1157
1156
1158 def instance_init(self):
1157 def instance_init(self):
1159 for trait_type in self.trait_types:
1158 for trait_type in self.trait_types:
1160 trait_type.name = self.name
1159 trait_type.name = self.name
1161 trait_type.this_class = self.this_class
1160 trait_type.this_class = self.this_class
1162 trait_type.instance_init()
1161 trait_type.instance_init()
1163 super(Union, self).instance_init()
1162 super(Union, self).instance_init()
1164
1163
1165 def validate(self, obj, value):
1164 def validate(self, obj, value):
1166 for trait_type in self.trait_types:
1165 for trait_type in self.trait_types:
1167 try:
1166 try:
1168 v = trait_type._validate(obj, value)
1167 v = trait_type._validate(obj, value)
1169 self._metadata = trait_type._metadata
1168 self._metadata = trait_type._metadata
1170 return v
1169 return v
1171 except TraitError:
1170 except TraitError:
1172 continue
1171 continue
1173 self.error(obj, value)
1172 self.error(obj, value)
1174
1173
1175 def __or__(self, other):
1174 def __or__(self, other):
1176 if isinstance(other, Union):
1175 if isinstance(other, Union):
1177 return Union(self.trait_types + other.trait_types)
1176 return Union(self.trait_types + other.trait_types)
1178 else:
1177 else:
1179 return Union(self.trait_types + [other])
1178 return Union(self.trait_types + [other])
1180
1179
1181 #-----------------------------------------------------------------------------
1180 #-----------------------------------------------------------------------------
1182 # Basic TraitTypes implementations/subclasses
1181 # Basic TraitTypes implementations/subclasses
1183 #-----------------------------------------------------------------------------
1182 #-----------------------------------------------------------------------------
1184
1183
1185
1184
1186 class Any(TraitType):
1185 class Any(TraitType):
1187 default_value = None
1186 default_value = None
1188 info_text = 'any value'
1187 info_text = 'any value'
1189
1188
1190
1189
1191 class Int(TraitType):
1190 class Int(TraitType):
1192 """An int trait."""
1191 """An int trait."""
1193
1192
1194 default_value = 0
1193 default_value = 0
1195 info_text = 'an int'
1194 info_text = 'an int'
1196
1195
1197 def validate(self, obj, value):
1196 def validate(self, obj, value):
1198 if isinstance(value, int):
1197 if isinstance(value, int):
1199 return value
1198 return value
1200 self.error(obj, value)
1199 self.error(obj, value)
1201
1200
1202 class CInt(Int):
1201 class CInt(Int):
1203 """A casting version of the int trait."""
1202 """A casting version of the int trait."""
1204
1203
1205 def validate(self, obj, value):
1204 def validate(self, obj, value):
1206 try:
1205 try:
1207 return int(value)
1206 return int(value)
1208 except:
1207 except:
1209 self.error(obj, value)
1208 self.error(obj, value)
1210
1209
1211 if py3compat.PY3:
1210 if py3compat.PY3:
1212 Long, CLong = Int, CInt
1211 Long, CLong = Int, CInt
1213 Integer = Int
1212 Integer = Int
1214 else:
1213 else:
1215 class Long(TraitType):
1214 class Long(TraitType):
1216 """A long integer trait."""
1215 """A long integer trait."""
1217
1216
1218 default_value = 0
1217 default_value = 0
1219 info_text = 'a long'
1218 info_text = 'a long'
1220
1219
1221 def validate(self, obj, value):
1220 def validate(self, obj, value):
1222 if isinstance(value, long):
1221 if isinstance(value, long):
1223 return value
1222 return value
1224 if isinstance(value, int):
1223 if isinstance(value, int):
1225 return long(value)
1224 return long(value)
1226 self.error(obj, value)
1225 self.error(obj, value)
1227
1226
1228
1227
1229 class CLong(Long):
1228 class CLong(Long):
1230 """A casting version of the long integer trait."""
1229 """A casting version of the long integer trait."""
1231
1230
1232 def validate(self, obj, value):
1231 def validate(self, obj, value):
1233 try:
1232 try:
1234 return long(value)
1233 return long(value)
1235 except:
1234 except:
1236 self.error(obj, value)
1235 self.error(obj, value)
1237
1236
1238 class Integer(TraitType):
1237 class Integer(TraitType):
1239 """An integer trait.
1238 """An integer trait.
1240
1239
1241 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1240 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1242
1241
1243 default_value = 0
1242 default_value = 0
1244 info_text = 'an integer'
1243 info_text = 'an integer'
1245
1244
1246 def validate(self, obj, value):
1245 def validate(self, obj, value):
1247 if isinstance(value, int):
1246 if isinstance(value, int):
1248 return value
1247 return value
1249 if isinstance(value, long):
1248 if isinstance(value, long):
1250 # downcast longs that fit in int:
1249 # downcast longs that fit in int:
1251 # note that int(n > sys.maxint) returns a long, so
1250 # note that int(n > sys.maxint) returns a long, so
1252 # we don't need a condition on this cast
1251 # we don't need a condition on this cast
1253 return int(value)
1252 return int(value)
1254 if sys.platform == "cli":
1253 if sys.platform == "cli":
1255 from System import Int64
1254 from System import Int64
1256 if isinstance(value, Int64):
1255 if isinstance(value, Int64):
1257 return int(value)
1256 return int(value)
1258 self.error(obj, value)
1257 self.error(obj, value)
1259
1258
1260
1259
1261 class Float(TraitType):
1260 class Float(TraitType):
1262 """A float trait."""
1261 """A float trait."""
1263
1262
1264 default_value = 0.0
1263 default_value = 0.0
1265 info_text = 'a float'
1264 info_text = 'a float'
1266
1265
1267 def validate(self, obj, value):
1266 def validate(self, obj, value):
1268 if isinstance(value, float):
1267 if isinstance(value, float):
1269 return value
1268 return value
1270 if isinstance(value, int):
1269 if isinstance(value, int):
1271 return float(value)
1270 return float(value)
1272 self.error(obj, value)
1271 self.error(obj, value)
1273
1272
1274
1273
1275 class CFloat(Float):
1274 class CFloat(Float):
1276 """A casting version of the float trait."""
1275 """A casting version of the float trait."""
1277
1276
1278 def validate(self, obj, value):
1277 def validate(self, obj, value):
1279 try:
1278 try:
1280 return float(value)
1279 return float(value)
1281 except:
1280 except:
1282 self.error(obj, value)
1281 self.error(obj, value)
1283
1282
1284 class Complex(TraitType):
1283 class Complex(TraitType):
1285 """A trait for complex numbers."""
1284 """A trait for complex numbers."""
1286
1285
1287 default_value = 0.0 + 0.0j
1286 default_value = 0.0 + 0.0j
1288 info_text = 'a complex number'
1287 info_text = 'a complex number'
1289
1288
1290 def validate(self, obj, value):
1289 def validate(self, obj, value):
1291 if isinstance(value, complex):
1290 if isinstance(value, complex):
1292 return value
1291 return value
1293 if isinstance(value, (float, int)):
1292 if isinstance(value, (float, int)):
1294 return complex(value)
1293 return complex(value)
1295 self.error(obj, value)
1294 self.error(obj, value)
1296
1295
1297
1296
1298 class CComplex(Complex):
1297 class CComplex(Complex):
1299 """A casting version of the complex number trait."""
1298 """A casting version of the complex number trait."""
1300
1299
1301 def validate (self, obj, value):
1300 def validate (self, obj, value):
1302 try:
1301 try:
1303 return complex(value)
1302 return complex(value)
1304 except:
1303 except:
1305 self.error(obj, value)
1304 self.error(obj, value)
1306
1305
1307 # We should always be explicit about whether we're using bytes or unicode, both
1306 # We should always be explicit about whether we're using bytes or unicode, both
1308 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1307 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1309 # we don't have a Str type.
1308 # we don't have a Str type.
1310 class Bytes(TraitType):
1309 class Bytes(TraitType):
1311 """A trait for byte strings."""
1310 """A trait for byte strings."""
1312
1311
1313 default_value = b''
1312 default_value = b''
1314 info_text = 'a bytes object'
1313 info_text = 'a bytes object'
1315
1314
1316 def validate(self, obj, value):
1315 def validate(self, obj, value):
1317 if isinstance(value, bytes):
1316 if isinstance(value, bytes):
1318 return value
1317 return value
1319 self.error(obj, value)
1318 self.error(obj, value)
1320
1319
1321
1320
1322 class CBytes(Bytes):
1321 class CBytes(Bytes):
1323 """A casting version of the byte string trait."""
1322 """A casting version of the byte string trait."""
1324
1323
1325 def validate(self, obj, value):
1324 def validate(self, obj, value):
1326 try:
1325 try:
1327 return bytes(value)
1326 return bytes(value)
1328 except:
1327 except:
1329 self.error(obj, value)
1328 self.error(obj, value)
1330
1329
1331
1330
1332 class Unicode(TraitType):
1331 class Unicode(TraitType):
1333 """A trait for unicode strings."""
1332 """A trait for unicode strings."""
1334
1333
1335 default_value = u''
1334 default_value = u''
1336 info_text = 'a unicode string'
1335 info_text = 'a unicode string'
1337
1336
1338 def validate(self, obj, value):
1337 def validate(self, obj, value):
1339 if isinstance(value, py3compat.unicode_type):
1338 if isinstance(value, py3compat.unicode_type):
1340 return value
1339 return value
1341 if isinstance(value, bytes):
1340 if isinstance(value, bytes):
1342 try:
1341 try:
1343 return value.decode('ascii', 'strict')
1342 return value.decode('ascii', 'strict')
1344 except UnicodeDecodeError:
1343 except UnicodeDecodeError:
1345 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1344 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1346 raise TraitError(msg.format(value, self.name, class_of(obj)))
1345 raise TraitError(msg.format(value, self.name, class_of(obj)))
1347 self.error(obj, value)
1346 self.error(obj, value)
1348
1347
1349
1348
1350 class CUnicode(Unicode):
1349 class CUnicode(Unicode):
1351 """A casting version of the unicode trait."""
1350 """A casting version of the unicode trait."""
1352
1351
1353 def validate(self, obj, value):
1352 def validate(self, obj, value):
1354 try:
1353 try:
1355 return py3compat.unicode_type(value)
1354 return py3compat.unicode_type(value)
1356 except:
1355 except:
1357 self.error(obj, value)
1356 self.error(obj, value)
1358
1357
1359
1358
1360 class ObjectName(TraitType):
1359 class ObjectName(TraitType):
1361 """A string holding a valid object name in this version of Python.
1360 """A string holding a valid object name in this version of Python.
1362
1361
1363 This does not check that the name exists in any scope."""
1362 This does not check that the name exists in any scope."""
1364 info_text = "a valid object identifier in Python"
1363 info_text = "a valid object identifier in Python"
1365
1364
1366 if py3compat.PY3:
1365 if py3compat.PY3:
1367 # Python 3:
1366 # Python 3:
1368 coerce_str = staticmethod(lambda _,s: s)
1367 coerce_str = staticmethod(lambda _,s: s)
1369
1368
1370 else:
1369 else:
1371 # Python 2:
1370 # Python 2:
1372 def coerce_str(self, obj, value):
1371 def coerce_str(self, obj, value):
1373 "In Python 2, coerce ascii-only unicode to str"
1372 "In Python 2, coerce ascii-only unicode to str"
1374 if isinstance(value, unicode):
1373 if isinstance(value, unicode):
1375 try:
1374 try:
1376 return str(value)
1375 return str(value)
1377 except UnicodeEncodeError:
1376 except UnicodeEncodeError:
1378 self.error(obj, value)
1377 self.error(obj, value)
1379 return value
1378 return value
1380
1379
1381 def validate(self, obj, value):
1380 def validate(self, obj, value):
1382 value = self.coerce_str(obj, value)
1381 value = self.coerce_str(obj, value)
1383
1382
1384 if isinstance(value, string_types) and py3compat.isidentifier(value):
1383 if isinstance(value, string_types) and py3compat.isidentifier(value):
1385 return value
1384 return value
1386 self.error(obj, value)
1385 self.error(obj, value)
1387
1386
1388 class DottedObjectName(ObjectName):
1387 class DottedObjectName(ObjectName):
1389 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1388 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1390 def validate(self, obj, value):
1389 def validate(self, obj, value):
1391 value = self.coerce_str(obj, value)
1390 value = self.coerce_str(obj, value)
1392
1391
1393 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1392 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1394 return value
1393 return value
1395 self.error(obj, value)
1394 self.error(obj, value)
1396
1395
1397
1396
1398 class Bool(TraitType):
1397 class Bool(TraitType):
1399 """A boolean (True, False) trait."""
1398 """A boolean (True, False) trait."""
1400
1399
1401 default_value = False
1400 default_value = False
1402 info_text = 'a boolean'
1401 info_text = 'a boolean'
1403
1402
1404 def validate(self, obj, value):
1403 def validate(self, obj, value):
1405 if isinstance(value, bool):
1404 if isinstance(value, bool):
1406 return value
1405 return value
1407 self.error(obj, value)
1406 self.error(obj, value)
1408
1407
1409
1408
1410 class CBool(Bool):
1409 class CBool(Bool):
1411 """A casting version of the boolean trait."""
1410 """A casting version of the boolean trait."""
1412
1411
1413 def validate(self, obj, value):
1412 def validate(self, obj, value):
1414 try:
1413 try:
1415 return bool(value)
1414 return bool(value)
1416 except:
1415 except:
1417 self.error(obj, value)
1416 self.error(obj, value)
1418
1417
1419
1418
1420 class Enum(TraitType):
1419 class Enum(TraitType):
1421 """An enum that whose value must be in a given sequence."""
1420 """An enum that whose value must be in a given sequence."""
1422
1421
1423 def __init__(self, values, default_value=None, **metadata):
1422 def __init__(self, values, default_value=None, **metadata):
1424 self.values = values
1423 self.values = values
1425 super(Enum, self).__init__(default_value, **metadata)
1424 super(Enum, self).__init__(default_value, **metadata)
1426
1425
1427 def validate(self, obj, value):
1426 def validate(self, obj, value):
1428 if value in self.values:
1427 if value in self.values:
1429 return value
1428 return value
1430 self.error(obj, value)
1429 self.error(obj, value)
1431
1430
1432 def info(self):
1431 def info(self):
1433 """ Returns a description of the trait."""
1432 """ Returns a description of the trait."""
1434 result = 'any of ' + repr(self.values)
1433 result = 'any of ' + repr(self.values)
1435 if self.allow_none:
1434 if self.allow_none:
1436 return result + ' or None'
1435 return result + ' or None'
1437 return result
1436 return result
1438
1437
1439 class CaselessStrEnum(Enum):
1438 class CaselessStrEnum(Enum):
1440 """An enum of strings that are caseless in validate."""
1439 """An enum of strings that are caseless in validate."""
1441
1440
1442 def validate(self, obj, value):
1441 def validate(self, obj, value):
1443 if not isinstance(value, py3compat.string_types):
1442 if not isinstance(value, py3compat.string_types):
1444 self.error(obj, value)
1443 self.error(obj, value)
1445
1444
1446 for v in self.values:
1445 for v in self.values:
1447 if v.lower() == value.lower():
1446 if v.lower() == value.lower():
1448 return v
1447 return v
1449 self.error(obj, value)
1448 self.error(obj, value)
1450
1449
1451 class Container(Instance):
1450 class Container(Instance):
1452 """An instance of a container (list, set, etc.)
1451 """An instance of a container (list, set, etc.)
1453
1452
1454 To be subclassed by overriding klass.
1453 To be subclassed by overriding klass.
1455 """
1454 """
1456 klass = None
1455 klass = None
1457 _cast_types = ()
1456 _cast_types = ()
1458 _valid_defaults = SequenceTypes
1457 _valid_defaults = SequenceTypes
1459 _trait = None
1458 _trait = None
1460
1459
1461 def __init__(self, trait=None, default_value=None, **metadata):
1460 def __init__(self, trait=None, default_value=None, **metadata):
1462 """Create a container trait type from a list, set, or tuple.
1461 """Create a container trait type from a list, set, or tuple.
1463
1462
1464 The default value is created by doing ``List(default_value)``,
1463 The default value is created by doing ``List(default_value)``,
1465 which creates a copy of the ``default_value``.
1464 which creates a copy of the ``default_value``.
1466
1465
1467 ``trait`` can be specified, which restricts the type of elements
1466 ``trait`` can be specified, which restricts the type of elements
1468 in the container to that TraitType.
1467 in the container to that TraitType.
1469
1468
1470 If only one arg is given and it is not a Trait, it is taken as
1469 If only one arg is given and it is not a Trait, it is taken as
1471 ``default_value``:
1470 ``default_value``:
1472
1471
1473 ``c = List([1,2,3])``
1472 ``c = List([1,2,3])``
1474
1473
1475 Parameters
1474 Parameters
1476 ----------
1475 ----------
1477
1476
1478 trait : TraitType [ optional ]
1477 trait : TraitType [ optional ]
1479 the type for restricting the contents of the Container. If unspecified,
1478 the type for restricting the contents of the Container. If unspecified,
1480 types are not checked.
1479 types are not checked.
1481
1480
1482 default_value : SequenceType [ optional ]
1481 default_value : SequenceType [ optional ]
1483 The default value for the Trait. Must be list/tuple/set, and
1482 The default value for the Trait. Must be list/tuple/set, and
1484 will be cast to the container type.
1483 will be cast to the container type.
1485
1484
1486 allow_none : bool [ default False ]
1485 allow_none : bool [ default False ]
1487 Whether to allow the value to be None
1486 Whether to allow the value to be None
1488
1487
1489 **metadata : any
1488 **metadata : any
1490 further keys for extensions to the Trait (e.g. config)
1489 further keys for extensions to the Trait (e.g. config)
1491
1490
1492 """
1491 """
1493 # allow List([values]):
1492 # allow List([values]):
1494 if default_value is None and not is_trait(trait):
1493 if default_value is None and not is_trait(trait):
1495 default_value = trait
1494 default_value = trait
1496 trait = None
1495 trait = None
1497
1496
1498 if default_value is None:
1497 if default_value is None:
1499 args = ()
1498 args = ()
1500 elif isinstance(default_value, self._valid_defaults):
1499 elif isinstance(default_value, self._valid_defaults):
1501 args = (default_value,)
1500 args = (default_value,)
1502 else:
1501 else:
1503 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1502 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1504
1503
1505 if is_trait(trait):
1504 if is_trait(trait):
1506 self._trait = trait() if isinstance(trait, type) else trait
1505 self._trait = trait() if isinstance(trait, type) else trait
1507 self._trait.name = 'element'
1506 self._trait.name = 'element'
1508 elif trait is not None:
1507 elif trait is not None:
1509 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1508 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1510
1509
1511 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1510 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1512
1511
1513 def element_error(self, obj, element, validator):
1512 def element_error(self, obj, element, validator):
1514 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1513 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1515 % (self.name, class_of(obj), validator.info(), repr_type(element))
1514 % (self.name, class_of(obj), validator.info(), repr_type(element))
1516 raise TraitError(e)
1515 raise TraitError(e)
1517
1516
1518 def validate(self, obj, value):
1517 def validate(self, obj, value):
1519 if isinstance(value, self._cast_types):
1518 if isinstance(value, self._cast_types):
1520 value = self.klass(value)
1519 value = self.klass(value)
1521 value = super(Container, self).validate(obj, value)
1520 value = super(Container, self).validate(obj, value)
1522 if value is None:
1521 if value is None:
1523 return value
1522 return value
1524
1523
1525 value = self.validate_elements(obj, value)
1524 value = self.validate_elements(obj, value)
1526
1525
1527 return value
1526 return value
1528
1527
1529 def validate_elements(self, obj, value):
1528 def validate_elements(self, obj, value):
1530 validated = []
1529 validated = []
1531 if self._trait is None or isinstance(self._trait, Any):
1530 if self._trait is None or isinstance(self._trait, Any):
1532 return value
1531 return value
1533 for v in value:
1532 for v in value:
1534 try:
1533 try:
1535 v = self._trait._validate(obj, v)
1534 v = self._trait._validate(obj, v)
1536 except TraitError:
1535 except TraitError:
1537 self.element_error(obj, v, self._trait)
1536 self.element_error(obj, v, self._trait)
1538 else:
1537 else:
1539 validated.append(v)
1538 validated.append(v)
1540 return self.klass(validated)
1539 return self.klass(validated)
1541
1540
1542 def instance_init(self):
1541 def instance_init(self):
1543 if isinstance(self._trait, TraitType):
1542 if isinstance(self._trait, TraitType):
1544 self._trait.this_class = self.this_class
1543 self._trait.this_class = self.this_class
1545 self._trait.instance_init()
1544 self._trait.instance_init()
1546 super(Container, self).instance_init()
1545 super(Container, self).instance_init()
1547
1546
1548
1547
1549 class List(Container):
1548 class List(Container):
1550 """An instance of a Python list."""
1549 """An instance of a Python list."""
1551 klass = list
1550 klass = list
1552 _cast_types = (tuple,)
1551 _cast_types = (tuple,)
1553
1552
1554 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1553 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1555 """Create a List trait type from a list, set, or tuple.
1554 """Create a List trait type from a list, set, or tuple.
1556
1555
1557 The default value is created by doing ``List(default_value)``,
1556 The default value is created by doing ``List(default_value)``,
1558 which creates a copy of the ``default_value``.
1557 which creates a copy of the ``default_value``.
1559
1558
1560 ``trait`` can be specified, which restricts the type of elements
1559 ``trait`` can be specified, which restricts the type of elements
1561 in the container to that TraitType.
1560 in the container to that TraitType.
1562
1561
1563 If only one arg is given and it is not a Trait, it is taken as
1562 If only one arg is given and it is not a Trait, it is taken as
1564 ``default_value``:
1563 ``default_value``:
1565
1564
1566 ``c = List([1,2,3])``
1565 ``c = List([1,2,3])``
1567
1566
1568 Parameters
1567 Parameters
1569 ----------
1568 ----------
1570
1569
1571 trait : TraitType [ optional ]
1570 trait : TraitType [ optional ]
1572 the type for restricting the contents of the Container. If unspecified,
1571 the type for restricting the contents of the Container. If unspecified,
1573 types are not checked.
1572 types are not checked.
1574
1573
1575 default_value : SequenceType [ optional ]
1574 default_value : SequenceType [ optional ]
1576 The default value for the Trait. Must be list/tuple/set, and
1575 The default value for the Trait. Must be list/tuple/set, and
1577 will be cast to the container type.
1576 will be cast to the container type.
1578
1577
1579 minlen : Int [ default 0 ]
1578 minlen : Int [ default 0 ]
1580 The minimum length of the input list
1579 The minimum length of the input list
1581
1580
1582 maxlen : Int [ default sys.maxsize ]
1581 maxlen : Int [ default sys.maxsize ]
1583 The maximum length of the input list
1582 The maximum length of the input list
1584
1583
1585 allow_none : bool [ default False ]
1584 allow_none : bool [ default False ]
1586 Whether to allow the value to be None
1585 Whether to allow the value to be None
1587
1586
1588 **metadata : any
1587 **metadata : any
1589 further keys for extensions to the Trait (e.g. config)
1588 further keys for extensions to the Trait (e.g. config)
1590
1589
1591 """
1590 """
1592 self._minlen = minlen
1591 self._minlen = minlen
1593 self._maxlen = maxlen
1592 self._maxlen = maxlen
1594 super(List, self).__init__(trait=trait, default_value=default_value,
1593 super(List, self).__init__(trait=trait, default_value=default_value,
1595 **metadata)
1594 **metadata)
1596
1595
1597 def length_error(self, obj, value):
1596 def length_error(self, obj, value):
1598 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1597 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1599 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1598 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1600 raise TraitError(e)
1599 raise TraitError(e)
1601
1600
1602 def validate_elements(self, obj, value):
1601 def validate_elements(self, obj, value):
1603 length = len(value)
1602 length = len(value)
1604 if length < self._minlen or length > self._maxlen:
1603 if length < self._minlen or length > self._maxlen:
1605 self.length_error(obj, value)
1604 self.length_error(obj, value)
1606
1605
1607 return super(List, self).validate_elements(obj, value)
1606 return super(List, self).validate_elements(obj, value)
1608
1607
1609 def validate(self, obj, value):
1608 def validate(self, obj, value):
1610 value = super(List, self).validate(obj, value)
1609 value = super(List, self).validate(obj, value)
1611 value = self.validate_elements(obj, value)
1610 value = self.validate_elements(obj, value)
1612 return value
1611 return value
1613
1612
1614
1613
1615 class Set(List):
1614 class Set(List):
1616 """An instance of a Python set."""
1615 """An instance of a Python set."""
1617 klass = set
1616 klass = set
1618 _cast_types = (tuple, list)
1617 _cast_types = (tuple, list)
1619
1618
1620
1619
1621 class Tuple(Container):
1620 class Tuple(Container):
1622 """An instance of a Python tuple."""
1621 """An instance of a Python tuple."""
1623 klass = tuple
1622 klass = tuple
1624 _cast_types = (list,)
1623 _cast_types = (list,)
1625
1624
1626 def __init__(self, *traits, **metadata):
1625 def __init__(self, *traits, **metadata):
1627 """Tuple(*traits, default_value=None, **medatata)
1626 """Tuple(*traits, default_value=None, **medatata)
1628
1627
1629 Create a tuple from a list, set, or tuple.
1628 Create a tuple from a list, set, or tuple.
1630
1629
1631 Create a fixed-type tuple with Traits:
1630 Create a fixed-type tuple with Traits:
1632
1631
1633 ``t = Tuple(Int, Str, CStr)``
1632 ``t = Tuple(Int, Str, CStr)``
1634
1633
1635 would be length 3, with Int,Str,CStr for each element.
1634 would be length 3, with Int,Str,CStr for each element.
1636
1635
1637 If only one arg is given and it is not a Trait, it is taken as
1636 If only one arg is given and it is not a Trait, it is taken as
1638 default_value:
1637 default_value:
1639
1638
1640 ``t = Tuple((1,2,3))``
1639 ``t = Tuple((1,2,3))``
1641
1640
1642 Otherwise, ``default_value`` *must* be specified by keyword.
1641 Otherwise, ``default_value`` *must* be specified by keyword.
1643
1642
1644 Parameters
1643 Parameters
1645 ----------
1644 ----------
1646
1645
1647 *traits : TraitTypes [ optional ]
1646 *traits : TraitTypes [ optional ]
1648 the types for restricting the contents of the Tuple. If unspecified,
1647 the types for restricting the contents of the Tuple. If unspecified,
1649 types are not checked. If specified, then each positional argument
1648 types are not checked. If specified, then each positional argument
1650 corresponds to an element of the tuple. Tuples defined with traits
1649 corresponds to an element of the tuple. Tuples defined with traits
1651 are of fixed length.
1650 are of fixed length.
1652
1651
1653 default_value : SequenceType [ optional ]
1652 default_value : SequenceType [ optional ]
1654 The default value for the Tuple. Must be list/tuple/set, and
1653 The default value for the Tuple. Must be list/tuple/set, and
1655 will be cast to a tuple. If `traits` are specified, the
1654 will be cast to a tuple. If `traits` are specified, the
1656 `default_value` must conform to the shape and type they specify.
1655 `default_value` must conform to the shape and type they specify.
1657
1656
1658 allow_none : bool [ default False ]
1657 allow_none : bool [ default False ]
1659 Whether to allow the value to be None
1658 Whether to allow the value to be None
1660
1659
1661 **metadata : any
1660 **metadata : any
1662 further keys for extensions to the Trait (e.g. config)
1661 further keys for extensions to the Trait (e.g. config)
1663
1662
1664 """
1663 """
1665 default_value = metadata.pop('default_value', None)
1664 default_value = metadata.pop('default_value', None)
1666
1665
1667 # allow Tuple((values,)):
1666 # allow Tuple((values,)):
1668 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1667 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1669 default_value = traits[0]
1668 default_value = traits[0]
1670 traits = ()
1669 traits = ()
1671
1670
1672 if default_value is None:
1671 if default_value is None:
1673 args = ()
1672 args = ()
1674 elif isinstance(default_value, self._valid_defaults):
1673 elif isinstance(default_value, self._valid_defaults):
1675 args = (default_value,)
1674 args = (default_value,)
1676 else:
1675 else:
1677 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1676 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1678
1677
1679 self._traits = []
1678 self._traits = []
1680 for trait in traits:
1679 for trait in traits:
1681 t = trait() if isinstance(trait, type) else trait
1680 t = trait() if isinstance(trait, type) else trait
1682 t.name = 'element'
1681 t.name = 'element'
1683 self._traits.append(t)
1682 self._traits.append(t)
1684
1683
1685 if self._traits and default_value is None:
1684 if self._traits and default_value is None:
1686 # don't allow default to be an empty container if length is specified
1685 # don't allow default to be an empty container if length is specified
1687 args = None
1686 args = None
1688 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1687 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1689
1688
1690 def validate_elements(self, obj, value):
1689 def validate_elements(self, obj, value):
1691 if not self._traits:
1690 if not self._traits:
1692 # nothing to validate
1691 # nothing to validate
1693 return value
1692 return value
1694 if len(value) != len(self._traits):
1693 if len(value) != len(self._traits):
1695 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1694 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1696 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1695 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1697 raise TraitError(e)
1696 raise TraitError(e)
1698
1697
1699 validated = []
1698 validated = []
1700 for t, v in zip(self._traits, value):
1699 for t, v in zip(self._traits, value):
1701 try:
1700 try:
1702 v = t._validate(obj, v)
1701 v = t._validate(obj, v)
1703 except TraitError:
1702 except TraitError:
1704 self.element_error(obj, v, t)
1703 self.element_error(obj, v, t)
1705 else:
1704 else:
1706 validated.append(v)
1705 validated.append(v)
1707 return tuple(validated)
1706 return tuple(validated)
1708
1707
1709 def instance_init(self):
1708 def instance_init(self):
1710 for trait in self._traits:
1709 for trait in self._traits:
1711 if isinstance(trait, TraitType):
1710 if isinstance(trait, TraitType):
1712 trait.this_class = self.this_class
1711 trait.this_class = self.this_class
1713 trait.instance_init()
1712 trait.instance_init()
1714 super(Container, self).instance_init()
1713 super(Container, self).instance_init()
1715
1714
1716
1715
1717 class Dict(Instance):
1716 class Dict(Instance):
1718 """An instance of a Python dict."""
1717 """An instance of a Python dict."""
1719 _trait = None
1718 _trait = None
1720
1719
1721 def __init__(self, trait=None, default_value=NoDefaultSpecified, **metadata):
1720 def __init__(self, trait=None, default_value=NoDefaultSpecified, **metadata):
1722 """Create a dict trait type from a dict.
1721 """Create a dict trait type from a dict.
1723
1722
1724 The default value is created by doing ``dict(default_value)``,
1723 The default value is created by doing ``dict(default_value)``,
1725 which creates a copy of the ``default_value``.
1724 which creates a copy of the ``default_value``.
1726
1725
1727 trait : TraitType [ optional ]
1726 trait : TraitType [ optional ]
1728 the type for restricting the contents of the Container. If unspecified,
1727 the type for restricting the contents of the Container. If unspecified,
1729 types are not checked.
1728 types are not checked.
1730
1729
1731 default_value : SequenceType [ optional ]
1730 default_value : SequenceType [ optional ]
1732 The default value for the Dict. Must be dict, tuple, or None, and
1731 The default value for the Dict. Must be dict, tuple, or None, and
1733 will be cast to a dict if not None. If `trait` is specified, the
1732 will be cast to a dict if not None. If `trait` is specified, the
1734 `default_value` must conform to the constraints it specifies.
1733 `default_value` must conform to the constraints it specifies.
1735
1734
1736 allow_none : bool [ default False ]
1735 allow_none : bool [ default False ]
1737 Whether to allow the value to be None
1736 Whether to allow the value to be None
1738
1737
1739 """
1738 """
1740 if default_value is NoDefaultSpecified and trait is not None:
1739 if default_value is NoDefaultSpecified and trait is not None:
1741 if not is_trait(trait):
1740 if not is_trait(trait):
1742 default_value = trait
1741 default_value = trait
1743 trait = None
1742 trait = None
1744 if default_value is NoDefaultSpecified:
1743 if default_value is NoDefaultSpecified:
1745 default_value = {}
1744 default_value = {}
1746 if default_value is None:
1745 if default_value is None:
1747 args = None
1746 args = None
1748 elif isinstance(default_value, dict):
1747 elif isinstance(default_value, dict):
1749 args = (default_value,)
1748 args = (default_value,)
1750 elif isinstance(default_value, SequenceTypes):
1749 elif isinstance(default_value, SequenceTypes):
1751 args = (default_value,)
1750 args = (default_value,)
1752 else:
1751 else:
1753 raise TypeError('default value of Dict was %s' % default_value)
1752 raise TypeError('default value of Dict was %s' % default_value)
1754
1753
1755 if is_trait(trait):
1754 if is_trait(trait):
1756 self._trait = trait() if isinstance(trait, type) else trait
1755 self._trait = trait() if isinstance(trait, type) else trait
1757 self._trait.name = 'element'
1756 self._trait.name = 'element'
1758 elif trait is not None:
1757 elif trait is not None:
1759 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1758 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1760
1759
1761 super(Dict,self).__init__(klass=dict, args=args, **metadata)
1760 super(Dict,self).__init__(klass=dict, args=args, **metadata)
1762
1761
1763 def element_error(self, obj, element, validator):
1762 def element_error(self, obj, element, validator):
1764 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1763 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1765 % (self.name, class_of(obj), validator.info(), repr_type(element))
1764 % (self.name, class_of(obj), validator.info(), repr_type(element))
1766 raise TraitError(e)
1765 raise TraitError(e)
1767
1766
1768 def validate(self, obj, value):
1767 def validate(self, obj, value):
1769 value = super(Dict, self).validate(obj, value)
1768 value = super(Dict, self).validate(obj, value)
1770 if value is None:
1769 if value is None:
1771 return value
1770 return value
1772 value = self.validate_elements(obj, value)
1771 value = self.validate_elements(obj, value)
1773 return value
1772 return value
1774
1773
1775 def validate_elements(self, obj, value):
1774 def validate_elements(self, obj, value):
1776 if self._trait is None or isinstance(self._trait, Any):
1775 if self._trait is None or isinstance(self._trait, Any):
1777 return value
1776 return value
1778 validated = {}
1777 validated = {}
1779 for key in value:
1778 for key in value:
1780 v = value[key]
1779 v = value[key]
1781 try:
1780 try:
1782 v = self._trait._validate(obj, v)
1781 v = self._trait._validate(obj, v)
1783 except TraitError:
1782 except TraitError:
1784 self.element_error(obj, v, self._trait)
1783 self.element_error(obj, v, self._trait)
1785 else:
1784 else:
1786 validated[key] = v
1785 validated[key] = v
1787 return self.klass(validated)
1786 return self.klass(validated)
1788
1787
1789 def instance_init(self):
1788 def instance_init(self):
1790 if isinstance(self._trait, TraitType):
1789 if isinstance(self._trait, TraitType):
1791 self._trait.this_class = self.this_class
1790 self._trait.this_class = self.this_class
1792 self._trait.instance_init()
1791 self._trait.instance_init()
1793 super(Dict, self).instance_init()
1792 super(Dict, self).instance_init()
1794
1793
1795
1794
1796 class EventfulDict(Instance):
1797 """An instance of an EventfulDict."""
1798
1799 def __init__(self, default_value={}, **metadata):
1800 """Create a EventfulDict trait type from a dict.
1801
1802 The default value is created by doing
1803 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1804 ``default_value``.
1805 """
1806 if default_value is None:
1807 args = None
1808 elif isinstance(default_value, dict):
1809 args = (default_value,)
1810 elif isinstance(default_value, SequenceTypes):
1811 args = (default_value,)
1812 else:
1813 raise TypeError('default value of EventfulDict was %s' % default_value)
1814
1815 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1816 **metadata)
1817
1818
1819 class EventfulList(Instance):
1820 """An instance of an EventfulList."""
1821
1822 def __init__(self, default_value=None, **metadata):
1823 """Create a EventfulList trait type from a dict.
1824
1825 The default value is created by doing
1826 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1827 ``default_value``.
1828 """
1829 if default_value is None:
1830 args = ((),)
1831 else:
1832 args = (default_value,)
1833
1834 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1835 **metadata)
1836
1837
1838 class TCPAddress(TraitType):
1795 class TCPAddress(TraitType):
1839 """A trait for an (ip, port) tuple.
1796 """A trait for an (ip, port) tuple.
1840
1797
1841 This allows for both IPv4 IP addresses as well as hostnames.
1798 This allows for both IPv4 IP addresses as well as hostnames.
1842 """
1799 """
1843
1800
1844 default_value = ('127.0.0.1', 0)
1801 default_value = ('127.0.0.1', 0)
1845 info_text = 'an (ip, port) tuple'
1802 info_text = 'an (ip, port) tuple'
1846
1803
1847 def validate(self, obj, value):
1804 def validate(self, obj, value):
1848 if isinstance(value, tuple):
1805 if isinstance(value, tuple):
1849 if len(value) == 2:
1806 if len(value) == 2:
1850 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1807 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1851 port = value[1]
1808 port = value[1]
1852 if port >= 0 and port <= 65535:
1809 if port >= 0 and port <= 65535:
1853 return value
1810 return value
1854 self.error(obj, value)
1811 self.error(obj, value)
1855
1812
1856 class CRegExp(TraitType):
1813 class CRegExp(TraitType):
1857 """A casting compiled regular expression trait.
1814 """A casting compiled regular expression trait.
1858
1815
1859 Accepts both strings and compiled regular expressions. The resulting
1816 Accepts both strings and compiled regular expressions. The resulting
1860 attribute will be a compiled regular expression."""
1817 attribute will be a compiled regular expression."""
1861
1818
1862 info_text = 'a regular expression'
1819 info_text = 'a regular expression'
1863
1820
1864 def validate(self, obj, value):
1821 def validate(self, obj, value):
1865 try:
1822 try:
1866 return re.compile(value)
1823 return re.compile(value)
1867 except:
1824 except:
1868 self.error(obj, value)
1825 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now