##// END OF EJS Templates
Fixing subtle bug in the traitlets with This....
Brian Granger -
Show More
@@ -1,189 +1,214 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A lightweight component system for IPython.
4 A lightweight component system for IPython.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 The IPython Development Team
13 # Copyright (C) 2008-2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23
23
24 from weakref import WeakValueDictionary
24 from weakref import WeakValueDictionary
25
25
26 from IPython.utils.ipstruct import Struct
26 from IPython.utils.ipstruct import Struct
27 from IPython.utils.traitlets import (
27 from IPython.utils.traitlets import (
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
28 HasTraitlets, TraitletError, MetaHasTraitlets, Instance, This
29 )
29 )
30
30
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Helper classes for Components
33 # Helper classes for Components
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class ComponentError(Exception):
38 pass
39
37 class MetaComponentTracker(type):
40 class MetaComponentTracker(type):
38 """A metaclass that tracks instances of Components and its subclasses."""
41 """A metaclass that tracks instances of Components and its subclasses."""
39
42
40 def __init__(cls, name, bases, d):
43 def __init__(cls, name, bases, d):
41 super(MetaComponentTracker, cls).__init__(name, bases, d)
44 super(MetaComponentTracker, cls).__init__(name, bases, d)
42 cls.__instance_refs = WeakValueDictionary()
45 cls.__instance_refs = WeakValueDictionary()
43 cls.__numcreated = 0
46 cls.__numcreated = 0
44
47
45 def __call__(cls, *args, **kw):
48 def __call__(cls, *args, **kw):
46 """Called when *class* is called (instantiated)!!!
49 """Called when *class* is called (instantiated)!!!
47
50
48 When a Component or subclass is instantiated, this is called and
51 When a Component or subclass is instantiated, this is called and
49 the instance is saved in a WeakValueDictionary for tracking.
52 the instance is saved in a WeakValueDictionary for tracking.
50 """
53 """
51
54
52 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
55 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
53 for c in cls.__mro__:
56 for c in cls.__mro__:
54 if issubclass(cls, c) and issubclass(c, Component):
57 if issubclass(cls, c) and issubclass(c, Component):
55 c.__numcreated += 1
58 c.__numcreated += 1
56 c.__instance_refs[c.__numcreated] = instance
59 c.__instance_refs[c.__numcreated] = instance
57 return instance
60 return instance
58
61
59 def get_instances(cls, name=None, klass=None, root=None):
62 def get_instances(cls, name=None, klass=None, root=None):
60 """Get all instances of cls and its subclasses.
63 """Get all instances of cls and its subclasses.
61
64
62 Parameters
65 Parameters
63 ----------
66 ----------
64 name : str
67 name : str
65 Limit to components with this name.
68 Limit to components with this name.
66 klass : class
69 klass : class
67 Limit to components having isinstance(component, klass)
70 Limit to components having isinstance(component, klass)
68 root : Component or subclass
71 root : Component or subclass
69 Limit to components having this root.
72 Limit to components having this root.
70 """
73 """
71 instances = cls.__instance_refs.values()
74 instances = cls.__instance_refs.values()
72 if name is not None:
75 if name is not None:
73 instances = [i for i in instances if i.name == name]
76 instances = [i for i in instances if i.name == name]
74 if klass is not None:
77 if klass is not None:
75 instances = [i for i in instances if isinstance(i, klass)]
78 instances = [i for i in instances if isinstance(i, klass)]
76 if root is not None:
79 if root is not None:
77 instances = [i for i in instances if i.root == root]
80 instances = [i for i in instances if i.root == root]
78 return instances
81 return instances
79
82
80 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
83 def get_instances_by_condition(cls, call, name=None, klass=None, root=None):
81 """Get all instances of cls, i such that call(i)==True.
84 """Get all instances of cls, i such that call(i)==True.
82
85
83 This also takes the ``name``, ``klass`` and ``root`` arguments of
86 This also takes the ``name``, ``klass`` and ``root`` arguments of
84 :meth:`get_instance`
87 :meth:`get_instance`
85 """
88 """
86 return [i for i in cls.get_instances(name,klass,root) if call(i)]
89 return [i for i in cls.get_instances(name,klass,root) if call(i)]
87
90
88
91
89 class ComponentNameGenerator(object):
92 class ComponentNameGenerator(object):
90 """A Singleton to generate unique component names."""
93 """A Singleton to generate unique component names."""
91
94
92 def __init__(self, prefix):
95 def __init__(self, prefix):
93 self.prefix = prefix
96 self.prefix = prefix
94 self.i = 0
97 self.i = 0
95
98
96 def __call__(self):
99 def __call__(self):
97 count = self.i
100 count = self.i
98 self.i += 1
101 self.i += 1
99 return "%s%s" % (self.prefix, count)
102 return "%s%s" % (self.prefix, count)
100
103
101
104
102 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
105 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
103
106
104
107
105 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
108 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
106 pass
109 pass
107
110
108
111
109 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
110 # Component implementation
113 # Component implementation
111 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
112
115
113
116
114 class Component(HasTraitlets):
117 class Component(HasTraitlets):
115
118
116 __metaclass__ = MetaComponent
119 __metaclass__ = MetaComponent
117
120
118 # Traitlets are fun!
121 # Traitlets are fun!
119 config = Instance(Struct)
122 config = Instance(Struct,(),{})
120 parent = This(allow_none=True)
123 parent = This()
121 root = This(allow_none=True)
124 root = This()
122
125
123 def __init__(self, parent, name=None, config=None):
126 def __init__(self, parent, name=None, config=None):
124 """Create a component given a parent.
127 """Create a component given a parent and possibly and name and config.
125
128
126 Parameters
129 Parameters
127 ----------
130 ----------
128 parent : Component subclass
131 parent : Component subclass
129 The parent in the component graph. The parent is used
132 The parent in the component graph. The parent is used
130 to get the root of the component graph.
133 to get the root of the component graph.
131 name : str
134 name : str
132 The unique name of the component. If empty, then a unique
135 The unique name of the component. If empty, then a unique
133 one will be autogenerated.
136 one will be autogenerated.
134 config : Config
137 config : Config
135 If this is empty, self.config = root.config, otherwise
138 If this is empty, self.config = parent.config, otherwise
136 self.config = config and root.config is ignored. This argument
139 self.config = config and root.config is ignored. This argument
137 should be used to pass the config to the root. Otherwise, it
140 should only be used to *override* the automatic inheritance of
138 can be used to *override* the inheritance of root.config. If a
141 parent.config. If a caller wants to modify parent.config
139 caller wants to modify root.config (not override), the caller
142 (not override), the caller should make a copy and change
140 should make a copy and change attributes and then pass the copy
143 attributes and then pass the copy to this argument.
141 to this argument. We might think about changing this behavior.
144
145 Notes
146 -----
147 Subclasses of Component must call the :meth:`__init__` method of
148 :class:`Component` *before* doing anything else and using
149 :func:`super`::
150
151 class MyComponent(Component):
152 def __init__(self, parent, name=None, config=None):
153 super(MyComponent, self).__init__(parent, name, config)
154 # Then any other code you need to finish initialization.
155
156 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
157 attributes are handled properly.
142 """
158 """
143 super(Component, self).__init__()
159 super(Component, self).__init__()
144 self._children = []
160 self._children = []
145 if name is None:
161 if name is None:
146 self.name = ComponentNameGenerator()
162 self.name = ComponentNameGenerator()
147 else:
163 else:
148 self.name = name
164 self.name = name
149 self.root = self # This is the default, it is set when parent is set
165 self.root = self # This is the default, it is set when parent is set
150 self.parent = parent
166 self.parent = parent
151 if config is not None:
167 if config is not None:
152 self.config = config
168 self.config = config
153 else:
169 else:
154 if self.parent is not None:
170 if self.parent is not None:
155 self.config = self.parent.config
171 self.config = self.parent.config
156
172
157 #-------------------------------------------------------------------------
173 #-------------------------------------------------------------------------
158 # Static traitlet notifiations
174 # Static traitlet notifiations
159 #-------------------------------------------------------------------------
175 #-------------------------------------------------------------------------
160
176
161 def _parent_changed(self, name, old, new):
177 def _parent_changed(self, name, old, new):
162 if old is not None:
178 if old is not None:
163 old._remove_child(self)
179 old._remove_child(self)
164 if new is not None:
180 if new is not None:
165 new._add_child(self)
181 new._add_child(self)
166
182
167 if new is None:
183 if new is None:
168 self.root = self
184 self.root = self
169 else:
185 else:
170 self.root = new.root
186 self.root = new.root
171
187
188 def _root_changed(self, name, old, new):
189 if self.parent is None:
190 if not (new is self):
191 raise ComponentError("Root not self, but parent is None.")
192 else:
193 if not self.parent.root is new:
194 raise ComponentError("Error in setting the root attribute: "
195 "root != parent.root")
196
172 @property
197 @property
173 def children(self):
198 def children(self):
174 """A list of all my child components."""
199 """A list of all my child components."""
175 return self._children
200 return self._children
176
201
177 def _remove_child(self, child):
202 def _remove_child(self, child):
178 """A private method for removing children componenets."""
203 """A private method for removing children componenets."""
179 if child in self._children:
204 if child in self._children:
180 index = self._children.index(child)
205 index = self._children.index(child)
181 del self._children[index]
206 del self._children[index]
182
207
183 def _add_child(self, child):
208 def _add_child(self, child):
184 """A private method for adding children componenets."""
209 """A private method for adding children componenets."""
185 if child not in self._children:
210 if child not in self._children:
186 self._children.append(child)
211 self._children.append(child)
187
212
188 def __repr__(self):
213 def __repr__(self):
189 return "<Component('%s')>" % self.name
214 return "<Component('%s')>" % self.name
@@ -1,67 +1,170 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Tests for IPython.core.component
4 Tests for IPython.core.component
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez (design help)
9 * Fernando Perez (design help)
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2009 The IPython Development Team
13 # Copyright (C) 2008-2009 The IPython Development Team
14 #
14 #
15 # Distributed under the terms of the BSD License. The full license is in
15 # Distributed under the terms of the BSD License. The full license is in
16 # the file COPYING, distributed as part of this software.
16 # the file COPYING, distributed as part of this software.
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20 # Imports
20 # Imports
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22
22
23 from unittest import TestCase
23 from unittest import TestCase
24
24
25 from IPython.core.component import Component
25 from IPython.core.component import Component, ComponentError
26 from IPython.utils.traitlets import (
27 TraitletError
28 )
29 from IPython.utils.ipstruct import Struct
26
30
27
31
28 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
29 # Test cases
33 # Test cases
30 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
31
35
32
36
33 class TestComponentMeta(TestCase):
37 class TestComponentMeta(TestCase):
34
38
35 def test_get_instances(self):
39 def test_get_instances(self):
36 class BaseComponent(Component):
40 class BaseComponent(Component):
37 pass
41 pass
38 c1 = BaseComponent(None)
42 c1 = BaseComponent(None)
39 c2 = BaseComponent(c1)
43 c2 = BaseComponent(c1)
40 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
41
45
46 def test_get_instances_subclass(self):
47 class MyComponent(Component):
48 pass
49 class MyOtherComponent(MyComponent):
50 pass
51 c1 = MyComponent(None)
52 c2 = MyOtherComponent(c1)
53 c3 = MyOtherComponent(c2)
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
55 self.assertEquals(MyComponent.get_instances(klass=MyOtherComponent), [c2, c3])
56
57 def test_get_instances_root(self):
58 class MyComponent(Component):
59 pass
60 class MyOtherComponent(MyComponent):
61 pass
62 c1 = MyComponent(None)
63 c2 = MyOtherComponent(c1)
64 c3 = MyOtherComponent(c2)
65 c4 = MyComponent(None)
66 c5 = MyComponent(c4)
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
69
42
70
43 class TestComponent(TestCase):
71 class TestComponent(TestCase):
44
72
45 def test_parent_child(self):
73 def test_parent_child(self):
46 c1 = Component(None)
74 c1 = Component(None)
47 c2 = Component(c1)
75 c2 = Component(c1)
48 c3 = Component(c1)
76 c3 = Component(c1)
49 c4 = Component(c3)
77 c4 = Component(c3)
50 self.assertEquals(c1.parent, None)
78 self.assertEquals(c1.parent, None)
51 self.assertEquals(c2.parent, c1)
79 self.assertEquals(c2.parent, c1)
52 self.assertEquals(c3.parent, c1)
80 self.assertEquals(c3.parent, c1)
53 self.assertEquals(c4.parent, c3)
81 self.assertEquals(c4.parent, c3)
54 self.assertEquals(c1.children, [c2, c3])
82 self.assertEquals(c1.children, [c2, c3])
55 self.assertEquals(c2.children, [])
83 self.assertEquals(c2.children, [])
56 self.assertEquals(c3.children, [c4])
84 self.assertEquals(c3.children, [c4])
57 self.assertEquals(c4.children, [])
85 self.assertEquals(c4.children, [])
58
86
59 def test_root(self):
87 def test_root(self):
60 c1 = Component(None)
88 c1 = Component(None)
61 c2 = Component(c1)
89 c2 = Component(c1)
62 c3 = Component(c1)
90 c3 = Component(c1)
63 c4 = Component(c3)
91 c4 = Component(c3)
64 self.assertEquals(c1.root, c1.root)
92 self.assertEquals(c1.root, c1.root)
65 self.assertEquals(c2.root, c1)
93 self.assertEquals(c2.root, c1)
66 self.assertEquals(c3.root, c1)
94 self.assertEquals(c3.root, c1)
67 self.assertEquals(c4.root, c1)
95 self.assertEquals(c4.root, c1)
96
97 def test_change_parent(self):
98 c1 = Component(None)
99 c2 = Component(None)
100 c3 = Component(c1)
101 self.assertEquals(c3.root, c1)
102 self.assertEquals(c3.parent, c1)
103 self.assertEquals(c1.children,[c3])
104 c3.parent = c2
105 self.assertEquals(c3.root, c2)
106 self.assertEquals(c3.parent, c2)
107 self.assertEquals(c2.children,[c3])
108 self.assertEquals(c1.children,[])
109
110 def test_subclass_parent(self):
111 c1 = Component(None)
112 self.assertRaises(TraitletError, setattr, c1, 'parent', 10)
113
114 class MyComponent(Component):
115 pass
116 c1 = Component(None)
117 c2 = MyComponent(c1)
118 self.assertEquals(MyComponent.parent.this_class, Component)
119 self.assertEquals(c2.parent, c1)
120
121 def test_bad_root(self):
122 c1 = Component(None)
123 c2 = Component(None)
124 c3 = Component(None)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
126 c1.parent = c2
127 self.assertEquals(c1.root, c2)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
129
130
131 class TestComponentConfig(TestCase):
132
133 def test_default(self):
134 c1 = Component(None)
135 c2 = Component(c1)
136 c3 = Component(c2)
137 self.assertEquals(c1.config, c2.config)
138 self.assertEquals(c2.config, c3.config)
139
140 def test_custom(self):
141 config = Struct()
142 config.FOO = 'foo'
143 config.BAR = 'bar'
144 c1 = Component(None, config=config)
145 c2 = Component(c1)
146 c3 = Component(c2)
147 self.assertEquals(c1.config, config)
148 self.assertEquals(c2.config, config)
149 self.assertEquals(c3.config, config)
150
151 class TestComponentName(TestCase):
152
153 def test_default(self):
154 class MyComponent(Component):
155 pass
156 c1 = Component(None)
157 c2 = MyComponent(None)
158 c3 = Component(c2)
159 self.assertNotEquals(c1.name, c2.name)
160 self.assertNotEquals(c1.name, c3.name)
161
162 def test_manual(self):
163 class MyComponent(Component):
164 pass
165 c1 = Component(None, name='foo')
166 c2 = MyComponent(None, name='bar')
167 c3 = Component(c2, name='bah')
168 self.assertEquals(c1.name, 'foo')
169 self.assertEquals(c2.name, 'bar')
170 self.assertEquals(c3.name, 'bah')
@@ -1,612 +1,645 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 Tests for IPython.utils.traitlets.
4 Tests for IPython.utils.traitlets.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
9 * Enthought, Inc. Some of the code in this file comes from enthought.traits
10 and is licensed under the BSD license. Also, many of the ideas also come
10 and is licensed under the BSD license. Also, many of the ideas also come
11 from enthought.traits even though our implementation is very different.
11 from enthought.traits even though our implementation is very different.
12 """
12 """
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Copyright (C) 2008-2009 The IPython Development Team
15 # Copyright (C) 2008-2009 The IPython Development Team
16 #
16 #
17 # Distributed under the terms of the BSD License. The full license is in
17 # Distributed under the terms of the BSD License. The full license is in
18 # the file COPYING, distributed as part of this software.
18 # the file COPYING, distributed as part of this software.
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 #-----------------------------------------------------------------------------
21 #-----------------------------------------------------------------------------
22 # Imports
22 # Imports
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24
24
25 import sys
25 import sys
26 import os
26 import os
27
27
28
28
29 from unittest import TestCase
29 from unittest import TestCase
30
30
31 from IPython.utils.traitlets import (
31 from IPython.utils.traitlets import (
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
32 HasTraitlets, MetaHasTraitlets, TraitletType, Any,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
33 Int, Long, Float, Complex, Str, Unicode, Bool, TraitletError,
34 Undefined, Type, This, Instance
34 Undefined, Type, This, Instance
35 )
35 )
36
36
37
37
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39 # Helper classes for testing
39 # Helper classes for testing
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41
41
42
42
43 class HasTraitletsStub(HasTraitlets):
43 class HasTraitletsStub(HasTraitlets):
44
44
45 def _notify_traitlet(self, name, old, new):
45 def _notify_traitlet(self, name, old, new):
46 self._notify_name = name
46 self._notify_name = name
47 self._notify_old = old
47 self._notify_old = old
48 self._notify_new = new
48 self._notify_new = new
49
49
50
50
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52 # Test classes
52 # Test classes
53 #-----------------------------------------------------------------------------
53 #-----------------------------------------------------------------------------
54
54
55
55
56 class TestTraitletType(TestCase):
56 class TestTraitletType(TestCase):
57
57
58 def test_get_undefined(self):
58 def test_get_undefined(self):
59 class A(HasTraitlets):
59 class A(HasTraitlets):
60 a = TraitletType
60 a = TraitletType
61 a = A()
61 a = A()
62 self.assertEquals(a.a, Undefined)
62 self.assertEquals(a.a, Undefined)
63
63
64 def test_set(self):
64 def test_set(self):
65 class A(HasTraitletsStub):
65 class A(HasTraitletsStub):
66 a = TraitletType
66 a = TraitletType
67
67
68 a = A()
68 a = A()
69 a.a = 10
69 a.a = 10
70 self.assertEquals(a.a, 10)
70 self.assertEquals(a.a, 10)
71 self.assertEquals(a._notify_name, 'a')
71 self.assertEquals(a._notify_name, 'a')
72 self.assertEquals(a._notify_old, Undefined)
72 self.assertEquals(a._notify_old, Undefined)
73 self.assertEquals(a._notify_new, 10)
73 self.assertEquals(a._notify_new, 10)
74
74
75 def test_validate(self):
75 def test_validate(self):
76 class MyTT(TraitletType):
76 class MyTT(TraitletType):
77 def validate(self, inst, value):
77 def validate(self, inst, value):
78 return -1
78 return -1
79 class A(HasTraitletsStub):
79 class A(HasTraitletsStub):
80 tt = MyTT
80 tt = MyTT
81
81
82 a = A()
82 a = A()
83 a.tt = 10
83 a.tt = 10
84 self.assertEquals(a.tt, -1)
84 self.assertEquals(a.tt, -1)
85
85
86 def test_default_validate(self):
86 def test_default_validate(self):
87 class MyIntTT(TraitletType):
87 class MyIntTT(TraitletType):
88 def validate(self, obj, value):
88 def validate(self, obj, value):
89 if isinstance(value, int):
89 if isinstance(value, int):
90 return value
90 return value
91 self.error(obj, value)
91 self.error(obj, value)
92 class A(HasTraitlets):
92 class A(HasTraitlets):
93 tt = MyIntTT(10)
93 tt = MyIntTT(10)
94 a = A()
94 a = A()
95 self.assertEquals(a.tt, 10)
95 self.assertEquals(a.tt, 10)
96
96
97 # Defaults are validated when the HasTraitlets is instantiated
97 # Defaults are validated when the HasTraitlets is instantiated
98 class B(HasTraitlets):
98 class B(HasTraitlets):
99 tt = MyIntTT('bad default')
99 tt = MyIntTT('bad default')
100 self.assertRaises(TraitletError, B)
100 self.assertRaises(TraitletError, B)
101
101
102 def test_is_valid_for(self):
102 def test_is_valid_for(self):
103 class MyTT(TraitletType):
103 class MyTT(TraitletType):
104 def is_valid_for(self, value):
104 def is_valid_for(self, value):
105 return True
105 return True
106 class A(HasTraitlets):
106 class A(HasTraitlets):
107 tt = MyTT
107 tt = MyTT
108
108
109 a = A()
109 a = A()
110 a.tt = 10
110 a.tt = 10
111 self.assertEquals(a.tt, 10)
111 self.assertEquals(a.tt, 10)
112
112
113 def test_value_for(self):
113 def test_value_for(self):
114 class MyTT(TraitletType):
114 class MyTT(TraitletType):
115 def value_for(self, value):
115 def value_for(self, value):
116 return 20
116 return 20
117 class A(HasTraitlets):
117 class A(HasTraitlets):
118 tt = MyTT
118 tt = MyTT
119
119
120 a = A()
120 a = A()
121 a.tt = 10
121 a.tt = 10
122 self.assertEquals(a.tt, 20)
122 self.assertEquals(a.tt, 20)
123
123
124 def test_info(self):
124 def test_info(self):
125 class A(HasTraitlets):
125 class A(HasTraitlets):
126 tt = TraitletType
126 tt = TraitletType
127 a = A()
127 a = A()
128 self.assertEquals(A.tt.info(), 'any value')
128 self.assertEquals(A.tt.info(), 'any value')
129
129
130 def test_error(self):
130 def test_error(self):
131 class A(HasTraitlets):
131 class A(HasTraitlets):
132 tt = TraitletType
132 tt = TraitletType
133 a = A()
133 a = A()
134 self.assertRaises(TraitletError, A.tt.error, a, 10)
134 self.assertRaises(TraitletError, A.tt.error, a, 10)
135
135
136
136
137 class TestHasTraitletsMeta(TestCase):
137 class TestHasTraitletsMeta(TestCase):
138
138
139 def test_metaclass(self):
139 def test_metaclass(self):
140 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
140 self.assertEquals(type(HasTraitlets), MetaHasTraitlets)
141
141
142 class A(HasTraitlets):
142 class A(HasTraitlets):
143 a = Int
143 a = Int
144
144
145 a = A()
145 a = A()
146 self.assertEquals(type(a.__class__), MetaHasTraitlets)
146 self.assertEquals(type(a.__class__), MetaHasTraitlets)
147 self.assertEquals(a.a,0)
147 self.assertEquals(a.a,0)
148 a.a = 10
148 a.a = 10
149 self.assertEquals(a.a,10)
149 self.assertEquals(a.a,10)
150
150
151 class B(HasTraitlets):
151 class B(HasTraitlets):
152 b = Int()
152 b = Int()
153
153
154 b = B()
154 b = B()
155 self.assertEquals(b.b,0)
155 self.assertEquals(b.b,0)
156 b.b = 10
156 b.b = 10
157 self.assertEquals(b.b,10)
157 self.assertEquals(b.b,10)
158
158
159 class C(HasTraitlets):
159 class C(HasTraitlets):
160 c = Int(30)
160 c = Int(30)
161
161
162 c = C()
162 c = C()
163 self.assertEquals(c.c,30)
163 self.assertEquals(c.c,30)
164 c.c = 10
164 c.c = 10
165 self.assertEquals(c.c,10)
165 self.assertEquals(c.c,10)
166
166
167 def test_this_class(self):
168 class A(HasTraitlets):
169 t = This()
170 tt = This()
171 class B(A):
172 tt = This()
173 ttt = This()
174 self.assertEquals(A.t.this_class, A)
175 self.assertEquals(B.t.this_class, A)
176 self.assertEquals(B.tt.this_class, B)
177 self.assertEquals(B.ttt.this_class, B)
167
178
168 class TestHasTraitletsNotify(TestCase):
179 class TestHasTraitletsNotify(TestCase):
169
180
170 def setUp(self):
181 def setUp(self):
171 self._notify1 = []
182 self._notify1 = []
172 self._notify2 = []
183 self._notify2 = []
173
184
174 def notify1(self, name, old, new):
185 def notify1(self, name, old, new):
175 self._notify1.append((name, old, new))
186 self._notify1.append((name, old, new))
176
187
177 def notify2(self, name, old, new):
188 def notify2(self, name, old, new):
178 self._notify2.append((name, old, new))
189 self._notify2.append((name, old, new))
179
190
180 def test_notify_all(self):
191 def test_notify_all(self):
181
192
182 class A(HasTraitlets):
193 class A(HasTraitlets):
183 a = Int
194 a = Int
184 b = Float
195 b = Float
185
196
186 a = A()
197 a = A()
187 a.on_traitlet_change(self.notify1)
198 a.on_traitlet_change(self.notify1)
188 a.a = 0
199 a.a = 0
189 self.assertEquals(len(self._notify1),0)
200 self.assertEquals(len(self._notify1),0)
190 a.b = 0.0
201 a.b = 0.0
191 self.assertEquals(len(self._notify1),0)
202 self.assertEquals(len(self._notify1),0)
192 a.a = 10
203 a.a = 10
193 self.assert_(('a',0,10) in self._notify1)
204 self.assert_(('a',0,10) in self._notify1)
194 a.b = 10.0
205 a.b = 10.0
195 self.assert_(('b',0.0,10.0) in self._notify1)
206 self.assert_(('b',0.0,10.0) in self._notify1)
196 self.assertRaises(TraitletError,setattr,a,'a','bad string')
207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
197 self.assertRaises(TraitletError,setattr,a,'b','bad string')
208 self.assertRaises(TraitletError,setattr,a,'b','bad string')
198 self._notify1 = []
209 self._notify1 = []
199 a.on_traitlet_change(self.notify1,remove=True)
210 a.on_traitlet_change(self.notify1,remove=True)
200 a.a = 20
211 a.a = 20
201 a.b = 20.0
212 a.b = 20.0
202 self.assertEquals(len(self._notify1),0)
213 self.assertEquals(len(self._notify1),0)
203
214
204 def test_notify_one(self):
215 def test_notify_one(self):
205
216
206 class A(HasTraitlets):
217 class A(HasTraitlets):
207 a = Int
218 a = Int
208 b = Float
219 b = Float
209
220
210 a = A()
221 a = A()
211 a.on_traitlet_change(self.notify1, 'a')
222 a.on_traitlet_change(self.notify1, 'a')
212 a.a = 0
223 a.a = 0
213 self.assertEquals(len(self._notify1),0)
224 self.assertEquals(len(self._notify1),0)
214 a.a = 10
225 a.a = 10
215 self.assert_(('a',0,10) in self._notify1)
226 self.assert_(('a',0,10) in self._notify1)
216 self.assertRaises(TraitletError,setattr,a,'a','bad string')
227 self.assertRaises(TraitletError,setattr,a,'a','bad string')
217
228
218 def test_subclass(self):
229 def test_subclass(self):
219
230
220 class A(HasTraitlets):
231 class A(HasTraitlets):
221 a = Int
232 a = Int
222
233
223 class B(A):
234 class B(A):
224 b = Float
235 b = Float
225
236
226 b = B()
237 b = B()
227 self.assertEquals(b.a,0)
238 self.assertEquals(b.a,0)
228 self.assertEquals(b.b,0.0)
239 self.assertEquals(b.b,0.0)
229 b.a = 100
240 b.a = 100
230 b.b = 100.0
241 b.b = 100.0
231 self.assertEquals(b.a,100)
242 self.assertEquals(b.a,100)
232 self.assertEquals(b.b,100.0)
243 self.assertEquals(b.b,100.0)
233
244
234 def test_notify_subclass(self):
245 def test_notify_subclass(self):
235
246
236 class A(HasTraitlets):
247 class A(HasTraitlets):
237 a = Int
248 a = Int
238
249
239 class B(A):
250 class B(A):
240 b = Float
251 b = Float
241
252
242 b = B()
253 b = B()
243 b.on_traitlet_change(self.notify1, 'a')
254 b.on_traitlet_change(self.notify1, 'a')
244 b.on_traitlet_change(self.notify2, 'b')
255 b.on_traitlet_change(self.notify2, 'b')
245 b.a = 0
256 b.a = 0
246 b.b = 0.0
257 b.b = 0.0
247 self.assertEquals(len(self._notify1),0)
258 self.assertEquals(len(self._notify1),0)
248 self.assertEquals(len(self._notify2),0)
259 self.assertEquals(len(self._notify2),0)
249 b.a = 10
260 b.a = 10
250 b.b = 10.0
261 b.b = 10.0
251 self.assert_(('a',0,10) in self._notify1)
262 self.assert_(('a',0,10) in self._notify1)
252 self.assert_(('b',0.0,10.0) in self._notify2)
263 self.assert_(('b',0.0,10.0) in self._notify2)
253
264
254 def test_static_notify(self):
265 def test_static_notify(self):
255
266
256 class A(HasTraitlets):
267 class A(HasTraitlets):
257 a = Int
268 a = Int
258 _notify1 = []
269 _notify1 = []
259 def _a_changed(self, name, old, new):
270 def _a_changed(self, name, old, new):
260 self._notify1.append((name, old, new))
271 self._notify1.append((name, old, new))
261
272
262 a = A()
273 a = A()
263 a.a = 0
274 a.a = 0
264 # This is broken!!!
275 # This is broken!!!
265 self.assertEquals(len(a._notify1),0)
276 self.assertEquals(len(a._notify1),0)
266 a.a = 10
277 a.a = 10
267 self.assert_(('a',0,10) in a._notify1)
278 self.assert_(('a',0,10) in a._notify1)
268
279
269 class B(A):
280 class B(A):
270 b = Float
281 b = Float
271 _notify2 = []
282 _notify2 = []
272 def _b_changed(self, name, old, new):
283 def _b_changed(self, name, old, new):
273 self._notify2.append((name, old, new))
284 self._notify2.append((name, old, new))
274
285
275 b = B()
286 b = B()
276 b.a = 10
287 b.a = 10
277 b.b = 10.0
288 b.b = 10.0
278 self.assert_(('a',0,10) in b._notify1)
289 self.assert_(('a',0,10) in b._notify1)
279 self.assert_(('b',0.0,10.0) in b._notify2)
290 self.assert_(('b',0.0,10.0) in b._notify2)
280
291
281 def test_notify_args(self):
292 def test_notify_args(self):
282
293
283 def callback0():
294 def callback0():
284 self.cb = ()
295 self.cb = ()
285 def callback1(name):
296 def callback1(name):
286 self.cb = (name,)
297 self.cb = (name,)
287 def callback2(name, new):
298 def callback2(name, new):
288 self.cb = (name, new)
299 self.cb = (name, new)
289 def callback3(name, old, new):
300 def callback3(name, old, new):
290 self.cb = (name, old, new)
301 self.cb = (name, old, new)
291
302
292 class A(HasTraitlets):
303 class A(HasTraitlets):
293 a = Int
304 a = Int
294
305
295 a = A()
306 a = A()
296 a.on_traitlet_change(callback0, 'a')
307 a.on_traitlet_change(callback0, 'a')
297 a.a = 10
308 a.a = 10
298 self.assertEquals(self.cb,())
309 self.assertEquals(self.cb,())
299 a.on_traitlet_change(callback0, 'a', remove=True)
310 a.on_traitlet_change(callback0, 'a', remove=True)
300
311
301 a.on_traitlet_change(callback1, 'a')
312 a.on_traitlet_change(callback1, 'a')
302 a.a = 100
313 a.a = 100
303 self.assertEquals(self.cb,('a',))
314 self.assertEquals(self.cb,('a',))
304 a.on_traitlet_change(callback1, 'a', remove=True)
315 a.on_traitlet_change(callback1, 'a', remove=True)
305
316
306 a.on_traitlet_change(callback2, 'a')
317 a.on_traitlet_change(callback2, 'a')
307 a.a = 1000
318 a.a = 1000
308 self.assertEquals(self.cb,('a',1000))
319 self.assertEquals(self.cb,('a',1000))
309 a.on_traitlet_change(callback2, 'a', remove=True)
320 a.on_traitlet_change(callback2, 'a', remove=True)
310
321
311 a.on_traitlet_change(callback3, 'a')
322 a.on_traitlet_change(callback3, 'a')
312 a.a = 10000
323 a.a = 10000
313 self.assertEquals(self.cb,('a',1000,10000))
324 self.assertEquals(self.cb,('a',1000,10000))
314 a.on_traitlet_change(callback3, 'a', remove=True)
325 a.on_traitlet_change(callback3, 'a', remove=True)
315
326
316 self.assertEquals(len(a._traitlet_notifiers['a']),0)
327 self.assertEquals(len(a._traitlet_notifiers['a']),0)
317
328
318
329
319 class TestTraitletKeys(TestCase):
330 class TestTraitletKeys(TestCase):
320
331
321 def test_keys(self):
332 def test_keys(self):
322 class A(HasTraitlets):
333 class A(HasTraitlets):
323 a = Int
334 a = Int
324 b = Float
335 b = Float
325 a = A()
336 a = A()
326 self.assertEquals(a.traitlet_keys(),['a','b'])
337 self.assertEquals(a.traitlet_names(),['a','b'])
327
338
328
339
329 #-----------------------------------------------------------------------------
340 #-----------------------------------------------------------------------------
330 # Tests for specific traitlet types
341 # Tests for specific traitlet types
331 #-----------------------------------------------------------------------------
342 #-----------------------------------------------------------------------------
332
343
333
344
334 class TestType(TestCase):
345 class TestType(TestCase):
335
346
336 def test_default(self):
347 def test_default(self):
337
348
338 class B(object): pass
349 class B(object): pass
339 class A(HasTraitlets):
350 class A(HasTraitlets):
340 klass = Type
351 klass = Type
341
352
342 a = A()
353 a = A()
343 self.assertEquals(a.klass, None)
354 self.assertEquals(a.klass, None)
344 a.klass = B
355 a.klass = B
345 self.assertEquals(a.klass, B)
356 self.assertEquals(a.klass, B)
346 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
357 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
347
358
348 def test_value(self):
359 def test_value(self):
349
360
350 class B(object): pass
361 class B(object): pass
351 class C(object): pass
362 class C(object): pass
352 class A(HasTraitlets):
363 class A(HasTraitlets):
353 klass = Type(B)
364 klass = Type(B)
354
365
355 a = A()
366 a = A()
356 self.assertEquals(a.klass, B)
367 self.assertEquals(a.klass, B)
357 self.assertRaises(TraitletError, setattr, a, 'klass', C)
368 self.assertRaises(TraitletError, setattr, a, 'klass', C)
358 self.assertRaises(TraitletError, setattr, a, 'klass', object)
369 self.assertRaises(TraitletError, setattr, a, 'klass', object)
359 a.klass = B
370 a.klass = B
360
371
361 def test_allow_none(self):
372 def test_allow_none(self):
362
373
363 class B(object): pass
374 class B(object): pass
364 class C(B): pass
375 class C(B): pass
365 class A(HasTraitlets):
376 class A(HasTraitlets):
366 klass = Type(B, allow_none=False)
377 klass = Type(B, allow_none=False)
367
378
368 a = A()
379 a = A()
369 self.assertEquals(a.klass, B)
380 self.assertEquals(a.klass, B)
370 self.assertRaises(TraitletError, setattr, a, 'klass', None)
381 self.assertRaises(TraitletError, setattr, a, 'klass', None)
371 a.klass = C
382 a.klass = C
372 self.assertEquals(a.klass, C)
383 self.assertEquals(a.klass, C)
373
384
374 def test_validate_klass(self):
385 def test_validate_klass(self):
375
386
376 def inner():
387 def inner():
377 class A(HasTraitlets):
388 class A(HasTraitlets):
378 klass = Type('no strings allowed')
389 klass = Type('no strings allowed')
379
390
380 self.assertRaises(TraitletError, inner)
391 self.assertRaises(TraitletError, inner)
381
392
382 def test_validate_default(self):
393 def test_validate_default(self):
383
394
384 class B(object): pass
395 class B(object): pass
385 class A(HasTraitlets):
396 class A(HasTraitlets):
386 klass = Type('bad default', B)
397 klass = Type('bad default', B)
387
398
388 self.assertRaises(TraitletError, A)
399 self.assertRaises(TraitletError, A)
389
400
390 class C(HasTraitlets):
401 class C(HasTraitlets):
391 klass = Type(None, B, allow_none=False)
402 klass = Type(None, B, allow_none=False)
392
403
393 self.assertRaises(TraitletError, C)
404 self.assertRaises(TraitletError, C)
394
405
395 class TestInstance(TestCase):
406 class TestInstance(TestCase):
396
407
397 def test_basic(self):
408 def test_basic(self):
398 class Foo(object): pass
409 class Foo(object): pass
399 class Bar(Foo): pass
410 class Bar(Foo): pass
400 class Bah(object): pass
411 class Bah(object): pass
401
412
402 class A(HasTraitlets):
413 class A(HasTraitlets):
403 inst = Instance(Foo)
414 inst = Instance(Foo)
404
415
405 a = A()
416 a = A()
406 self.assert_(a.inst is None)
417 self.assert_(a.inst is None)
407 a.inst = Foo()
418 a.inst = Foo()
408 self.assert_(isinstance(a.inst, Foo))
419 self.assert_(isinstance(a.inst, Foo))
409 a.inst = Bar()
420 a.inst = Bar()
410 self.assert_(isinstance(a.inst, Foo))
421 self.assert_(isinstance(a.inst, Foo))
411 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
422 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
412 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
423 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
413 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
424 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
414
425
415 def test_unique_default_value(self):
426 def test_unique_default_value(self):
416 class Foo(object): pass
427 class Foo(object): pass
417 class A(HasTraitlets):
428 class A(HasTraitlets):
418 inst = Instance(Foo,(),{})
429 inst = Instance(Foo,(),{})
419
430
420 a = A()
431 a = A()
421 b = A()
432 b = A()
422 self.assert_(a.inst is not b.inst)
433 self.assert_(a.inst is not b.inst)
423
434
424 def test_args_kw(self):
435 def test_args_kw(self):
425 class Foo(object):
436 class Foo(object):
426 def __init__(self, c): self.c = c
437 def __init__(self, c): self.c = c
427 class Bar(object): pass
438 class Bar(object): pass
428 class Bah(object):
439 class Bah(object):
429 def __init__(self, c, d):
440 def __init__(self, c, d):
430 self.c = c; self.d = d
441 self.c = c; self.d = d
431
442
432 class A(HasTraitlets):
443 class A(HasTraitlets):
433 inst = Instance(Foo, (10,))
444 inst = Instance(Foo, (10,))
434 a = A()
445 a = A()
435 self.assertEquals(a.inst.c, 10)
446 self.assertEquals(a.inst.c, 10)
436
447
437 class B(HasTraitlets):
448 class B(HasTraitlets):
438 inst = Instance(Bah, args=(10,), kw=dict(d=20))
449 inst = Instance(Bah, args=(10,), kw=dict(d=20))
439 b = B()
450 b = B()
440 self.assertEquals(b.inst.c, 10)
451 self.assertEquals(b.inst.c, 10)
441 self.assertEquals(b.inst.d, 20)
452 self.assertEquals(b.inst.d, 20)
442
453
443 class C(HasTraitlets):
454 class C(HasTraitlets):
444 inst = Instance(Foo)
455 inst = Instance(Foo)
445 c = C()
456 c = C()
446 self.assert_(c.inst is None)
457 self.assert_(c.inst is None)
447
458
448 def test_bad_default(self):
459 def test_bad_default(self):
449 class Foo(object): pass
460 class Foo(object): pass
450
461
451 class A(HasTraitlets):
462 class A(HasTraitlets):
452 inst = Instance(Foo, allow_none=False)
463 inst = Instance(Foo, allow_none=False)
453
464
454 self.assertRaises(TraitletError, A)
465 self.assertRaises(TraitletError, A)
455
466
456 def test_instance(self):
467 def test_instance(self):
457 class Foo(object): pass
468 class Foo(object): pass
458
469
459 def inner():
470 def inner():
460 class A(HasTraitlets):
471 class A(HasTraitlets):
461 inst = Instance(Foo())
472 inst = Instance(Foo())
462
473
463 self.assertRaises(TraitletError, inner)
474 self.assertRaises(TraitletError, inner)
464
475
465
476
466 class TestThis(TestCase):
477 class TestThis(TestCase):
467
478
468 def test_this_class(self):
479 def test_this_class(self):
469 class Foo(HasTraitlets):
480 class Foo(HasTraitlets):
470 this = This
481 this = This
471
482
472 f = Foo()
483 f = Foo()
473 self.assertEquals(f.this, None)
484 self.assertEquals(f.this, None)
474 g = Foo()
485 g = Foo()
475 f.this = g
486 f.this = g
476 self.assertEquals(f.this, g)
487 self.assertEquals(f.this, g)
477 self.assertRaises(TraitletError, setattr, f, 'this', 10)
488 self.assertRaises(TraitletError, setattr, f, 'this', 10)
478
489
479 def test_this_inst(self):
490 def test_this_inst(self):
480 class Foo(HasTraitlets):
491 class Foo(HasTraitlets):
481 this = This()
492 this = This()
482
493
483 f = Foo()
494 f = Foo()
484 f.this = Foo()
495 f.this = Foo()
485 self.assert_(isinstance(f.this, Foo))
496 self.assert_(isinstance(f.this, Foo))
486
497
498 def test_subclass(self):
499 class Foo(HasTraitlets):
500 t = This()
501 class Bar(Foo):
502 pass
503 f = Foo()
504 b = Bar()
505 f.t = b
506 b.t = f
507 self.assertEquals(f.t, b)
508 self.assertEquals(b.t, f)
509
510 def test_subclass_override(self):
511 class Foo(HasTraitlets):
512 t = This()
513 class Bar(Foo):
514 t = This()
515 f = Foo()
516 b = Bar()
517 f.t = b
518 self.assertEquals(f.t, b)
519 self.assertRaises(TraitletError, setattr, b, 't', f)
487
520
488 class TraitletTestBase(TestCase):
521 class TraitletTestBase(TestCase):
489 """A best testing class for basic traitlet types."""
522 """A best testing class for basic traitlet types."""
490
523
491 def assign(self, value):
524 def assign(self, value):
492 self.obj.value = value
525 self.obj.value = value
493
526
494 def coerce(self, value):
527 def coerce(self, value):
495 return value
528 return value
496
529
497 def test_good_values(self):
530 def test_good_values(self):
498 if hasattr(self, '_good_values'):
531 if hasattr(self, '_good_values'):
499 for value in self._good_values:
532 for value in self._good_values:
500 self.assign(value)
533 self.assign(value)
501 self.assertEquals(self.obj.value, self.coerce(value))
534 self.assertEquals(self.obj.value, self.coerce(value))
502
535
503 def test_bad_values(self):
536 def test_bad_values(self):
504 if hasattr(self, '_bad_values'):
537 if hasattr(self, '_bad_values'):
505 for value in self._bad_values:
538 for value in self._bad_values:
506 self.assertRaises(TraitletError, self.assign, value)
539 self.assertRaises(TraitletError, self.assign, value)
507
540
508 def test_default_value(self):
541 def test_default_value(self):
509 if hasattr(self, '_default_value'):
542 if hasattr(self, '_default_value'):
510 self.assertEquals(self._default_value, self.obj.value)
543 self.assertEquals(self._default_value, self.obj.value)
511
544
512
545
513 class AnyTraitlet(HasTraitlets):
546 class AnyTraitlet(HasTraitlets):
514
547
515 value = Any
548 value = Any
516
549
517 class AnyTraitTest(TraitletTestBase):
550 class AnyTraitTest(TraitletTestBase):
518
551
519 obj = AnyTraitlet()
552 obj = AnyTraitlet()
520
553
521 _default_value = None
554 _default_value = None
522 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
555 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
523 _bad_values = []
556 _bad_values = []
524
557
525
558
526 class IntTraitlet(HasTraitlets):
559 class IntTraitlet(HasTraitlets):
527
560
528 value = Int(99)
561 value = Int(99)
529
562
530 class TestInt(TraitletTestBase):
563 class TestInt(TraitletTestBase):
531
564
532 obj = IntTraitlet()
565 obj = IntTraitlet()
533 _default_value = 99
566 _default_value = 99
534 _good_values = [10, -10]
567 _good_values = [10, -10]
535 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
568 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
536 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
569 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
537 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
570 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
538
571
539
572
540 class LongTraitlet(HasTraitlets):
573 class LongTraitlet(HasTraitlets):
541
574
542 value = Long(99L)
575 value = Long(99L)
543
576
544 class TestLong(TraitletTestBase):
577 class TestLong(TraitletTestBase):
545
578
546 obj = LongTraitlet()
579 obj = LongTraitlet()
547
580
548 _default_value = 99L
581 _default_value = 99L
549 _good_values = [10, -10, 10L, -10L]
582 _good_values = [10, -10, 10L, -10L]
550 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
583 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
551 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
584 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
552 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
585 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
553 u'-10.1']
586 u'-10.1']
554
587
555
588
556 class FloatTraitlet(HasTraitlets):
589 class FloatTraitlet(HasTraitlets):
557
590
558 value = Float(99.0)
591 value = Float(99.0)
559
592
560 class TestFloat(TraitletTestBase):
593 class TestFloat(TraitletTestBase):
561
594
562 obj = FloatTraitlet()
595 obj = FloatTraitlet()
563
596
564 _default_value = 99.0
597 _default_value = 99.0
565 _good_values = [10, -10, 10.1, -10.1]
598 _good_values = [10, -10, 10.1, -10.1]
566 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
599 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
567 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
600 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
568 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
601 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
569
602
570
603
571 class ComplexTraitlet(HasTraitlets):
604 class ComplexTraitlet(HasTraitlets):
572
605
573 value = Complex(99.0-99.0j)
606 value = Complex(99.0-99.0j)
574
607
575 class TestComplex(TraitletTestBase):
608 class TestComplex(TraitletTestBase):
576
609
577 obj = ComplexTraitlet()
610 obj = ComplexTraitlet()
578
611
579 _default_value = 99.0-99.0j
612 _default_value = 99.0-99.0j
580 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
613 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
581 10.1j, 10.1+10.1j, 10.1-10.1j]
614 10.1j, 10.1+10.1j, 10.1-10.1j]
582 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
615 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
583
616
584
617
585 class StringTraitlet(HasTraitlets):
618 class StringTraitlet(HasTraitlets):
586
619
587 value = Str('string')
620 value = Str('string')
588
621
589 class TestString(TraitletTestBase):
622 class TestString(TraitletTestBase):
590
623
591 obj = StringTraitlet()
624 obj = StringTraitlet()
592
625
593 _default_value = 'string'
626 _default_value = 'string'
594 _good_values = ['10', '-10', '10L',
627 _good_values = ['10', '-10', '10L',
595 '-10L', '10.1', '-10.1', 'string']
628 '-10L', '10.1', '-10.1', 'string']
596 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
629 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
597 ['ten'],{'ten': 10},(10,), None, u'string']
630 ['ten'],{'ten': 10},(10,), None, u'string']
598
631
599
632
600 class UnicodeTraitlet(HasTraitlets):
633 class UnicodeTraitlet(HasTraitlets):
601
634
602 value = Unicode(u'unicode')
635 value = Unicode(u'unicode')
603
636
604 class TestUnicode(TraitletTestBase):
637 class TestUnicode(TraitletTestBase):
605
638
606 obj = UnicodeTraitlet()
639 obj = UnicodeTraitlet()
607
640
608 _default_value = u'unicode'
641 _default_value = u'unicode'
609 _good_values = ['10', '-10', '10L', '-10L', '10.1',
642 _good_values = ['10', '-10', '10L', '-10L', '10.1',
610 '-10.1', '', u'', 'string', u'string', ]
643 '-10.1', '', u'', 'string', u'string', ]
611 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
644 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
612 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
645 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,751 +1,798 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A lightweight Traits like module.
4 A lightweight Traits like module.
5
5
6 This is designed to provide a lightweight, simple, pure Python version of
6 This is designed to provide a lightweight, simple, pure Python version of
7 many of the capabilities of enthought.traits. This includes:
7 many of the capabilities of enthought.traits. This includes:
8
8
9 * Validation
9 * Validation
10 * Type specification with defaults
10 * Type specification with defaults
11 * Static and dynamic notification
11 * Static and dynamic notification
12 * Basic predefined types
12 * Basic predefined types
13 * An API that is similar to enthought.traits
13 * An API that is similar to enthought.traits
14
14
15 We don't support:
15 We don't support:
16
16
17 * Delegation
17 * Delegation
18 * Automatic GUI generation
18 * Automatic GUI generation
19 * A full set of trait types. Most importantly, we don't provide container
19 * A full set of trait types. Most importantly, we don't provide container
20 traitlets (list, dict, tuple) that can trigger notifications if their
20 traitlets (list, dict, tuple) that can trigger notifications if their
21 contents change.
21 contents change.
22 * API compatibility with enthought.traits
22 * API compatibility with enthought.traits
23
23
24 There are also some important difference in our design:
24 There are also some important difference in our design:
25
25
26 * enthought.traits does not validate default values. We do.
26 * enthought.traits does not validate default values. We do.
27
27
28 We choose to create this module because we need these capabilities, but
28 We choose to create this module because we need these capabilities, but
29 we need them to be pure Python so they work in all Python implementations,
29 we need them to be pure Python so they work in all Python implementations,
30 including Jython and IronPython.
30 including Jython and IronPython.
31
31
32 Authors:
32 Authors:
33
33
34 * Brian Granger
34 * Brian Granger
35 * Enthought, Inc. Some of the code in this file comes from enthought.traits
35 * Enthought, Inc. Some of the code in this file comes from enthought.traits
36 and is licensed under the BSD license. Also, many of the ideas also come
36 and is licensed under the BSD license. Also, many of the ideas also come
37 from enthought.traits even though our implementation is very different.
37 from enthought.traits even though our implementation is very different.
38 """
38 """
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Copyright (C) 2008-2009 The IPython Development Team
41 # Copyright (C) 2008-2009 The IPython Development Team
42 #
42 #
43 # Distributed under the terms of the BSD License. The full license is in
43 # Distributed under the terms of the BSD License. The full license is in
44 # the file COPYING, distributed as part of this software.
44 # the file COPYING, distributed as part of this software.
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 #-----------------------------------------------------------------------------
47 #-----------------------------------------------------------------------------
48 # Imports
48 # Imports
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50
50
51
51
52 import inspect
52 import inspect
53 import sys
53 import sys
54 import types
54 import types
55 from types import InstanceType, ClassType
55 from types import InstanceType, ClassType
56
56
57 ClassTypes = (ClassType, type)
57 ClassTypes = (ClassType, type)
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Basic classes
60 # Basic classes
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class NoDefaultSpecified ( object ): pass
64 class NoDefaultSpecified ( object ): pass
65 NoDefaultSpecified = NoDefaultSpecified()
65 NoDefaultSpecified = NoDefaultSpecified()
66
66
67
67
68 class Undefined ( object ): pass
68 class Undefined ( object ): pass
69 Undefined = Undefined()
69 Undefined = Undefined()
70
70
71
71
72 class TraitletError(Exception):
72 class TraitletError(Exception):
73 pass
73 pass
74
74
75
75
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77 # Utilities
77 # Utilities
78 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
79
79
80
80
81 def class_of ( object ):
81 def class_of ( object ):
82 """ Returns a string containing the class name of an object with the
82 """ Returns a string containing the class name of an object with the
83 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
83 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
84 'a PlotValue').
84 'a PlotValue').
85 """
85 """
86 if isinstance( object, basestring ):
86 if isinstance( object, basestring ):
87 return add_article( object )
87 return add_article( object )
88
88
89 return add_article( object.__class__.__name__ )
89 return add_article( object.__class__.__name__ )
90
90
91
91
92 def add_article ( name ):
92 def add_article ( name ):
93 """ Returns a string containing the correct indefinite article ('a' or 'an')
93 """ Returns a string containing the correct indefinite article ('a' or 'an')
94 prefixed to the specified string.
94 prefixed to the specified string.
95 """
95 """
96 if name[:1].lower() in 'aeiou':
96 if name[:1].lower() in 'aeiou':
97 return 'an ' + name
97 return 'an ' + name
98
98
99 return 'a ' + name
99 return 'a ' + name
100
100
101
101
102 def repr_type(obj):
102 def repr_type(obj):
103 """ Return a string representation of a value and its type for readable
103 """ Return a string representation of a value and its type for readable
104 error messages.
104 error messages.
105 """
105 """
106 the_type = type(obj)
106 the_type = type(obj)
107 if the_type is InstanceType:
107 if the_type is InstanceType:
108 # Old-style class.
108 # Old-style class.
109 the_type = obj.__class__
109 the_type = obj.__class__
110 msg = '%r %r' % (obj, the_type)
110 msg = '%r %r' % (obj, the_type)
111 return msg
111 return msg
112
112
113
113
114 def parse_notifier_name(name):
114 def parse_notifier_name(name):
115 """Convert the name argument to a list of names.
115 """Convert the name argument to a list of names.
116
116
117 Examples
117 Examples
118 --------
118 --------
119
119
120 >>> parse_notifier_name('a')
120 >>> parse_notifier_name('a')
121 ['a']
121 ['a']
122 >>> parse_notifier_name(['a','b'])
122 >>> parse_notifier_name(['a','b'])
123 ['a', 'b']
123 ['a', 'b']
124 >>> parse_notifier_name(None)
124 >>> parse_notifier_name(None)
125 ['anytraitlet']
125 ['anytraitlet']
126 """
126 """
127 if isinstance(name, str):
127 if isinstance(name, str):
128 return [name]
128 return [name]
129 elif name is None:
129 elif name is None:
130 return ['anytraitlet']
130 return ['anytraitlet']
131 elif isinstance(name, (list, tuple)):
131 elif isinstance(name, (list, tuple)):
132 for n in name:
132 for n in name:
133 assert isinstance(n, str), "names must be strings"
133 assert isinstance(n, str), "names must be strings"
134 return name
134 return name
135
135
136 #-----------------------------------------------------------------------------
136 #-----------------------------------------------------------------------------
137 # Base TraitletType for all traitlets
137 # Base TraitletType for all traitlets
138 #-----------------------------------------------------------------------------
138 #-----------------------------------------------------------------------------
139
139
140
140
141 class TraitletType(object):
141 class TraitletType(object):
142 """A base class for all traitlet descriptors.
143
144 Notes
145 -----
146 Our implementation of traitlets is based on Python's descriptor
147 prototol. This class is the base class for all such descriptors. The
148 only magic we use is a custom metaclass for the main :class:`HasTraitlets`
149 class that does the following:
150
151 1. Sets the :attr:`name` attribute of every :class:`TraitletType`
152 instance in the class dict to the name of the attribute.
153 2. Sets the :attr:`this_class` attribute of every :class:`TraitletType`
154 instance in the class dict to the *class* that declared the traitlet.
155 This is used by the :class:`This` traitlet to allow subclasses to
156 accept superclasses for :class:`This` values.
157 """
158
142
159
143 metadata = {}
160 metadata = {}
144 default_value = Undefined
161 default_value = Undefined
145 info_text = 'any value'
162 info_text = 'any value'
146
163
147 def __init__(self, default_value=NoDefaultSpecified, **metadata):
164 def __init__(self, default_value=NoDefaultSpecified, **metadata):
148 """Create a TraitletType.
165 """Create a TraitletType.
149 """
166 """
150 if default_value is not NoDefaultSpecified:
167 if default_value is not NoDefaultSpecified:
151 self.default_value = default_value
168 self.default_value = default_value
152 self.metadata.update(metadata)
169 self.metadata.update(metadata)
153 self.init()
170 self.init()
154
171
155 def init(self):
172 def init(self):
156 pass
173 pass
157
174
158 def get_default_value(self):
175 def get_default_value(self):
159 """Create a new instance of the default value."""
176 """Create a new instance of the default value."""
160 dv = self.default_value
177 dv = self.default_value
161 return dv
178 return dv
162
179
163 def set_default_value(self, obj):
180 def set_default_value(self, obj):
164 dv = self.get_default_value()
181 dv = self.get_default_value()
165 newdv = self._validate(obj, dv)
182 newdv = self._validate(obj, dv)
166 obj._traitlet_values[self.name] = newdv
183 obj._traitlet_values[self.name] = newdv
167
184
168
185
169 def __get__(self, obj, cls=None):
186 def __get__(self, obj, cls=None):
170 """Get the value of the traitlet by self.name for the instance.
187 """Get the value of the traitlet by self.name for the instance.
171
188
172 Default values are instantiated when :meth:`HasTraitlets.__new__`
189 Default values are instantiated when :meth:`HasTraitlets.__new__`
173 is called. Thus by the time this method gets called either the
190 is called. Thus by the time this method gets called either the
174 default value or a user defined value (they called :meth:`__set__`)
191 default value or a user defined value (they called :meth:`__set__`)
175 is in the :class:`HasTraitlets` instance.
192 is in the :class:`HasTraitlets` instance.
176 """
193 """
177 if obj is None:
194 if obj is None:
178 return self
195 return self
179 else:
196 else:
180 try:
197 try:
181 value = obj._traitlet_values[self.name]
198 value = obj._traitlet_values[self.name]
182 except:
199 except:
183 # HasTraitlets should call set_default_value to populate
200 # HasTraitlets should call set_default_value to populate
184 # this. So this should never be reached.
201 # this. So this should never be reached.
185 raise TraitletError('Unexpected error in TraitletType: '
202 raise TraitletError('Unexpected error in TraitletType: '
186 'default value not set properly')
203 'default value not set properly')
187 else:
204 else:
188 return value
205 return value
189
206
190 def __set__(self, obj, value):
207 def __set__(self, obj, value):
191 new_value = self._validate(obj, value)
208 new_value = self._validate(obj, value)
192 old_value = self.__get__(obj)
209 old_value = self.__get__(obj)
193 if old_value != new_value:
210 if old_value != new_value:
194 obj._traitlet_values[self.name] = new_value
211 obj._traitlet_values[self.name] = new_value
195 obj._notify_traitlet(self.name, old_value, new_value)
212 obj._notify_traitlet(self.name, old_value, new_value)
196
213
197 def _validate(self, obj, value):
214 def _validate(self, obj, value):
198 if hasattr(self, 'validate'):
215 if hasattr(self, 'validate'):
199 return self.validate(obj, value)
216 return self.validate(obj, value)
200 elif hasattr(self, 'is_valid_for'):
217 elif hasattr(self, 'is_valid_for'):
201 valid = self.is_valid_for(value)
218 valid = self.is_valid_for(value)
202 if valid:
219 if valid:
203 return value
220 return value
204 else:
221 else:
205 raise TraitletError('invalid value for type: %r' % value)
222 raise TraitletError('invalid value for type: %r' % value)
206 elif hasattr(self, 'value_for'):
223 elif hasattr(self, 'value_for'):
207 return self.value_for(value)
224 return self.value_for(value)
208 else:
225 else:
209 return value
226 return value
210
227
211 def info(self):
228 def info(self):
212 return self.info_text
229 return self.info_text
213
230
214 def error(self, obj, value):
231 def error(self, obj, value):
215 if obj is not None:
232 if obj is not None:
216 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
233 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
217 % (self.name, class_of(obj),
234 % (self.name, class_of(obj),
218 self.info(), repr_type(value))
235 self.info(), repr_type(value))
219 else:
236 else:
220 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
237 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
221 % (self.name, self.info(), repr_type(value))
238 % (self.name, self.info(), repr_type(value))
222 raise TraitletError(e)
239 raise TraitletError(e)
223
240
224
241
225 #-----------------------------------------------------------------------------
242 #-----------------------------------------------------------------------------
226 # The HasTraitlets implementation
243 # The HasTraitlets implementation
227 #-----------------------------------------------------------------------------
244 #-----------------------------------------------------------------------------
228
245
229
246
230 class MetaHasTraitlets(type):
247 class MetaHasTraitlets(type):
231 """A metaclass for HasTraitlets.
248 """A metaclass for HasTraitlets.
232
249
233 This metaclass makes sure that any TraitletType class attributes are
250 This metaclass makes sure that any TraitletType class attributes are
234 instantiated and sets their name attribute.
251 instantiated and sets their name attribute.
235 """
252 """
236
253
237 def __new__(mcls, name, bases, classdict):
254 def __new__(mcls, name, bases, classdict):
255 """Create the HasTraitlets class.
256
257 This instantiates all TraitletTypes in the class dict and sets their
258 :attr:`name` attribute.
259 """
260 # print "========================="
261 # print "MetaHasTraitlets.__new__"
262 # print "mcls, ", mcls
263 # print "name, ", name
264 # print "bases, ", bases
265 # print "classdict, ", classdict
238 for k,v in classdict.iteritems():
266 for k,v in classdict.iteritems():
239 if isinstance(v, TraitletType):
267 if isinstance(v, TraitletType):
240 v.name = k
268 v.name = k
241 elif inspect.isclass(v):
269 elif inspect.isclass(v):
242 if issubclass(v, TraitletType):
270 if issubclass(v, TraitletType):
243 vinst = v()
271 vinst = v()
244 vinst.name = k
272 vinst.name = k
245 classdict[k] = vinst
273 classdict[k] = vinst
246 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
274 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
247
275
276 def __init__(cls, name, bases, classdict):
277 """Finish initializing the HasTraitlets class.
278
279 This sets the :attr:`this_class` attribute of each TraitletType in the
280 class dict to the newly created class ``cls``.
281 """
282 # print "========================="
283 # print "MetaHasTraitlets.__init__"
284 # print "cls, ", cls
285 # print "name, ", name
286 # print "bases, ", bases
287 # print "classdict, ", classdict
288 for k, v in classdict.iteritems():
289 if isinstance(v, TraitletType):
290 v.this_class = cls
291 super(MetaHasTraitlets, cls).__init__(name, bases, classdict)
248
292
249 class HasTraitlets(object):
293 class HasTraitlets(object):
250
294
251 __metaclass__ = MetaHasTraitlets
295 __metaclass__ = MetaHasTraitlets
252
296
253 def __new__(cls, *args, **kw):
297 def __new__(cls, *args, **kw):
254 inst = super(HasTraitlets, cls).__new__(cls, *args, **kw)
298 inst = super(HasTraitlets, cls).__new__(cls, *args, **kw)
255 inst._traitlet_values = {}
299 inst._traitlet_values = {}
256 inst._traitlet_notifiers = {}
300 inst._traitlet_notifiers = {}
257 # Here we tell all the TraitletType instances to set their default
301 # Here we tell all the TraitletType instances to set their default
258 # values on the instance.
302 # values on the instance.
259 for key in dir(cls):
303 for key in dir(cls):
260 value = getattr(cls, key)
304 value = getattr(cls, key)
261 if isinstance(value, TraitletType):
305 if isinstance(value, TraitletType):
262 # print 'value: ', value
306 # print 'value: ', value
263 value.set_default_value(inst)
307 value.set_default_value(inst)
264 return inst
308 return inst
265
309
266 # def __init__(self):
310 # def __init__(self):
267 # self._traitlet_values = {}
311 # self._traitlet_values = {}
268 # self._traitlet_notifiers = {}
312 # self._traitlet_notifiers = {}
269
313
270 def _notify_traitlet(self, name, old_value, new_value):
314 def _notify_traitlet(self, name, old_value, new_value):
271
315
272 # First dynamic ones
316 # First dynamic ones
273 callables = self._traitlet_notifiers.get(name,[])
317 callables = self._traitlet_notifiers.get(name,[])
274 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
318 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
275 callables.extend(more_callables)
319 callables.extend(more_callables)
276
320
277 # Now static ones
321 # Now static ones
278 try:
322 try:
279 cb = getattr(self, '_%s_changed' % name)
323 cb = getattr(self, '_%s_changed' % name)
280 except:
324 except:
281 pass
325 pass
282 else:
326 else:
283 callables.append(cb)
327 callables.append(cb)
284
328
285 # Call them all now
329 # Call them all now
286 for c in callables:
330 for c in callables:
287 # Traits catches and logs errors here. I allow them to raise
331 # Traits catches and logs errors here. I allow them to raise
288 if callable(c):
332 if callable(c):
289 argspec = inspect.getargspec(c)
333 argspec = inspect.getargspec(c)
290 nargs = len(argspec[0])
334 nargs = len(argspec[0])
291 # Bound methods have an additional 'self' argument
335 # Bound methods have an additional 'self' argument
292 # I don't know how to treat unbound methods, but they
336 # I don't know how to treat unbound methods, but they
293 # can't really be used for callbacks.
337 # can't really be used for callbacks.
294 if isinstance(c, types.MethodType):
338 if isinstance(c, types.MethodType):
295 offset = -1
339 offset = -1
296 else:
340 else:
297 offset = 0
341 offset = 0
298 if nargs + offset == 0:
342 if nargs + offset == 0:
299 c()
343 c()
300 elif nargs + offset == 1:
344 elif nargs + offset == 1:
301 c(name)
345 c(name)
302 elif nargs + offset == 2:
346 elif nargs + offset == 2:
303 c(name, new_value)
347 c(name, new_value)
304 elif nargs + offset == 3:
348 elif nargs + offset == 3:
305 c(name, old_value, new_value)
349 c(name, old_value, new_value)
306 else:
350 else:
307 raise TraitletError('a traitlet changed callback '
351 raise TraitletError('a traitlet changed callback '
308 'must have 0-3 arguments.')
352 'must have 0-3 arguments.')
309 else:
353 else:
310 raise TraitletError('a traitlet changed callback '
354 raise TraitletError('a traitlet changed callback '
311 'must be callable.')
355 'must be callable.')
312
356
313
357
314 def _add_notifiers(self, handler, name):
358 def _add_notifiers(self, handler, name):
315 if not self._traitlet_notifiers.has_key(name):
359 if not self._traitlet_notifiers.has_key(name):
316 nlist = []
360 nlist = []
317 self._traitlet_notifiers[name] = nlist
361 self._traitlet_notifiers[name] = nlist
318 else:
362 else:
319 nlist = self._traitlet_notifiers[name]
363 nlist = self._traitlet_notifiers[name]
320 if handler not in nlist:
364 if handler not in nlist:
321 nlist.append(handler)
365 nlist.append(handler)
322
366
323 def _remove_notifiers(self, handler, name):
367 def _remove_notifiers(self, handler, name):
324 if self._traitlet_notifiers.has_key(name):
368 if self._traitlet_notifiers.has_key(name):
325 nlist = self._traitlet_notifiers[name]
369 nlist = self._traitlet_notifiers[name]
326 try:
370 try:
327 index = nlist.index(handler)
371 index = nlist.index(handler)
328 except ValueError:
372 except ValueError:
329 pass
373 pass
330 else:
374 else:
331 del nlist[index]
375 del nlist[index]
332
376
333 def on_traitlet_change(self, handler, name=None, remove=False):
377 def on_traitlet_change(self, handler, name=None, remove=False):
334 """Setup a handler to be called when a traitlet changes.
378 """Setup a handler to be called when a traitlet changes.
335
379
336 This is used to setup dynamic notifications of traitlet changes.
380 This is used to setup dynamic notifications of traitlet changes.
337
381
338 Static handlers can be created by creating methods on a HasTraitlets
382 Static handlers can be created by creating methods on a HasTraitlets
339 subclass with the naming convention '_[traitletname]_changed'. Thus,
383 subclass with the naming convention '_[traitletname]_changed'. Thus,
340 to create static handler for the traitlet 'a', create the method
384 to create static handler for the traitlet 'a', create the method
341 _a_changed(self, name, old, new) (fewer arguments can be used, see
385 _a_changed(self, name, old, new) (fewer arguments can be used, see
342 below).
386 below).
343
387
344 Parameters
388 Parameters
345 ----------
389 ----------
346 handler : callable
390 handler : callable
347 A callable that is called when a traitlet changes. Its
391 A callable that is called when a traitlet changes. Its
348 signature can be handler(), handler(name), handler(name, new)
392 signature can be handler(), handler(name), handler(name, new)
349 or handler(name, old, new).
393 or handler(name, old, new).
350 name : list, str, None
394 name : list, str, None
351 If None, the handler will apply to all traitlets. If a list
395 If None, the handler will apply to all traitlets. If a list
352 of str, handler will apply to all names in the list. If a
396 of str, handler will apply to all names in the list. If a
353 str, the handler will apply just to that name.
397 str, the handler will apply just to that name.
354 remove : bool
398 remove : bool
355 If False (the default), then install the handler. If True
399 If False (the default), then install the handler. If True
356 then unintall it.
400 then unintall it.
357 """
401 """
358 if remove:
402 if remove:
359 names = parse_notifier_name(name)
403 names = parse_notifier_name(name)
360 for n in names:
404 for n in names:
361 self._remove_notifiers(handler, n)
405 self._remove_notifiers(handler, n)
362 else:
406 else:
363 names = parse_notifier_name(name)
407 names = parse_notifier_name(name)
364 for n in names:
408 for n in names:
365 self._add_notifiers(handler, n)
409 self._add_notifiers(handler, n)
366
410
367 def traitlet_keys(self):
411 def traitlet_names(self):
368 """Get a list of all the names of this classes traitlets."""
412 """Get a list of all the names of this classes traitlets."""
369 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
413 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
370
414
371
415
372 #-----------------------------------------------------------------------------
416 #-----------------------------------------------------------------------------
373 # Actual TraitletTypes implementations/subclasses
417 # Actual TraitletTypes implementations/subclasses
374 #-----------------------------------------------------------------------------
418 #-----------------------------------------------------------------------------
375
419
376 #-----------------------------------------------------------------------------
420 #-----------------------------------------------------------------------------
377 # TraitletTypes subclasses for handling classes and instances of classes
421 # TraitletTypes subclasses for handling classes and instances of classes
378 #-----------------------------------------------------------------------------
422 #-----------------------------------------------------------------------------
379
423
380
424
381 class ClassBasedTraitletType(TraitletType):
425 class ClassBasedTraitletType(TraitletType):
382 """A traitlet with error reporting for Type, Instance and This."""
426 """A traitlet with error reporting for Type, Instance and This."""
383
427
384 def error(self, obj, value):
428 def error(self, obj, value):
385 kind = type(value)
429 kind = type(value)
386 if kind is InstanceType:
430 if kind is InstanceType:
387 msg = 'class %s' % value.__class__.__name__
431 msg = 'class %s' % value.__class__.__name__
388 else:
432 else:
389 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
433 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
390
434
391 super(ClassBasedTraitletType, self).error(obj, msg)
435 super(ClassBasedTraitletType, self).error(obj, msg)
392
436
393
437
394 class Type(ClassBasedTraitletType):
438 class Type(ClassBasedTraitletType):
395 """A traitlet whose value must be a subclass of a specified class."""
439 """A traitlet whose value must be a subclass of a specified class."""
396
440
397 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
441 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
398 """Construct a Type traitlet
442 """Construct a Type traitlet
399
443
400 A Type traitlet specifies that its values must be subclasses of
444 A Type traitlet specifies that its values must be subclasses of
401 a particular class.
445 a particular class.
402
446
403 Parameters
447 Parameters
404 ----------
448 ----------
405 default_value : class
449 default_value : class
406 The default value must be a subclass of klass.
450 The default value must be a subclass of klass.
407 klass : class, str, None
451 klass : class, str, None
408 Values of this traitlet must be a subclass of klass. The klass
452 Values of this traitlet must be a subclass of klass. The klass
409 may be specified in a string like: 'foo.bar.MyClass'.
453 may be specified in a string like: 'foo.bar.MyClass'.
410 allow_none : boolean
454 allow_none : boolean
411 Indicates whether None is allowed as an assignable value. Even if
455 Indicates whether None is allowed as an assignable value. Even if
412 ``False``, the default value may be ``None``.
456 ``False``, the default value may be ``None``.
413 """
457 """
414 if default_value is None:
458 if default_value is None:
415 if klass is None:
459 if klass is None:
416 klass = object
460 klass = object
417 elif klass is None:
461 elif klass is None:
418 klass = default_value
462 klass = default_value
419
463
420 if not inspect.isclass(klass):
464 if not inspect.isclass(klass):
421 raise TraitletError("A Type traitlet must specify a class.")
465 raise TraitletError("A Type traitlet must specify a class.")
422
466
423 self.klass = klass
467 self.klass = klass
424 self._allow_none = allow_none
468 self._allow_none = allow_none
425
469
426 super(Type, self).__init__(default_value, **metadata)
470 super(Type, self).__init__(default_value, **metadata)
427
471
428 def validate(self, obj, value):
472 def validate(self, obj, value):
429 """Validates that the value is a valid object instance."""
473 """Validates that the value is a valid object instance."""
430 try:
474 try:
431 if issubclass(value, self.klass):
475 if issubclass(value, self.klass):
432 return value
476 return value
433 except:
477 except:
434 if (value is None) and (self._allow_none):
478 if (value is None) and (self._allow_none):
435 return value
479 return value
436
480
437 self.error(obj, value)
481 self.error(obj, value)
438
482
439 def info(self):
483 def info(self):
440 """ Returns a description of the trait."""
484 """ Returns a description of the trait."""
441 klass = self.klass.__name__
485 klass = self.klass.__name__
442 result = 'a subclass of ' + klass
486 result = 'a subclass of ' + klass
443 if self._allow_none:
487 if self._allow_none:
444 return result + ' or None'
488 return result + ' or None'
445 return result
489 return result
446
490
447
491
448 class DefaultValueGenerator(object):
492 class DefaultValueGenerator(object):
449 """A class for generating new default value instances."""
493 """A class for generating new default value instances."""
450
494
451 def __init__(self, klass, *args, **kw):
495 def __init__(self, klass, *args, **kw):
452 self.klass = klass
496 self.klass = klass
453 self.args = args
497 self.args = args
454 self.kw = kw
498 self.kw = kw
455
499
456 def generate(self):
500 def generate(self):
457 return self.klass(*self.args, **self.kw)
501 return self.klass(*self.args, **self.kw)
458
502
459
503
460 class Instance(ClassBasedTraitletType):
504 class Instance(ClassBasedTraitletType):
461 """A trait whose value must be an instance of a specified class.
505 """A trait whose value must be an instance of a specified class.
462
506
463 The value can also be an instance of a subclass of the specified class.
507 The value can also be an instance of a subclass of the specified class.
464 """
508 """
465
509
466 def __init__(self, klass=None, args=None, kw=None,
510 def __init__(self, klass=None, args=None, kw=None,
467 allow_none=True, **metadata ):
511 allow_none=True, **metadata ):
468 """Construct an Instance traitlet.
512 """Construct an Instance traitlet.
469
513
470 This traitlet allows values that are instances of a particular
514 This traitlet allows values that are instances of a particular
471 class or its sublclasses. Our implementation is quite different
515 class or its sublclasses. Our implementation is quite different
472 from that of enthough.traits as we don't allow instances to be used
516 from that of enthough.traits as we don't allow instances to be used
473 for klass and we handle the ``args`` and ``kw`` arguments differently.
517 for klass and we handle the ``args`` and ``kw`` arguments differently.
474
518
475 Parameters
519 Parameters
476 ----------
520 ----------
477 klass : class
521 klass : class
478 The class that forms the basis for the traitlet. Instances
522 The class that forms the basis for the traitlet. Instances
479 and strings are not allowed.
523 and strings are not allowed.
480 args : tuple
524 args : tuple
481 Positional arguments for generating the default value.
525 Positional arguments for generating the default value.
482 kw : dict
526 kw : dict
483 Keyword arguments for generating the default value.
527 Keyword arguments for generating the default value.
484 allow_none : bool
528 allow_none : bool
485 Indicates whether None is allowed as a value.
529 Indicates whether None is allowed as a value.
486
530
487 Default Value
531 Default Value
488 -------------
532 -------------
489 If both ``args`` and ``kw`` are None, then the default value is None.
533 If both ``args`` and ``kw`` are None, then the default value is None.
490 If ``args`` is a tuple and ``kw`` is a dict, then the default is
534 If ``args`` is a tuple and ``kw`` is a dict, then the default is
491 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
535 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
492 not (but not both), None is replace by ``()`` or ``{}``.
536 not (but not both), None is replace by ``()`` or ``{}``.
493 """
537 """
494
538
495 self._allow_none = allow_none
539 self._allow_none = allow_none
496
540
497 if (klass is None) or (not inspect.isclass(klass)):
541 if (klass is None) or (not inspect.isclass(klass)):
498 raise TraitletError('The klass argument must be a class'
542 raise TraitletError('The klass argument must be a class'
499 ' you gave: %r' % klass)
543 ' you gave: %r' % klass)
500 self.klass = klass
544 self.klass = klass
501
545
502 # self.klass is a class, so handle default_value
546 # self.klass is a class, so handle default_value
503 if args is None and kw is None:
547 if args is None and kw is None:
504 default_value = None
548 default_value = None
505 else:
549 else:
506 if args is None:
550 if args is None:
507 # kw is not None
551 # kw is not None
508 args = ()
552 args = ()
509 elif kw is None:
553 elif kw is None:
510 # args is not None
554 # args is not None
511 kw = {}
555 kw = {}
512
556
513 if not isinstance(kw, dict):
557 if not isinstance(kw, dict):
514 raise TraitletError("The 'kw' argument must be a dict or None.")
558 raise TraitletError("The 'kw' argument must be a dict or None.")
515 if not isinstance(args, tuple):
559 if not isinstance(args, tuple):
516 raise TraitletError("The 'args' argument must be a tuple or None.")
560 raise TraitletError("The 'args' argument must be a tuple or None.")
517
561
518 default_value = DefaultValueGenerator(self.klass, *args, **kw)
562 default_value = DefaultValueGenerator(self.klass, *args, **kw)
519
563
520 super(Instance, self).__init__(default_value, **metadata)
564 super(Instance, self).__init__(default_value, **metadata)
521
565
522 def validate(self, obj, value):
566 def validate(self, obj, value):
523 if value is None:
567 if value is None:
524 if self._allow_none:
568 if self._allow_none:
525 return value
569 return value
526 self.error(obj, value)
570 self.error(obj, value)
527
571
528 if isinstance(value, self.klass):
572 if isinstance(value, self.klass):
529 return value
573 return value
530 else:
574 else:
531 self.error(obj, value)
575 self.error(obj, value)
532
576
533 def info(self):
577 def info(self):
534 klass = self.klass.__name__
578 klass = self.klass.__name__
535 result = class_of(klass)
579 result = class_of(klass)
536 if self._allow_none:
580 if self._allow_none:
537 return result + ' or None'
581 return result + ' or None'
538
582
539 return result
583 return result
540
584
541 def get_default_value(self):
585 def get_default_value(self):
542 """Instantiate a default value instance.
586 """Instantiate a default value instance.
543
587
544 This is called when the containing HasTraitlets classes'
588 This is called when the containing HasTraitlets classes'
545 :meth:`__new__` method is called to ensure that a unique instance
589 :meth:`__new__` method is called to ensure that a unique instance
546 is created for each HasTraitlets instance.
590 is created for each HasTraitlets instance.
547 """
591 """
548 dv = self.default_value
592 dv = self.default_value
549 if isinstance(dv, DefaultValueGenerator):
593 if isinstance(dv, DefaultValueGenerator):
550 return dv.generate()
594 return dv.generate()
551 else:
595 else:
552 return dv
596 return dv
553
597
554
598
555 class This(ClassBasedTraitletType):
599 class This(ClassBasedTraitletType):
556 """A traitlet for instances of the class containing this trait.
600 """A traitlet for instances of the class containing this trait.
557
601
558 Because how how and when class bodies are executed, the ``This``
602 Because how how and when class bodies are executed, the ``This``
559 traitlet can only have a default value of None. This, and because we
603 traitlet can only have a default value of None. This, and because we
560 always validate default values, ``allow_none`` is *always* true.
604 always validate default values, ``allow_none`` is *always* true.
561 """
605 """
562
606
563 info_text = 'an instance of the same type as the receiver or None'
607 info_text = 'an instance of the same type as the receiver or None'
564
608
565 def __init__(self, **metadata):
609 def __init__(self, **metadata):
566 super(This, self).__init__(None, **metadata)
610 super(This, self).__init__(None, **metadata)
567
611
568 def validate(self, obj, value):
612 def validate(self, obj, value):
569 if isinstance(value, obj.__class__) or (value is None):
613 # What if value is a superclass of obj.__class__? This is
614 # complicated if it was the superclass that defined the This
615 # traitlet.
616 if isinstance(value, self.this_class) or (value is None):
570 return value
617 return value
571 else:
618 else:
572 self.error(obj, value)
619 self.error(obj, value)
573
620
574
621
575 #-----------------------------------------------------------------------------
622 #-----------------------------------------------------------------------------
576 # Basic TraitletTypes implementations/subclasses
623 # Basic TraitletTypes implementations/subclasses
577 #-----------------------------------------------------------------------------
624 #-----------------------------------------------------------------------------
578
625
579
626
580 class Any(TraitletType):
627 class Any(TraitletType):
581 default_value = None
628 default_value = None
582 info_text = 'any value'
629 info_text = 'any value'
583
630
584
631
585 class Int(TraitletType):
632 class Int(TraitletType):
586 """A integer traitlet."""
633 """A integer traitlet."""
587
634
588 evaluate = int
635 evaluate = int
589 default_value = 0
636 default_value = 0
590 info_text = 'an integer'
637 info_text = 'an integer'
591
638
592 def validate(self, obj, value):
639 def validate(self, obj, value):
593 if isinstance(value, int):
640 if isinstance(value, int):
594 return value
641 return value
595 self.error(obj, value)
642 self.error(obj, value)
596
643
597 class CInt(Int):
644 class CInt(Int):
598 """A casting version of the int traitlet."""
645 """A casting version of the int traitlet."""
599
646
600 def validate(self, obj, value):
647 def validate(self, obj, value):
601 try:
648 try:
602 return int(value)
649 return int(value)
603 except:
650 except:
604 self.error(obj, value)
651 self.error(obj, value)
605
652
606
653
607 class Long(TraitletType):
654 class Long(TraitletType):
608 """A long integer traitlet."""
655 """A long integer traitlet."""
609
656
610 evaluate = long
657 evaluate = long
611 default_value = 0L
658 default_value = 0L
612 info_text = 'a long'
659 info_text = 'a long'
613
660
614 def validate(self, obj, value):
661 def validate(self, obj, value):
615 if isinstance(value, long):
662 if isinstance(value, long):
616 return value
663 return value
617 if isinstance(value, int):
664 if isinstance(value, int):
618 return long(value)
665 return long(value)
619 self.error(obj, value)
666 self.error(obj, value)
620
667
621
668
622 class CLong(Long):
669 class CLong(Long):
623 """A casting version of the long integer traitlet."""
670 """A casting version of the long integer traitlet."""
624
671
625 def validate(self, obj, value):
672 def validate(self, obj, value):
626 try:
673 try:
627 return long(value)
674 return long(value)
628 except:
675 except:
629 self.error(obj, value)
676 self.error(obj, value)
630
677
631
678
632 class Float(TraitletType):
679 class Float(TraitletType):
633 """A float traitlet."""
680 """A float traitlet."""
634
681
635 evaluate = float
682 evaluate = float
636 default_value = 0.0
683 default_value = 0.0
637 info_text = 'a float'
684 info_text = 'a float'
638
685
639 def validate(self, obj, value):
686 def validate(self, obj, value):
640 if isinstance(value, float):
687 if isinstance(value, float):
641 return value
688 return value
642 if isinstance(value, int):
689 if isinstance(value, int):
643 return float(value)
690 return float(value)
644 self.error(obj, value)
691 self.error(obj, value)
645
692
646
693
647 class CFloat(Float):
694 class CFloat(Float):
648 """A casting version of the float traitlet."""
695 """A casting version of the float traitlet."""
649
696
650 def validate(self, obj, value):
697 def validate(self, obj, value):
651 try:
698 try:
652 return float(value)
699 return float(value)
653 except:
700 except:
654 self.error(obj, value)
701 self.error(obj, value)
655
702
656 class Complex(TraitletType):
703 class Complex(TraitletType):
657 """A traitlet for complex numbers."""
704 """A traitlet for complex numbers."""
658
705
659 evaluate = complex
706 evaluate = complex
660 default_value = 0.0 + 0.0j
707 default_value = 0.0 + 0.0j
661 info_text = 'a complex number'
708 info_text = 'a complex number'
662
709
663 def validate(self, obj, value):
710 def validate(self, obj, value):
664 if isinstance(value, complex):
711 if isinstance(value, complex):
665 return value
712 return value
666 if isinstance(value, (float, int)):
713 if isinstance(value, (float, int)):
667 return complex(value)
714 return complex(value)
668 self.error(obj, value)
715 self.error(obj, value)
669
716
670
717
671 class CComplex(Complex):
718 class CComplex(Complex):
672 """A casting version of the complex number traitlet."""
719 """A casting version of the complex number traitlet."""
673
720
674 def validate (self, obj, value):
721 def validate (self, obj, value):
675 try:
722 try:
676 return complex(value)
723 return complex(value)
677 except:
724 except:
678 self.error(obj, value)
725 self.error(obj, value)
679
726
680
727
681 class Str(TraitletType):
728 class Str(TraitletType):
682 """A traitlet for strings."""
729 """A traitlet for strings."""
683
730
684 evaluate = lambda x: x
731 evaluate = lambda x: x
685 default_value = ''
732 default_value = ''
686 info_text = 'a string'
733 info_text = 'a string'
687
734
688 def validate(self, obj, value):
735 def validate(self, obj, value):
689 if isinstance(value, str):
736 if isinstance(value, str):
690 return value
737 return value
691 self.error(obj, value)
738 self.error(obj, value)
692
739
693
740
694 class CStr(Str):
741 class CStr(Str):
695 """A casting version of the string traitlet."""
742 """A casting version of the string traitlet."""
696
743
697 def validate(self, obj, value):
744 def validate(self, obj, value):
698 try:
745 try:
699 return str(value)
746 return str(value)
700 except:
747 except:
701 try:
748 try:
702 return unicode(value)
749 return unicode(value)
703 except:
750 except:
704 self.error(obj, value)
751 self.error(obj, value)
705
752
706
753
707 class Unicode(TraitletType):
754 class Unicode(TraitletType):
708 """A traitlet for unicode strings."""
755 """A traitlet for unicode strings."""
709
756
710 evaluate = unicode
757 evaluate = unicode
711 default_value = u''
758 default_value = u''
712 info_text = 'a unicode string'
759 info_text = 'a unicode string'
713
760
714 def validate(self, obj, value):
761 def validate(self, obj, value):
715 if isinstance(value, unicode):
762 if isinstance(value, unicode):
716 return value
763 return value
717 if isinstance(value, str):
764 if isinstance(value, str):
718 return unicode(value)
765 return unicode(value)
719 self.error(obj, value)
766 self.error(obj, value)
720
767
721
768
722 class CUnicode(Unicode):
769 class CUnicode(Unicode):
723 """A casting version of the unicode traitlet."""
770 """A casting version of the unicode traitlet."""
724
771
725 def validate(self, obj, value):
772 def validate(self, obj, value):
726 try:
773 try:
727 return unicode(value)
774 return unicode(value)
728 except:
775 except:
729 self.error(obj, value)
776 self.error(obj, value)
730
777
731
778
732 class Bool(TraitletType):
779 class Bool(TraitletType):
733 """A boolean (True, False) traitlet."""
780 """A boolean (True, False) traitlet."""
734 evaluate = bool
781 evaluate = bool
735 default_value = False
782 default_value = False
736 info_text = 'a boolean'
783 info_text = 'a boolean'
737
784
738 def validate(self, obj, value):
785 def validate(self, obj, value):
739 if isinstance(value, bool):
786 if isinstance(value, bool):
740 return value
787 return value
741 self.error(obj, value)
788 self.error(obj, value)
742
789
743
790
744 class CBool(Bool):
791 class CBool(Bool):
745 """A casting version of the boolean traitlet."""
792 """A casting version of the boolean traitlet."""
746
793
747 def validate(self, obj, value):
794 def validate(self, obj, value):
748 try:
795 try:
749 return bool(value)
796 return bool(value)
750 except:
797 except:
751 self.error(obj, value) No newline at end of file
798 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now