##// END OF EJS Templates
Improvement to how config is handled in Components....
Brian Granger -
Show More
@@ -1,214 +1,225 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 from copy import deepcopy
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):
37 class ComponentError(Exception):
38 pass
38 pass
39
39
40 class MetaComponentTracker(type):
40 class MetaComponentTracker(type):
41 """A metaclass that tracks instances of Components and its subclasses."""
41 """A metaclass that tracks instances of Components and its subclasses."""
42
42
43 def __init__(cls, name, bases, d):
43 def __init__(cls, name, bases, d):
44 super(MetaComponentTracker, cls).__init__(name, bases, d)
44 super(MetaComponentTracker, cls).__init__(name, bases, d)
45 cls.__instance_refs = WeakValueDictionary()
45 cls.__instance_refs = WeakValueDictionary()
46 cls.__numcreated = 0
46 cls.__numcreated = 0
47
47
48 def __call__(cls, *args, **kw):
48 def __call__(cls, *args, **kw):
49 """Called when *class* is called (instantiated)!!!
49 """Called when *class* is called (instantiated)!!!
50
50
51 When a Component or subclass is instantiated, this is called and
51 When a Component or subclass is instantiated, this is called and
52 the instance is saved in a WeakValueDictionary for tracking.
52 the instance is saved in a WeakValueDictionary for tracking.
53 """
53 """
54
54
55 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
55 instance = super(MetaComponentTracker, cls).__call__(*args, **kw)
56 for c in cls.__mro__:
56 for c in cls.__mro__:
57 if issubclass(cls, c) and issubclass(c, Component):
57 if issubclass(cls, c) and issubclass(c, Component):
58 c.__numcreated += 1
58 c.__numcreated += 1
59 c.__instance_refs[c.__numcreated] = instance
59 c.__instance_refs[c.__numcreated] = instance
60 return instance
60 return instance
61
61
62 def get_instances(cls, name=None, klass=None, root=None):
62 def get_instances(cls, name=None, klass=None, root=None):
63 """Get all instances of cls and its subclasses.
63 """Get all instances of cls and its subclasses.
64
64
65 Parameters
65 Parameters
66 ----------
66 ----------
67 name : str
67 name : str
68 Limit to components with this name.
68 Limit to components with this name.
69 klass : class
69 klass : class
70 Limit to components having isinstance(component, klass)
70 Limit to components having isinstance(component, klass)
71 root : Component or subclass
71 root : Component or subclass
72 Limit to components having this root.
72 Limit to components having this root.
73 """
73 """
74 instances = cls.__instance_refs.values()
74 instances = cls.__instance_refs.values()
75 if name is not None:
75 if name is not None:
76 instances = [i for i in instances if i.name == name]
76 instances = [i for i in instances if i.name == name]
77 if klass is not None:
77 if klass is not None:
78 instances = [i for i in instances if isinstance(i, klass)]
78 instances = [i for i in instances if isinstance(i, klass)]
79 if root is not None:
79 if root is not None:
80 instances = [i for i in instances if i.root == root]
80 instances = [i for i in instances if i.root == root]
81 return instances
81 return instances
82
82
83 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):
84 """Get all instances of cls, i such that call(i)==True.
84 """Get all instances of cls, i such that call(i)==True.
85
85
86 This also takes the ``name``, ``klass`` and ``root`` arguments of
86 This also takes the ``name``, ``klass`` and ``root`` arguments of
87 :meth:`get_instance`
87 :meth:`get_instance`
88 """
88 """
89 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)]
90
90
91
91
92 class ComponentNameGenerator(object):
92 class ComponentNameGenerator(object):
93 """A Singleton to generate unique component names."""
93 """A Singleton to generate unique component names."""
94
94
95 def __init__(self, prefix):
95 def __init__(self, prefix):
96 self.prefix = prefix
96 self.prefix = prefix
97 self.i = 0
97 self.i = 0
98
98
99 def __call__(self):
99 def __call__(self):
100 count = self.i
100 count = self.i
101 self.i += 1
101 self.i += 1
102 return "%s%s" % (self.prefix, count)
102 return "%s%s" % (self.prefix, count)
103
103
104
104
105 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
105 ComponentNameGenerator = ComponentNameGenerator('ipython.component')
106
106
107
107
108 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
108 class MetaComponent(MetaHasTraitlets, MetaComponentTracker):
109 pass
109 pass
110
110
111
111
112 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
113 # Component implementation
113 # Component implementation
114 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
115
115
116
116
117 class Component(HasTraitlets):
117 class Component(HasTraitlets):
118
118
119 __metaclass__ = MetaComponent
119 __metaclass__ = MetaComponent
120
120
121 # Traitlets are fun!
121 # Traitlets are fun!
122 config = Instance(Struct,(),{})
122 config = Instance(Struct,(),{})
123 parent = This()
123 parent = This()
124 root = This()
124 root = This()
125
125
126 def __init__(self, parent, name=None, config=None):
126 def __init__(self, parent, name=None, config=None):
127 """Create a component given a parent and possibly and name and config.
127 """Create a component given a parent and possibly and name and config.
128
128
129 Parameters
129 Parameters
130 ----------
130 ----------
131 parent : Component subclass
131 parent : Component subclass
132 The parent in the component graph. The parent is used
132 The parent in the component graph. The parent is used
133 to get the root of the component graph.
133 to get the root of the component graph.
134 name : str
134 name : str
135 The unique name of the component. If empty, then a unique
135 The unique name of the component. If empty, then a unique
136 one will be autogenerated.
136 one will be autogenerated.
137 config : Config
137 config : Struct
138 If this is empty, self.config = parent.config, otherwise
138 If this is empty, self.config = parent.config, otherwise
139 self.config = config and root.config is ignored. This argument
139 self.config = config and root.config is ignored. This argument
140 should only be used to *override* the automatic inheritance of
140 should only be used to *override* the automatic inheritance of
141 parent.config. If a caller wants to modify parent.config
141 parent.config. If a caller wants to modify parent.config
142 (not override), the caller should make a copy and change
142 (not override), the caller should make a copy and change
143 attributes and then pass the copy to this argument.
143 attributes and then pass the copy to this argument.
144
144
145 Notes
145 Notes
146 -----
146 -----
147 Subclasses of Component must call the :meth:`__init__` method of
147 Subclasses of Component must call the :meth:`__init__` method of
148 :class:`Component` *before* doing anything else and using
148 :class:`Component` *before* doing anything else and using
149 :func:`super`::
149 :func:`super`::
150
150
151 class MyComponent(Component):
151 class MyComponent(Component):
152 def __init__(self, parent, name=None, config=None):
152 def __init__(self, parent, name=None, config=None):
153 super(MyComponent, self).__init__(parent, name, config)
153 super(MyComponent, self).__init__(parent, name, config)
154 # Then any other code you need to finish initialization.
154 # Then any other code you need to finish initialization.
155
155
156 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
156 This ensures that the :attr:`parent`, :attr:`name` and :attr:`config`
157 attributes are handled properly.
157 attributes are handled properly.
158 """
158 """
159 super(Component, self).__init__()
159 super(Component, self).__init__()
160 self._children = []
160 self._children = []
161 if name is None:
161 if name is None:
162 self.name = ComponentNameGenerator()
162 self.name = ComponentNameGenerator()
163 else:
163 else:
164 self.name = name
164 self.name = name
165 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
166 self.parent = parent
166 self.parent = parent
167 if config is not None:
167 if config is not None:
168 self.config = config
168 self.config = deepcopy(config)
169 else:
169 else:
170 if self.parent is not None:
170 if self.parent is not None:
171 self.config = self.parent.config
171 self.config = deepcopy(self.parent.config)
172
172
173 #-------------------------------------------------------------------------
173 #-------------------------------------------------------------------------
174 # Static traitlet notifiations
174 # Static traitlet notifiations
175 #-------------------------------------------------------------------------
175 #-------------------------------------------------------------------------
176
176
177 def _parent_changed(self, name, old, new):
177 def _parent_changed(self, name, old, new):
178 if old is not None:
178 if old is not None:
179 old._remove_child(self)
179 old._remove_child(self)
180 if new is not None:
180 if new is not None:
181 new._add_child(self)
181 new._add_child(self)
182
182
183 if new is None:
183 if new is None:
184 self.root = self
184 self.root = self
185 else:
185 else:
186 self.root = new.root
186 self.root = new.root
187
187
188 def _root_changed(self, name, old, new):
188 def _root_changed(self, name, old, new):
189 if self.parent is None:
189 if self.parent is None:
190 if not (new is self):
190 if not (new is self):
191 raise ComponentError("Root not self, but parent is None.")
191 raise ComponentError("Root not self, but parent is None.")
192 else:
192 else:
193 if not self.parent.root is new:
193 if not self.parent.root is new:
194 raise ComponentError("Error in setting the root attribute: "
194 raise ComponentError("Error in setting the root attribute: "
195 "root != parent.root")
195 "root != parent.root")
196
196
197 def _config_changed(self, name, old, new):
198 # Get all traitlets with a config_key metadata entry
199 traitlets = self.traitlets(config_key=lambda v: True)
200 for k, v in traitlets.items():
201 try:
202 config_value = new[v.get_metadata('config_key')]
203 except KeyError:
204 pass
205 else:
206 setattr(self, k, config_value)
207
197 @property
208 @property
198 def children(self):
209 def children(self):
199 """A list of all my child components."""
210 """A list of all my child components."""
200 return self._children
211 return self._children
201
212
202 def _remove_child(self, child):
213 def _remove_child(self, child):
203 """A private method for removing children componenets."""
214 """A private method for removing children componenets."""
204 if child in self._children:
215 if child in self._children:
205 index = self._children.index(child)
216 index = self._children.index(child)
206 del self._children[index]
217 del self._children[index]
207
218
208 def _add_child(self, child):
219 def _add_child(self, child):
209 """A private method for adding children componenets."""
220 """A private method for adding children componenets."""
210 if child not in self._children:
221 if child not in self._children:
211 self._children.append(child)
222 self._children.append(child)
212
223
213 def __repr__(self):
224 def __repr__(self):
214 return "<Component('%s')>" % self.name
225 return "<Component('%s')>" % self.name
@@ -1,170 +1,194 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, ComponentError
25 from IPython.core.component import Component, ComponentError
26 from IPython.utils.traitlets import (
26 from IPython.utils.traitlets import (
27 TraitletError
27 TraitletError, Int, Float, Str
28 )
28 )
29 from IPython.utils.ipstruct import Struct
29 from IPython.utils.ipstruct import Struct
30
30
31
31
32 #-----------------------------------------------------------------------------
32 #-----------------------------------------------------------------------------
33 # Test cases
33 # Test cases
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35
35
36
36
37 class TestComponentMeta(TestCase):
37 class TestComponentMeta(TestCase):
38
38
39 def test_get_instances(self):
39 def test_get_instances(self):
40 class BaseComponent(Component):
40 class BaseComponent(Component):
41 pass
41 pass
42 c1 = BaseComponent(None)
42 c1 = BaseComponent(None)
43 c2 = BaseComponent(c1)
43 c2 = BaseComponent(c1)
44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
44 self.assertEquals(BaseComponent.get_instances(),[c1,c2])
45
45
46 def test_get_instances_subclass(self):
46 def test_get_instances_subclass(self):
47 class MyComponent(Component):
47 class MyComponent(Component):
48 pass
48 pass
49 class MyOtherComponent(MyComponent):
49 class MyOtherComponent(MyComponent):
50 pass
50 pass
51 c1 = MyComponent(None)
51 c1 = MyComponent(None)
52 c2 = MyOtherComponent(c1)
52 c2 = MyOtherComponent(c1)
53 c3 = MyOtherComponent(c2)
53 c3 = MyOtherComponent(c2)
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
54 self.assertEquals(MyComponent.get_instances(), [c1, c2, c3])
55 self.assertEquals(MyComponent.get_instances(klass=MyOtherComponent), [c2, c3])
55 self.assertEquals(MyComponent.get_instances(klass=MyOtherComponent), [c2, c3])
56
56
57 def test_get_instances_root(self):
57 def test_get_instances_root(self):
58 class MyComponent(Component):
58 class MyComponent(Component):
59 pass
59 pass
60 class MyOtherComponent(MyComponent):
60 class MyOtherComponent(MyComponent):
61 pass
61 pass
62 c1 = MyComponent(None)
62 c1 = MyComponent(None)
63 c2 = MyOtherComponent(c1)
63 c2 = MyOtherComponent(c1)
64 c3 = MyOtherComponent(c2)
64 c3 = MyOtherComponent(c2)
65 c4 = MyComponent(None)
65 c4 = MyComponent(None)
66 c5 = MyComponent(c4)
66 c5 = MyComponent(c4)
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
67 self.assertEquals(MyComponent.get_instances(root=c1), [c1, c2, c3])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
68 self.assertEquals(MyComponent.get_instances(root=c4), [c4, c5])
69
69
70
70
71 class TestComponent(TestCase):
71 class TestComponent(TestCase):
72
72
73 def test_parent_child(self):
73 def test_parent_child(self):
74 c1 = Component(None)
74 c1 = Component(None)
75 c2 = Component(c1)
75 c2 = Component(c1)
76 c3 = Component(c1)
76 c3 = Component(c1)
77 c4 = Component(c3)
77 c4 = Component(c3)
78 self.assertEquals(c1.parent, None)
78 self.assertEquals(c1.parent, None)
79 self.assertEquals(c2.parent, c1)
79 self.assertEquals(c2.parent, c1)
80 self.assertEquals(c3.parent, c1)
80 self.assertEquals(c3.parent, c1)
81 self.assertEquals(c4.parent, c3)
81 self.assertEquals(c4.parent, c3)
82 self.assertEquals(c1.children, [c2, c3])
82 self.assertEquals(c1.children, [c2, c3])
83 self.assertEquals(c2.children, [])
83 self.assertEquals(c2.children, [])
84 self.assertEquals(c3.children, [c4])
84 self.assertEquals(c3.children, [c4])
85 self.assertEquals(c4.children, [])
85 self.assertEquals(c4.children, [])
86
86
87 def test_root(self):
87 def test_root(self):
88 c1 = Component(None)
88 c1 = Component(None)
89 c2 = Component(c1)
89 c2 = Component(c1)
90 c3 = Component(c1)
90 c3 = Component(c1)
91 c4 = Component(c3)
91 c4 = Component(c3)
92 self.assertEquals(c1.root, c1.root)
92 self.assertEquals(c1.root, c1.root)
93 self.assertEquals(c2.root, c1)
93 self.assertEquals(c2.root, c1)
94 self.assertEquals(c3.root, c1)
94 self.assertEquals(c3.root, c1)
95 self.assertEquals(c4.root, c1)
95 self.assertEquals(c4.root, c1)
96
96
97 def test_change_parent(self):
97 def test_change_parent(self):
98 c1 = Component(None)
98 c1 = Component(None)
99 c2 = Component(None)
99 c2 = Component(None)
100 c3 = Component(c1)
100 c3 = Component(c1)
101 self.assertEquals(c3.root, c1)
101 self.assertEquals(c3.root, c1)
102 self.assertEquals(c3.parent, c1)
102 self.assertEquals(c3.parent, c1)
103 self.assertEquals(c1.children,[c3])
103 self.assertEquals(c1.children,[c3])
104 c3.parent = c2
104 c3.parent = c2
105 self.assertEquals(c3.root, c2)
105 self.assertEquals(c3.root, c2)
106 self.assertEquals(c3.parent, c2)
106 self.assertEquals(c3.parent, c2)
107 self.assertEquals(c2.children,[c3])
107 self.assertEquals(c2.children,[c3])
108 self.assertEquals(c1.children,[])
108 self.assertEquals(c1.children,[])
109
109
110 def test_subclass_parent(self):
110 def test_subclass_parent(self):
111 c1 = Component(None)
111 c1 = Component(None)
112 self.assertRaises(TraitletError, setattr, c1, 'parent', 10)
112 self.assertRaises(TraitletError, setattr, c1, 'parent', 10)
113
113
114 class MyComponent(Component):
114 class MyComponent(Component):
115 pass
115 pass
116 c1 = Component(None)
116 c1 = Component(None)
117 c2 = MyComponent(c1)
117 c2 = MyComponent(c1)
118 self.assertEquals(MyComponent.parent.this_class, Component)
118 self.assertEquals(MyComponent.parent.this_class, Component)
119 self.assertEquals(c2.parent, c1)
119 self.assertEquals(c2.parent, c1)
120
120
121 def test_bad_root(self):
121 def test_bad_root(self):
122 c1 = Component(None)
122 c1 = Component(None)
123 c2 = Component(None)
123 c2 = Component(None)
124 c3 = Component(None)
124 c3 = Component(None)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
125 self.assertRaises(ComponentError, setattr, c1, 'root', c2)
126 c1.parent = c2
126 c1.parent = c2
127 self.assertEquals(c1.root, c2)
127 self.assertEquals(c1.root, c2)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
128 self.assertRaises(ComponentError, setattr, c1, 'root', c3)
129
129
130
130
131 class TestComponentConfig(TestCase):
131 class TestComponentConfig(TestCase):
132
132
133 def test_default(self):
133 def test_default(self):
134 c1 = Component(None)
134 c1 = Component(None)
135 c2 = Component(c1)
135 c2 = Component(c1)
136 c3 = Component(c2)
136 c3 = Component(c2)
137 self.assertEquals(c1.config, c2.config)
137 self.assertEquals(c1.config, c2.config)
138 self.assertEquals(c2.config, c3.config)
138 self.assertEquals(c2.config, c3.config)
139
139
140 def test_custom(self):
140 def test_custom(self):
141 config = Struct()
141 config = Struct()
142 config.FOO = 'foo'
142 config.FOO = 'foo'
143 config.BAR = 'bar'
143 config.BAR = 'bar'
144 c1 = Component(None, config=config)
144 c1 = Component(None, config=config)
145 c2 = Component(c1)
145 c2 = Component(c1)
146 c3 = Component(c2)
146 c3 = Component(c2)
147 self.assertEquals(c1.config, config)
147 self.assertEquals(c1.config, config)
148 self.assertEquals(c2.config, config)
148 self.assertEquals(c2.config, config)
149 self.assertEquals(c3.config, config)
149 self.assertEquals(c3.config, config)
150 # Test that we always make copies
151 self.assert_(c1.config is not config)
152 self.assert_(c2.config is not config)
153 self.assert_(c3.config is not config)
154 self.assert_(c1.config is not c2.config)
155 self.assert_(c2.config is not c3.config)
156
157 def test_inheritance(self):
158 class MyComponent(Component):
159 a = Int(1, config_key='A')
160 b = Float(1.0, config_key='B')
161 c = Str('no config')
162 config = Struct()
163 config.A = 2
164 config.B = 2.0
165 c1 = MyComponent(None, config=config)
166 c2 = MyComponent(c1)
167 self.assertEquals(c1.a, config.A)
168 self.assertEquals(c1.b, config.B)
169 self.assertEquals(c2.a, config.A)
170 self.assertEquals(c2.b, config.B)
171 c4 = MyComponent(c2, config=Struct())
172 self.assertEquals(c4.a, 1)
173 self.assertEquals(c4.b, 1.0)
150
174
151 class TestComponentName(TestCase):
175 class TestComponentName(TestCase):
152
176
153 def test_default(self):
177 def test_default(self):
154 class MyComponent(Component):
178 class MyComponent(Component):
155 pass
179 pass
156 c1 = Component(None)
180 c1 = Component(None)
157 c2 = MyComponent(None)
181 c2 = MyComponent(None)
158 c3 = Component(c2)
182 c3 = Component(c2)
159 self.assertNotEquals(c1.name, c2.name)
183 self.assertNotEquals(c1.name, c2.name)
160 self.assertNotEquals(c1.name, c3.name)
184 self.assertNotEquals(c1.name, c3.name)
161
185
162 def test_manual(self):
186 def test_manual(self):
163 class MyComponent(Component):
187 class MyComponent(Component):
164 pass
188 pass
165 c1 = Component(None, name='foo')
189 c1 = Component(None, name='foo')
166 c2 = MyComponent(None, name='bar')
190 c2 = MyComponent(None, name='bar')
167 c3 = Component(c2, name='bah')
191 c3 = Component(c2, name='bah')
168 self.assertEquals(c1.name, 'foo')
192 self.assertEquals(c1.name, 'foo')
169 self.assertEquals(c2.name, 'bar')
193 self.assertEquals(c2.name, 'bar')
170 self.assertEquals(c3.name, 'bah')
194 self.assertEquals(c3.name, 'bah')
@@ -1,400 +1,400 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """A dict subclass that supports attribute style access.
3 """A dict subclass that supports attribute style access.
4
4
5 Authors:
5 Authors:
6
6
7 * Fernando Perez (original)
7 * Fernando Perez (original)
8 * Brian Granger (refactoring to a dict subclass)
8 * Brian Granger (refactoring to a dict subclass)
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2009 The IPython Development Team
12 # Copyright (C) 2008-2009 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import pprint
22 import pprint
23
23
24 from IPython.utils.genutils import list2dict2
24 from IPython.utils.genutils import list2dict2
25
25
26 __all__ = ['Struct']
26 __all__ = ['Struct']
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Code
29 # Code
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32
32
33 class Struct(dict):
33 class Struct(dict):
34 """A dict subclass with attribute style access.
34 """A dict subclass with attribute style access.
35
35
36 This dict subclass has a a few extra features:
36 This dict subclass has a a few extra features:
37
37
38 * Attribute style access.
38 * Attribute style access.
39 * Protection of class members (like keys, items) when using attribute
39 * Protection of class members (like keys, items) when using attribute
40 style access.
40 style access.
41 * The ability to restrict assignment to only existing keys.
41 * The ability to restrict assignment to only existing keys.
42 * Intelligent merging.
42 * Intelligent merging.
43 * Overloaded operators.
43 * Overloaded operators.
44 """
44 """
45
45 _allownew = True
46 def __init__(self, *args, **kw):
46 def __init__(self, *args, **kw):
47 """Initialize with a dictionary, another Struct, or data.
47 """Initialize with a dictionary, another Struct, or data.
48
48
49 Parameters
49 Parameters
50 ----------
50 ----------
51 args : dict, Struct
51 args : dict, Struct
52 Initialize with one dict or Struct
52 Initialize with one dict or Struct
53 kw : dict
53 kw : dict
54 Initialize with key, value pairs.
54 Initialize with key, value pairs.
55
55
56 Examples
56 Examples
57 --------
57 --------
58
58
59 >>> s = Struct(a=10,b=30)
59 >>> s = Struct(a=10,b=30)
60 >>> s.a
60 >>> s.a
61 10
61 10
62 >>> s.b
62 >>> s.b
63 30
63 30
64 >>> s2 = Struct(s,c=30)
64 >>> s2 = Struct(s,c=30)
65 >>> s2.keys()
65 >>> s2.keys()
66 ['a', 'c', 'b']
66 ['a', 'c', 'b']
67 """
67 """
68 object.__setattr__(self, '_allownew', True)
68 object.__setattr__(self, '_allownew', True)
69 dict.__init__(self, *args, **kw)
69 dict.__init__(self, *args, **kw)
70
70
71 def __setitem__(self, key, value):
71 def __setitem__(self, key, value):
72 """Set an item with check for allownew.
72 """Set an item with check for allownew.
73
73
74 Examples
74 Examples
75 --------
75 --------
76
76
77 >>> s = Struct()
77 >>> s = Struct()
78 >>> s['a'] = 10
78 >>> s['a'] = 10
79 >>> s.allow_new_attr(False)
79 >>> s.allow_new_attr(False)
80 >>> s['a'] = 10
80 >>> s['a'] = 10
81 >>> s['a']
81 >>> s['a']
82 10
82 10
83 >>> try:
83 >>> try:
84 ... s['b'] = 20
84 ... s['b'] = 20
85 ... except KeyError:
85 ... except KeyError:
86 ... print 'this is not allowed'
86 ... print 'this is not allowed'
87 ...
87 ...
88 this is not allowed
88 this is not allowed
89 """
89 """
90 if not self._allownew and not self.has_key(key):
90 if not self._allownew and not self.has_key(key):
91 raise KeyError(
91 raise KeyError(
92 "can't create new attribute %s when allow_new_attr(False)" % key)
92 "can't create new attribute %s when allow_new_attr(False)" % key)
93 dict.__setitem__(self, key, value)
93 dict.__setitem__(self, key, value)
94
94
95 def __setattr__(self, key, value):
95 def __setattr__(self, key, value):
96 """Set an attr with protection of class members.
96 """Set an attr with protection of class members.
97
97
98 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
98 This calls :meth:`self.__setitem__` but convert :exc:`KeyError` to
99 :exc:`AttributeError`.
99 :exc:`AttributeError`.
100
100
101 Examples
101 Examples
102 --------
102 --------
103
103
104 >>> s = Struct()
104 >>> s = Struct()
105 >>> s.a = 10
105 >>> s.a = 10
106 >>> s.a
106 >>> s.a
107 10
107 10
108 >>> try:
108 >>> try:
109 ... s.get = 10
109 ... s.get = 10
110 ... except AttributeError:
110 ... except AttributeError:
111 ... print "you can't set a class member"
111 ... print "you can't set a class member"
112 ...
112 ...
113 you can't set a class member
113 you can't set a class member
114 """
114 """
115 # If key is an str it might be a class member or instance var
115 # If key is an str it might be a class member or instance var
116 if isinstance(key, str):
116 if isinstance(key, str):
117 # I can't simply call hasattr here because it calls getattr, which
117 # I can't simply call hasattr here because it calls getattr, which
118 # calls self.__getattr__, which returns True for keys in
118 # calls self.__getattr__, which returns True for keys in
119 # self._data. But I only want keys in the class and in
119 # self._data. But I only want keys in the class and in
120 # self.__dict__
120 # self.__dict__
121 if key in self.__dict__ or hasattr(Struct, key):
121 if key in self.__dict__ or hasattr(Struct, key):
122 raise AttributeError(
122 raise AttributeError(
123 'attr %s is a protected member of class Struct.' % key
123 'attr %s is a protected member of class Struct.' % key
124 )
124 )
125 try:
125 try:
126 self.__setitem__(key, value)
126 self.__setitem__(key, value)
127 except KeyError, e:
127 except KeyError, e:
128 raise AttributeError(e)
128 raise AttributeError(e)
129
129
130 def __getattr__(self, key):
130 def __getattr__(self, key):
131 """Get an attr by calling :meth:`dict.__getitem__`.
131 """Get an attr by calling :meth:`dict.__getitem__`.
132
132
133 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
133 Like :meth:`__setattr__`, this method converts :exc:`KeyError` to
134 :exc:`AttributeError`.
134 :exc:`AttributeError`.
135
135
136 Examples
136 Examples
137 --------
137 --------
138
138
139 >>> s = Struct(a=10)
139 >>> s = Struct(a=10)
140 >>> s.a
140 >>> s.a
141 10
141 10
142 >>> type(s.get)
142 >>> type(s.get)
143 <type 'builtin_function_or_method'>
143 <type 'builtin_function_or_method'>
144 >>> try:
144 >>> try:
145 ... s.b
145 ... s.b
146 ... except AttributeError:
146 ... except AttributeError:
147 ... print "I don't have that key"
147 ... print "I don't have that key"
148 ...
148 ...
149 I don't have that key
149 I don't have that key
150 """
150 """
151 try:
151 try:
152 result = self[key]
152 result = self[key]
153 except KeyError:
153 except KeyError:
154 raise AttributeError(key)
154 raise AttributeError(key)
155 else:
155 else:
156 return result
156 return result
157
157
158 def __iadd__(self, other):
158 def __iadd__(self, other):
159 """s += s2 is a shorthand for s.merge(s2).
159 """s += s2 is a shorthand for s.merge(s2).
160
160
161 Examples
161 Examples
162 --------
162 --------
163
163
164 >>> s = Struct(a=10,b=30)
164 >>> s = Struct(a=10,b=30)
165 >>> s2 = Struct(a=20,c=40)
165 >>> s2 = Struct(a=20,c=40)
166 >>> s += s2
166 >>> s += s2
167 >>> s
167 >>> s
168 {'a': 10, 'c': 40, 'b': 30}
168 {'a': 10, 'c': 40, 'b': 30}
169 """
169 """
170 self.merge(other)
170 self.merge(other)
171 return self
171 return self
172
172
173 def __add__(self,other):
173 def __add__(self,other):
174 """s + s2 -> New Struct made from s.merge(s2).
174 """s + s2 -> New Struct made from s.merge(s2).
175
175
176 Examples
176 Examples
177 --------
177 --------
178
178
179 >>> s1 = Struct(a=10,b=30)
179 >>> s1 = Struct(a=10,b=30)
180 >>> s2 = Struct(a=20,c=40)
180 >>> s2 = Struct(a=20,c=40)
181 >>> s = s1 + s2
181 >>> s = s1 + s2
182 >>> s
182 >>> s
183 {'a': 10, 'c': 40, 'b': 30}
183 {'a': 10, 'c': 40, 'b': 30}
184 """
184 """
185 sout = self.copy()
185 sout = self.copy()
186 sout.merge(other)
186 sout.merge(other)
187 return sout
187 return sout
188
188
189 def __sub__(self,other):
189 def __sub__(self,other):
190 """s1 - s2 -> remove keys in s2 from s1.
190 """s1 - s2 -> remove keys in s2 from s1.
191
191
192 Examples
192 Examples
193 --------
193 --------
194
194
195 >>> s1 = Struct(a=10,b=30)
195 >>> s1 = Struct(a=10,b=30)
196 >>> s2 = Struct(a=40)
196 >>> s2 = Struct(a=40)
197 >>> s = s1 - s2
197 >>> s = s1 - s2
198 >>> s
198 >>> s
199 {'b': 30}
199 {'b': 30}
200 """
200 """
201 sout = self.copy()
201 sout = self.copy()
202 sout -= other
202 sout -= other
203 return sout
203 return sout
204
204
205 def __isub__(self,other):
205 def __isub__(self,other):
206 """Inplace remove keys from self that are in other.
206 """Inplace remove keys from self that are in other.
207
207
208 Examples
208 Examples
209 --------
209 --------
210
210
211 >>> s1 = Struct(a=10,b=30)
211 >>> s1 = Struct(a=10,b=30)
212 >>> s2 = Struct(a=40)
212 >>> s2 = Struct(a=40)
213 >>> s1 -= s2
213 >>> s1 -= s2
214 >>> s1
214 >>> s1
215 {'b': 30}
215 {'b': 30}
216 """
216 """
217 for k in other.keys():
217 for k in other.keys():
218 if self.has_key(k):
218 if self.has_key(k):
219 del self[k]
219 del self[k]
220 return self
220 return self
221
221
222 def __dict_invert(self, data):
222 def __dict_invert(self, data):
223 """Helper function for merge.
223 """Helper function for merge.
224
224
225 Takes a dictionary whose values are lists and returns a dict with
225 Takes a dictionary whose values are lists and returns a dict with
226 the elements of each list as keys and the original keys as values.
226 the elements of each list as keys and the original keys as values.
227 """
227 """
228 outdict = {}
228 outdict = {}
229 for k,lst in data.items():
229 for k,lst in data.items():
230 if isinstance(lst, str):
230 if isinstance(lst, str):
231 lst = lst.split()
231 lst = lst.split()
232 for entry in lst:
232 for entry in lst:
233 outdict[entry] = k
233 outdict[entry] = k
234 return outdict
234 return outdict
235
235
236 def dict(self):
236 def dict(self):
237 return self
237 return self
238
238
239 def copy(self):
239 def copy(self):
240 """Return a copy as a Struct.
240 """Return a copy as a Struct.
241
241
242 Examples
242 Examples
243 --------
243 --------
244
244
245 >>> s = Struct(a=10,b=30)
245 >>> s = Struct(a=10,b=30)
246 >>> s2 = s.copy()
246 >>> s2 = s.copy()
247 >>> s2
247 >>> s2
248 {'a': 10, 'b': 30}
248 {'a': 10, 'b': 30}
249 >>> type(s2).__name__
249 >>> type(s2).__name__
250 'Struct'
250 'Struct'
251 """
251 """
252 return Struct(dict.copy(self))
252 return Struct(dict.copy(self))
253
253
254 def hasattr(self, key):
254 def hasattr(self, key):
255 """hasattr function available as a method.
255 """hasattr function available as a method.
256
256
257 Implemented like has_key.
257 Implemented like has_key.
258
258
259 Examples
259 Examples
260 --------
260 --------
261
261
262 >>> s = Struct(a=10)
262 >>> s = Struct(a=10)
263 >>> s.hasattr('a')
263 >>> s.hasattr('a')
264 True
264 True
265 >>> s.hasattr('b')
265 >>> s.hasattr('b')
266 False
266 False
267 >>> s.hasattr('get')
267 >>> s.hasattr('get')
268 False
268 False
269 """
269 """
270 return self.has_key(key)
270 return self.has_key(key)
271
271
272 def allow_new_attr(self, allow = True):
272 def allow_new_attr(self, allow = True):
273 """Set whether new attributes can be created in this Struct.
273 """Set whether new attributes can be created in this Struct.
274
274
275 This can be used to catch typos by verifying that the attribute user
275 This can be used to catch typos by verifying that the attribute user
276 tries to change already exists in this Struct.
276 tries to change already exists in this Struct.
277 """
277 """
278 object.__setattr__(self, '_allownew', allow)
278 object.__setattr__(self, '_allownew', allow)
279
279
280 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
280 def merge(self, __loc_data__=None, __conflict_solve=None, **kw):
281 """Merge two Structs with customizable conflict resolution.
281 """Merge two Structs with customizable conflict resolution.
282
282
283 This is similar to :meth:`update`, but much more flexible. First, a
283 This is similar to :meth:`update`, but much more flexible. First, a
284 dict is made from data+key=value pairs. When merging this dict with
284 dict is made from data+key=value pairs. When merging this dict with
285 the Struct S, the optional dictionary 'conflict' is used to decide
285 the Struct S, the optional dictionary 'conflict' is used to decide
286 what to do.
286 what to do.
287
287
288 If conflict is not given, the default behavior is to preserve any keys
288 If conflict is not given, the default behavior is to preserve any keys
289 with their current value (the opposite of the :meth:`update` method's
289 with their current value (the opposite of the :meth:`update` method's
290 behavior).
290 behavior).
291
291
292 Parameters
292 Parameters
293 ----------
293 ----------
294 __loc_data : dict, Struct
294 __loc_data : dict, Struct
295 The data to merge into self
295 The data to merge into self
296 __conflict_solve : dict
296 __conflict_solve : dict
297 The conflict policy dict. The keys are binary functions used to
297 The conflict policy dict. The keys are binary functions used to
298 resolve the conflict and the values are lists of strings naming
298 resolve the conflict and the values are lists of strings naming
299 the keys the conflict resolution function applies to. Instead of
299 the keys the conflict resolution function applies to. Instead of
300 a list of strings a space separated string can be used, like
300 a list of strings a space separated string can be used, like
301 'a b c'.
301 'a b c'.
302 kw : dict
302 kw : dict
303 Additional key, value pairs to merge in
303 Additional key, value pairs to merge in
304
304
305 Notes
305 Notes
306 -----
306 -----
307
307
308 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
308 The `__conflict_solve` dict is a dictionary of binary functions which will be used to
309 solve key conflicts. Here is an example::
309 solve key conflicts. Here is an example::
310
310
311 __conflict_solve = dict(
311 __conflict_solve = dict(
312 func1=['a','b','c'],
312 func1=['a','b','c'],
313 func2=['d','e']
313 func2=['d','e']
314 )
314 )
315
315
316 In this case, the function :func:`func1` will be used to resolve
316 In this case, the function :func:`func1` will be used to resolve
317 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
317 keys 'a', 'b' and 'c' and the function :func:`func2` will be used for
318 keys 'd' and 'e'. This could also be written as::
318 keys 'd' and 'e'. This could also be written as::
319
319
320 __conflict_solve = dict(func1='a b c',func2='d e')
320 __conflict_solve = dict(func1='a b c',func2='d e')
321
321
322 These functions will be called for each key they apply to with the
322 These functions will be called for each key they apply to with the
323 form::
323 form::
324
324
325 func1(self['a'], other['a'])
325 func1(self['a'], other['a'])
326
326
327 The return value is used as the final merged value.
327 The return value is used as the final merged value.
328
328
329 As a convenience, merge() provides five (the most commonly needed)
329 As a convenience, merge() provides five (the most commonly needed)
330 pre-defined policies: preserve, update, add, add_flip and add_s. The
330 pre-defined policies: preserve, update, add, add_flip and add_s. The
331 easiest explanation is their implementation::
331 easiest explanation is their implementation::
332
332
333 preserve = lambda old,new: old
333 preserve = lambda old,new: old
334 update = lambda old,new: new
334 update = lambda old,new: new
335 add = lambda old,new: old + new
335 add = lambda old,new: old + new
336 add_flip = lambda old,new: new + old # note change of order!
336 add_flip = lambda old,new: new + old # note change of order!
337 add_s = lambda old,new: old + ' ' + new # only for str!
337 add_s = lambda old,new: old + ' ' + new # only for str!
338
338
339 You can use those four words (as strings) as keys instead
339 You can use those four words (as strings) as keys instead
340 of defining them as functions, and the merge method will substitute
340 of defining them as functions, and the merge method will substitute
341 the appropriate functions for you.
341 the appropriate functions for you.
342
342
343 For more complicated conflict resolution policies, you still need to
343 For more complicated conflict resolution policies, you still need to
344 construct your own functions.
344 construct your own functions.
345
345
346 Examples
346 Examples
347 --------
347 --------
348
348
349 This show the default policy:
349 This show the default policy:
350
350
351 >>> s = Struct(a=10,b=30)
351 >>> s = Struct(a=10,b=30)
352 >>> s2 = Struct(a=20,c=40)
352 >>> s2 = Struct(a=20,c=40)
353 >>> s.merge(s2)
353 >>> s.merge(s2)
354 >>> s
354 >>> s
355 {'a': 10, 'c': 40, 'b': 30}
355 {'a': 10, 'c': 40, 'b': 30}
356
356
357 Now, show how to specify a conflict dict:
357 Now, show how to specify a conflict dict:
358
358
359 >>> s = Struct(a=10,b=30)
359 >>> s = Struct(a=10,b=30)
360 >>> s2 = Struct(a=20,b=40)
360 >>> s2 = Struct(a=20,b=40)
361 >>> conflict = {'update':'a','add':'b'}
361 >>> conflict = {'update':'a','add':'b'}
362 >>> s.merge(s2,conflict)
362 >>> s.merge(s2,conflict)
363 >>> s
363 >>> s
364 {'a': 20, 'b': 70}
364 {'a': 20, 'b': 70}
365 """
365 """
366
366
367 data_dict = dict(__loc_data__,**kw)
367 data_dict = dict(__loc_data__,**kw)
368
368
369 # policies for conflict resolution: two argument functions which return
369 # policies for conflict resolution: two argument functions which return
370 # the value that will go in the new struct
370 # the value that will go in the new struct
371 preserve = lambda old,new: old
371 preserve = lambda old,new: old
372 update = lambda old,new: new
372 update = lambda old,new: new
373 add = lambda old,new: old + new
373 add = lambda old,new: old + new
374 add_flip = lambda old,new: new + old # note change of order!
374 add_flip = lambda old,new: new + old # note change of order!
375 add_s = lambda old,new: old + ' ' + new
375 add_s = lambda old,new: old + ' ' + new
376
376
377 # default policy is to keep current keys when there's a conflict
377 # default policy is to keep current keys when there's a conflict
378 conflict_solve = list2dict2(self.keys(), default = preserve)
378 conflict_solve = list2dict2(self.keys(), default = preserve)
379
379
380 # the conflict_solve dictionary is given by the user 'inverted': we
380 # the conflict_solve dictionary is given by the user 'inverted': we
381 # need a name-function mapping, it comes as a function -> names
381 # need a name-function mapping, it comes as a function -> names
382 # dict. Make a local copy (b/c we'll make changes), replace user
382 # dict. Make a local copy (b/c we'll make changes), replace user
383 # strings for the three builtin policies and invert it.
383 # strings for the three builtin policies and invert it.
384 if __conflict_solve:
384 if __conflict_solve:
385 inv_conflict_solve_user = __conflict_solve.copy()
385 inv_conflict_solve_user = __conflict_solve.copy()
386 for name, func in [('preserve',preserve), ('update',update),
386 for name, func in [('preserve',preserve), ('update',update),
387 ('add',add), ('add_flip',add_flip),
387 ('add',add), ('add_flip',add_flip),
388 ('add_s',add_s)]:
388 ('add_s',add_s)]:
389 if name in inv_conflict_solve_user.keys():
389 if name in inv_conflict_solve_user.keys():
390 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
390 inv_conflict_solve_user[func] = inv_conflict_solve_user[name]
391 del inv_conflict_solve_user[name]
391 del inv_conflict_solve_user[name]
392 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
392 conflict_solve.update(self.__dict_invert(inv_conflict_solve_user))
393 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
393 #print 'merge. conflict_solve: '; pprint(conflict_solve) # dbg
394 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
394 #print '*'*50,'in merger. conflict_solver:'; pprint(conflict_solve)
395 for key in data_dict:
395 for key in data_dict:
396 if key not in self:
396 if key not in self:
397 self[key] = data_dict[key]
397 self[key] = data_dict[key]
398 else:
398 else:
399 self[key] = conflict_solve[key](self[key],data_dict[key])
399 self[key] = conflict_solve[key](self[key],data_dict[key])
400
400
@@ -1,645 +1,667 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):
167 def test_this_class(self):
168 class A(HasTraitlets):
168 class A(HasTraitlets):
169 t = This()
169 t = This()
170 tt = This()
170 tt = This()
171 class B(A):
171 class B(A):
172 tt = This()
172 tt = This()
173 ttt = This()
173 ttt = This()
174 self.assertEquals(A.t.this_class, A)
174 self.assertEquals(A.t.this_class, A)
175 self.assertEquals(B.t.this_class, A)
175 self.assertEquals(B.t.this_class, A)
176 self.assertEquals(B.tt.this_class, B)
176 self.assertEquals(B.tt.this_class, B)
177 self.assertEquals(B.ttt.this_class, B)
177 self.assertEquals(B.ttt.this_class, B)
178
178
179 class TestHasTraitletsNotify(TestCase):
179 class TestHasTraitletsNotify(TestCase):
180
180
181 def setUp(self):
181 def setUp(self):
182 self._notify1 = []
182 self._notify1 = []
183 self._notify2 = []
183 self._notify2 = []
184
184
185 def notify1(self, name, old, new):
185 def notify1(self, name, old, new):
186 self._notify1.append((name, old, new))
186 self._notify1.append((name, old, new))
187
187
188 def notify2(self, name, old, new):
188 def notify2(self, name, old, new):
189 self._notify2.append((name, old, new))
189 self._notify2.append((name, old, new))
190
190
191 def test_notify_all(self):
191 def test_notify_all(self):
192
192
193 class A(HasTraitlets):
193 class A(HasTraitlets):
194 a = Int
194 a = Int
195 b = Float
195 b = Float
196
196
197 a = A()
197 a = A()
198 a.on_traitlet_change(self.notify1)
198 a.on_traitlet_change(self.notify1)
199 a.a = 0
199 a.a = 0
200 self.assertEquals(len(self._notify1),0)
200 self.assertEquals(len(self._notify1),0)
201 a.b = 0.0
201 a.b = 0.0
202 self.assertEquals(len(self._notify1),0)
202 self.assertEquals(len(self._notify1),0)
203 a.a = 10
203 a.a = 10
204 self.assert_(('a',0,10) in self._notify1)
204 self.assert_(('a',0,10) in self._notify1)
205 a.b = 10.0
205 a.b = 10.0
206 self.assert_(('b',0.0,10.0) in self._notify1)
206 self.assert_(('b',0.0,10.0) in self._notify1)
207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
207 self.assertRaises(TraitletError,setattr,a,'a','bad string')
208 self.assertRaises(TraitletError,setattr,a,'b','bad string')
208 self.assertRaises(TraitletError,setattr,a,'b','bad string')
209 self._notify1 = []
209 self._notify1 = []
210 a.on_traitlet_change(self.notify1,remove=True)
210 a.on_traitlet_change(self.notify1,remove=True)
211 a.a = 20
211 a.a = 20
212 a.b = 20.0
212 a.b = 20.0
213 self.assertEquals(len(self._notify1),0)
213 self.assertEquals(len(self._notify1),0)
214
214
215 def test_notify_one(self):
215 def test_notify_one(self):
216
216
217 class A(HasTraitlets):
217 class A(HasTraitlets):
218 a = Int
218 a = Int
219 b = Float
219 b = Float
220
220
221 a = A()
221 a = A()
222 a.on_traitlet_change(self.notify1, 'a')
222 a.on_traitlet_change(self.notify1, 'a')
223 a.a = 0
223 a.a = 0
224 self.assertEquals(len(self._notify1),0)
224 self.assertEquals(len(self._notify1),0)
225 a.a = 10
225 a.a = 10
226 self.assert_(('a',0,10) in self._notify1)
226 self.assert_(('a',0,10) in self._notify1)
227 self.assertRaises(TraitletError,setattr,a,'a','bad string')
227 self.assertRaises(TraitletError,setattr,a,'a','bad string')
228
228
229 def test_subclass(self):
229 def test_subclass(self):
230
230
231 class A(HasTraitlets):
231 class A(HasTraitlets):
232 a = Int
232 a = Int
233
233
234 class B(A):
234 class B(A):
235 b = Float
235 b = Float
236
236
237 b = B()
237 b = B()
238 self.assertEquals(b.a,0)
238 self.assertEquals(b.a,0)
239 self.assertEquals(b.b,0.0)
239 self.assertEquals(b.b,0.0)
240 b.a = 100
240 b.a = 100
241 b.b = 100.0
241 b.b = 100.0
242 self.assertEquals(b.a,100)
242 self.assertEquals(b.a,100)
243 self.assertEquals(b.b,100.0)
243 self.assertEquals(b.b,100.0)
244
244
245 def test_notify_subclass(self):
245 def test_notify_subclass(self):
246
246
247 class A(HasTraitlets):
247 class A(HasTraitlets):
248 a = Int
248 a = Int
249
249
250 class B(A):
250 class B(A):
251 b = Float
251 b = Float
252
252
253 b = B()
253 b = B()
254 b.on_traitlet_change(self.notify1, 'a')
254 b.on_traitlet_change(self.notify1, 'a')
255 b.on_traitlet_change(self.notify2, 'b')
255 b.on_traitlet_change(self.notify2, 'b')
256 b.a = 0
256 b.a = 0
257 b.b = 0.0
257 b.b = 0.0
258 self.assertEquals(len(self._notify1),0)
258 self.assertEquals(len(self._notify1),0)
259 self.assertEquals(len(self._notify2),0)
259 self.assertEquals(len(self._notify2),0)
260 b.a = 10
260 b.a = 10
261 b.b = 10.0
261 b.b = 10.0
262 self.assert_(('a',0,10) in self._notify1)
262 self.assert_(('a',0,10) in self._notify1)
263 self.assert_(('b',0.0,10.0) in self._notify2)
263 self.assert_(('b',0.0,10.0) in self._notify2)
264
264
265 def test_static_notify(self):
265 def test_static_notify(self):
266
266
267 class A(HasTraitlets):
267 class A(HasTraitlets):
268 a = Int
268 a = Int
269 _notify1 = []
269 _notify1 = []
270 def _a_changed(self, name, old, new):
270 def _a_changed(self, name, old, new):
271 self._notify1.append((name, old, new))
271 self._notify1.append((name, old, new))
272
272
273 a = A()
273 a = A()
274 a.a = 0
274 a.a = 0
275 # This is broken!!!
275 # This is broken!!!
276 self.assertEquals(len(a._notify1),0)
276 self.assertEquals(len(a._notify1),0)
277 a.a = 10
277 a.a = 10
278 self.assert_(('a',0,10) in a._notify1)
278 self.assert_(('a',0,10) in a._notify1)
279
279
280 class B(A):
280 class B(A):
281 b = Float
281 b = Float
282 _notify2 = []
282 _notify2 = []
283 def _b_changed(self, name, old, new):
283 def _b_changed(self, name, old, new):
284 self._notify2.append((name, old, new))
284 self._notify2.append((name, old, new))
285
285
286 b = B()
286 b = B()
287 b.a = 10
287 b.a = 10
288 b.b = 10.0
288 b.b = 10.0
289 self.assert_(('a',0,10) in b._notify1)
289 self.assert_(('a',0,10) in b._notify1)
290 self.assert_(('b',0.0,10.0) in b._notify2)
290 self.assert_(('b',0.0,10.0) in b._notify2)
291
291
292 def test_notify_args(self):
292 def test_notify_args(self):
293
293
294 def callback0():
294 def callback0():
295 self.cb = ()
295 self.cb = ()
296 def callback1(name):
296 def callback1(name):
297 self.cb = (name,)
297 self.cb = (name,)
298 def callback2(name, new):
298 def callback2(name, new):
299 self.cb = (name, new)
299 self.cb = (name, new)
300 def callback3(name, old, new):
300 def callback3(name, old, new):
301 self.cb = (name, old, new)
301 self.cb = (name, old, new)
302
302
303 class A(HasTraitlets):
303 class A(HasTraitlets):
304 a = Int
304 a = Int
305
305
306 a = A()
306 a = A()
307 a.on_traitlet_change(callback0, 'a')
307 a.on_traitlet_change(callback0, 'a')
308 a.a = 10
308 a.a = 10
309 self.assertEquals(self.cb,())
309 self.assertEquals(self.cb,())
310 a.on_traitlet_change(callback0, 'a', remove=True)
310 a.on_traitlet_change(callback0, 'a', remove=True)
311
311
312 a.on_traitlet_change(callback1, 'a')
312 a.on_traitlet_change(callback1, 'a')
313 a.a = 100
313 a.a = 100
314 self.assertEquals(self.cb,('a',))
314 self.assertEquals(self.cb,('a',))
315 a.on_traitlet_change(callback1, 'a', remove=True)
315 a.on_traitlet_change(callback1, 'a', remove=True)
316
316
317 a.on_traitlet_change(callback2, 'a')
317 a.on_traitlet_change(callback2, 'a')
318 a.a = 1000
318 a.a = 1000
319 self.assertEquals(self.cb,('a',1000))
319 self.assertEquals(self.cb,('a',1000))
320 a.on_traitlet_change(callback2, 'a', remove=True)
320 a.on_traitlet_change(callback2, 'a', remove=True)
321
321
322 a.on_traitlet_change(callback3, 'a')
322 a.on_traitlet_change(callback3, 'a')
323 a.a = 10000
323 a.a = 10000
324 self.assertEquals(self.cb,('a',1000,10000))
324 self.assertEquals(self.cb,('a',1000,10000))
325 a.on_traitlet_change(callback3, 'a', remove=True)
325 a.on_traitlet_change(callback3, 'a', remove=True)
326
326
327 self.assertEquals(len(a._traitlet_notifiers['a']),0)
327 self.assertEquals(len(a._traitlet_notifiers['a']),0)
328
328
329
329
330 class TestTraitletKeys(TestCase):
330 class TestHasTraitlets(TestCase):
331
331
332 def test_keys(self):
332 def test_traitlet_names(self):
333 class A(HasTraitlets):
333 class A(HasTraitlets):
334 a = Int
334 i = Int
335 b = Float
335 f = Float
336 a = A()
337 self.assertEquals(a.traitlet_names(),['i','f'])
338
339 def test_traitlet_metadata(self):
340 class A(HasTraitlets):
341 i = Int(config_key='MY_VALUE')
336 a = A()
342 a = A()
337 self.assertEquals(a.traitlet_names(),['a','b'])
343 self.assertEquals(a.traitlet_metadata('i','config_key'), 'MY_VALUE')
338
344
345 def test_traitlets(self):
346 class A(HasTraitlets):
347 i = Int
348 f = Float
349 a = A()
350 self.assertEquals(a.traitlets(), dict(i=A.i, f=A.f))
351
352 def test_traitlets_metadata(self):
353 class A(HasTraitlets):
354 i = Int(config_key='VALUE1', other_thing='VALUE2')
355 f = Float(config_key='VALUE3', other_thing='VALUE2')
356 a = A()
357 # traitlets = a.traitlets(config_key=lambda v: True)
358 # self.assertEquals(traitlets, dict(i=A.i, f=A.f))
359 traitlets = a.traitlets(config_key='VALUE1', other_thing='VALUE2')
360 self.assertEquals(traitlets, dict(i=A.i))
339
361
340 #-----------------------------------------------------------------------------
362 #-----------------------------------------------------------------------------
341 # Tests for specific traitlet types
363 # Tests for specific traitlet types
342 #-----------------------------------------------------------------------------
364 #-----------------------------------------------------------------------------
343
365
344
366
345 class TestType(TestCase):
367 class TestType(TestCase):
346
368
347 def test_default(self):
369 def test_default(self):
348
370
349 class B(object): pass
371 class B(object): pass
350 class A(HasTraitlets):
372 class A(HasTraitlets):
351 klass = Type
373 klass = Type
352
374
353 a = A()
375 a = A()
354 self.assertEquals(a.klass, None)
376 self.assertEquals(a.klass, None)
355 a.klass = B
377 a.klass = B
356 self.assertEquals(a.klass, B)
378 self.assertEquals(a.klass, B)
357 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
379 self.assertRaises(TraitletError, setattr, a, 'klass', 10)
358
380
359 def test_value(self):
381 def test_value(self):
360
382
361 class B(object): pass
383 class B(object): pass
362 class C(object): pass
384 class C(object): pass
363 class A(HasTraitlets):
385 class A(HasTraitlets):
364 klass = Type(B)
386 klass = Type(B)
365
387
366 a = A()
388 a = A()
367 self.assertEquals(a.klass, B)
389 self.assertEquals(a.klass, B)
368 self.assertRaises(TraitletError, setattr, a, 'klass', C)
390 self.assertRaises(TraitletError, setattr, a, 'klass', C)
369 self.assertRaises(TraitletError, setattr, a, 'klass', object)
391 self.assertRaises(TraitletError, setattr, a, 'klass', object)
370 a.klass = B
392 a.klass = B
371
393
372 def test_allow_none(self):
394 def test_allow_none(self):
373
395
374 class B(object): pass
396 class B(object): pass
375 class C(B): pass
397 class C(B): pass
376 class A(HasTraitlets):
398 class A(HasTraitlets):
377 klass = Type(B, allow_none=False)
399 klass = Type(B, allow_none=False)
378
400
379 a = A()
401 a = A()
380 self.assertEquals(a.klass, B)
402 self.assertEquals(a.klass, B)
381 self.assertRaises(TraitletError, setattr, a, 'klass', None)
403 self.assertRaises(TraitletError, setattr, a, 'klass', None)
382 a.klass = C
404 a.klass = C
383 self.assertEquals(a.klass, C)
405 self.assertEquals(a.klass, C)
384
406
385 def test_validate_klass(self):
407 def test_validate_klass(self):
386
408
387 def inner():
409 def inner():
388 class A(HasTraitlets):
410 class A(HasTraitlets):
389 klass = Type('no strings allowed')
411 klass = Type('no strings allowed')
390
412
391 self.assertRaises(TraitletError, inner)
413 self.assertRaises(TraitletError, inner)
392
414
393 def test_validate_default(self):
415 def test_validate_default(self):
394
416
395 class B(object): pass
417 class B(object): pass
396 class A(HasTraitlets):
418 class A(HasTraitlets):
397 klass = Type('bad default', B)
419 klass = Type('bad default', B)
398
420
399 self.assertRaises(TraitletError, A)
421 self.assertRaises(TraitletError, A)
400
422
401 class C(HasTraitlets):
423 class C(HasTraitlets):
402 klass = Type(None, B, allow_none=False)
424 klass = Type(None, B, allow_none=False)
403
425
404 self.assertRaises(TraitletError, C)
426 self.assertRaises(TraitletError, C)
405
427
406 class TestInstance(TestCase):
428 class TestInstance(TestCase):
407
429
408 def test_basic(self):
430 def test_basic(self):
409 class Foo(object): pass
431 class Foo(object): pass
410 class Bar(Foo): pass
432 class Bar(Foo): pass
411 class Bah(object): pass
433 class Bah(object): pass
412
434
413 class A(HasTraitlets):
435 class A(HasTraitlets):
414 inst = Instance(Foo)
436 inst = Instance(Foo)
415
437
416 a = A()
438 a = A()
417 self.assert_(a.inst is None)
439 self.assert_(a.inst is None)
418 a.inst = Foo()
440 a.inst = Foo()
419 self.assert_(isinstance(a.inst, Foo))
441 self.assert_(isinstance(a.inst, Foo))
420 a.inst = Bar()
442 a.inst = Bar()
421 self.assert_(isinstance(a.inst, Foo))
443 self.assert_(isinstance(a.inst, Foo))
422 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
444 self.assertRaises(TraitletError, setattr, a, 'inst', Foo)
423 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
445 self.assertRaises(TraitletError, setattr, a, 'inst', Bar)
424 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
446 self.assertRaises(TraitletError, setattr, a, 'inst', Bah())
425
447
426 def test_unique_default_value(self):
448 def test_unique_default_value(self):
427 class Foo(object): pass
449 class Foo(object): pass
428 class A(HasTraitlets):
450 class A(HasTraitlets):
429 inst = Instance(Foo,(),{})
451 inst = Instance(Foo,(),{})
430
452
431 a = A()
453 a = A()
432 b = A()
454 b = A()
433 self.assert_(a.inst is not b.inst)
455 self.assert_(a.inst is not b.inst)
434
456
435 def test_args_kw(self):
457 def test_args_kw(self):
436 class Foo(object):
458 class Foo(object):
437 def __init__(self, c): self.c = c
459 def __init__(self, c): self.c = c
438 class Bar(object): pass
460 class Bar(object): pass
439 class Bah(object):
461 class Bah(object):
440 def __init__(self, c, d):
462 def __init__(self, c, d):
441 self.c = c; self.d = d
463 self.c = c; self.d = d
442
464
443 class A(HasTraitlets):
465 class A(HasTraitlets):
444 inst = Instance(Foo, (10,))
466 inst = Instance(Foo, (10,))
445 a = A()
467 a = A()
446 self.assertEquals(a.inst.c, 10)
468 self.assertEquals(a.inst.c, 10)
447
469
448 class B(HasTraitlets):
470 class B(HasTraitlets):
449 inst = Instance(Bah, args=(10,), kw=dict(d=20))
471 inst = Instance(Bah, args=(10,), kw=dict(d=20))
450 b = B()
472 b = B()
451 self.assertEquals(b.inst.c, 10)
473 self.assertEquals(b.inst.c, 10)
452 self.assertEquals(b.inst.d, 20)
474 self.assertEquals(b.inst.d, 20)
453
475
454 class C(HasTraitlets):
476 class C(HasTraitlets):
455 inst = Instance(Foo)
477 inst = Instance(Foo)
456 c = C()
478 c = C()
457 self.assert_(c.inst is None)
479 self.assert_(c.inst is None)
458
480
459 def test_bad_default(self):
481 def test_bad_default(self):
460 class Foo(object): pass
482 class Foo(object): pass
461
483
462 class A(HasTraitlets):
484 class A(HasTraitlets):
463 inst = Instance(Foo, allow_none=False)
485 inst = Instance(Foo, allow_none=False)
464
486
465 self.assertRaises(TraitletError, A)
487 self.assertRaises(TraitletError, A)
466
488
467 def test_instance(self):
489 def test_instance(self):
468 class Foo(object): pass
490 class Foo(object): pass
469
491
470 def inner():
492 def inner():
471 class A(HasTraitlets):
493 class A(HasTraitlets):
472 inst = Instance(Foo())
494 inst = Instance(Foo())
473
495
474 self.assertRaises(TraitletError, inner)
496 self.assertRaises(TraitletError, inner)
475
497
476
498
477 class TestThis(TestCase):
499 class TestThis(TestCase):
478
500
479 def test_this_class(self):
501 def test_this_class(self):
480 class Foo(HasTraitlets):
502 class Foo(HasTraitlets):
481 this = This
503 this = This
482
504
483 f = Foo()
505 f = Foo()
484 self.assertEquals(f.this, None)
506 self.assertEquals(f.this, None)
485 g = Foo()
507 g = Foo()
486 f.this = g
508 f.this = g
487 self.assertEquals(f.this, g)
509 self.assertEquals(f.this, g)
488 self.assertRaises(TraitletError, setattr, f, 'this', 10)
510 self.assertRaises(TraitletError, setattr, f, 'this', 10)
489
511
490 def test_this_inst(self):
512 def test_this_inst(self):
491 class Foo(HasTraitlets):
513 class Foo(HasTraitlets):
492 this = This()
514 this = This()
493
515
494 f = Foo()
516 f = Foo()
495 f.this = Foo()
517 f.this = Foo()
496 self.assert_(isinstance(f.this, Foo))
518 self.assert_(isinstance(f.this, Foo))
497
519
498 def test_subclass(self):
520 def test_subclass(self):
499 class Foo(HasTraitlets):
521 class Foo(HasTraitlets):
500 t = This()
522 t = This()
501 class Bar(Foo):
523 class Bar(Foo):
502 pass
524 pass
503 f = Foo()
525 f = Foo()
504 b = Bar()
526 b = Bar()
505 f.t = b
527 f.t = b
506 b.t = f
528 b.t = f
507 self.assertEquals(f.t, b)
529 self.assertEquals(f.t, b)
508 self.assertEquals(b.t, f)
530 self.assertEquals(b.t, f)
509
531
510 def test_subclass_override(self):
532 def test_subclass_override(self):
511 class Foo(HasTraitlets):
533 class Foo(HasTraitlets):
512 t = This()
534 t = This()
513 class Bar(Foo):
535 class Bar(Foo):
514 t = This()
536 t = This()
515 f = Foo()
537 f = Foo()
516 b = Bar()
538 b = Bar()
517 f.t = b
539 f.t = b
518 self.assertEquals(f.t, b)
540 self.assertEquals(f.t, b)
519 self.assertRaises(TraitletError, setattr, b, 't', f)
541 self.assertRaises(TraitletError, setattr, b, 't', f)
520
542
521 class TraitletTestBase(TestCase):
543 class TraitletTestBase(TestCase):
522 """A best testing class for basic traitlet types."""
544 """A best testing class for basic traitlet types."""
523
545
524 def assign(self, value):
546 def assign(self, value):
525 self.obj.value = value
547 self.obj.value = value
526
548
527 def coerce(self, value):
549 def coerce(self, value):
528 return value
550 return value
529
551
530 def test_good_values(self):
552 def test_good_values(self):
531 if hasattr(self, '_good_values'):
553 if hasattr(self, '_good_values'):
532 for value in self._good_values:
554 for value in self._good_values:
533 self.assign(value)
555 self.assign(value)
534 self.assertEquals(self.obj.value, self.coerce(value))
556 self.assertEquals(self.obj.value, self.coerce(value))
535
557
536 def test_bad_values(self):
558 def test_bad_values(self):
537 if hasattr(self, '_bad_values'):
559 if hasattr(self, '_bad_values'):
538 for value in self._bad_values:
560 for value in self._bad_values:
539 self.assertRaises(TraitletError, self.assign, value)
561 self.assertRaises(TraitletError, self.assign, value)
540
562
541 def test_default_value(self):
563 def test_default_value(self):
542 if hasattr(self, '_default_value'):
564 if hasattr(self, '_default_value'):
543 self.assertEquals(self._default_value, self.obj.value)
565 self.assertEquals(self._default_value, self.obj.value)
544
566
545
567
546 class AnyTraitlet(HasTraitlets):
568 class AnyTraitlet(HasTraitlets):
547
569
548 value = Any
570 value = Any
549
571
550 class AnyTraitTest(TraitletTestBase):
572 class AnyTraitTest(TraitletTestBase):
551
573
552 obj = AnyTraitlet()
574 obj = AnyTraitlet()
553
575
554 _default_value = None
576 _default_value = None
555 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
577 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
556 _bad_values = []
578 _bad_values = []
557
579
558
580
559 class IntTraitlet(HasTraitlets):
581 class IntTraitlet(HasTraitlets):
560
582
561 value = Int(99)
583 value = Int(99)
562
584
563 class TestInt(TraitletTestBase):
585 class TestInt(TraitletTestBase):
564
586
565 obj = IntTraitlet()
587 obj = IntTraitlet()
566 _default_value = 99
588 _default_value = 99
567 _good_values = [10, -10]
589 _good_values = [10, -10]
568 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
590 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j, 10L,
569 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
591 -10L, 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
570 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
592 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
571
593
572
594
573 class LongTraitlet(HasTraitlets):
595 class LongTraitlet(HasTraitlets):
574
596
575 value = Long(99L)
597 value = Long(99L)
576
598
577 class TestLong(TraitletTestBase):
599 class TestLong(TraitletTestBase):
578
600
579 obj = LongTraitlet()
601 obj = LongTraitlet()
580
602
581 _default_value = 99L
603 _default_value = 99L
582 _good_values = [10, -10, 10L, -10L]
604 _good_values = [10, -10, 10L, -10L]
583 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
605 _bad_values = ['ten', u'ten', [10], [10l], {'ten': 10},(10,),(10L,),
584 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
606 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
585 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
607 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
586 u'-10.1']
608 u'-10.1']
587
609
588
610
589 class FloatTraitlet(HasTraitlets):
611 class FloatTraitlet(HasTraitlets):
590
612
591 value = Float(99.0)
613 value = Float(99.0)
592
614
593 class TestFloat(TraitletTestBase):
615 class TestFloat(TraitletTestBase):
594
616
595 obj = FloatTraitlet()
617 obj = FloatTraitlet()
596
618
597 _default_value = 99.0
619 _default_value = 99.0
598 _good_values = [10, -10, 10.1, -10.1]
620 _good_values = [10, -10, 10.1, -10.1]
599 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
621 _bad_values = [10L, -10L, 'ten', u'ten', [10], {'ten': 10},(10,), None,
600 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
622 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
601 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
623 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
602
624
603
625
604 class ComplexTraitlet(HasTraitlets):
626 class ComplexTraitlet(HasTraitlets):
605
627
606 value = Complex(99.0-99.0j)
628 value = Complex(99.0-99.0j)
607
629
608 class TestComplex(TraitletTestBase):
630 class TestComplex(TraitletTestBase):
609
631
610 obj = ComplexTraitlet()
632 obj = ComplexTraitlet()
611
633
612 _default_value = 99.0-99.0j
634 _default_value = 99.0-99.0j
613 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
635 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
614 10.1j, 10.1+10.1j, 10.1-10.1j]
636 10.1j, 10.1+10.1j, 10.1-10.1j]
615 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
637 _bad_values = [10L, -10L, u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
616
638
617
639
618 class StringTraitlet(HasTraitlets):
640 class StringTraitlet(HasTraitlets):
619
641
620 value = Str('string')
642 value = Str('string')
621
643
622 class TestString(TraitletTestBase):
644 class TestString(TraitletTestBase):
623
645
624 obj = StringTraitlet()
646 obj = StringTraitlet()
625
647
626 _default_value = 'string'
648 _default_value = 'string'
627 _good_values = ['10', '-10', '10L',
649 _good_values = ['10', '-10', '10L',
628 '-10L', '10.1', '-10.1', 'string']
650 '-10L', '10.1', '-10.1', 'string']
629 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
651 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j, [10],
630 ['ten'],{'ten': 10},(10,), None, u'string']
652 ['ten'],{'ten': 10},(10,), None, u'string']
631
653
632
654
633 class UnicodeTraitlet(HasTraitlets):
655 class UnicodeTraitlet(HasTraitlets):
634
656
635 value = Unicode(u'unicode')
657 value = Unicode(u'unicode')
636
658
637 class TestUnicode(TraitletTestBase):
659 class TestUnicode(TraitletTestBase):
638
660
639 obj = UnicodeTraitlet()
661 obj = UnicodeTraitlet()
640
662
641 _default_value = u'unicode'
663 _default_value = u'unicode'
642 _good_values = ['10', '-10', '10L', '-10L', '10.1',
664 _good_values = ['10', '-10', '10L', '-10L', '10.1',
643 '-10.1', '', u'', 'string', u'string', ]
665 '-10.1', '', u'', 'string', u'string', ]
644 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
666 _bad_values = [10, -10, 10L, -10L, 10.1, -10.1, 1j,
645 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
667 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
@@ -1,798 +1,858 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, FunctionType
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
137 class _SimpleTest:
138 def __init__ ( self, value ): self.value = value
139 def __call__ ( self, test ):
140 print test, self.value
141 return test == self.value
142 def __repr__(self):
143 return "<SimpleTest(%r)" % self.value
144 def __str__(self):
145 return self.__repr__()
146
147
136 #-----------------------------------------------------------------------------
148 #-----------------------------------------------------------------------------
137 # Base TraitletType for all traitlets
149 # Base TraitletType for all traitlets
138 #-----------------------------------------------------------------------------
150 #-----------------------------------------------------------------------------
139
151
140
152
141 class TraitletType(object):
153 class TraitletType(object):
142 """A base class for all traitlet descriptors.
154 """A base class for all traitlet descriptors.
143
155
144 Notes
156 Notes
145 -----
157 -----
146 Our implementation of traitlets is based on Python's descriptor
158 Our implementation of traitlets is based on Python's descriptor
147 prototol. This class is the base class for all such descriptors. The
159 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`
160 only magic we use is a custom metaclass for the main :class:`HasTraitlets`
149 class that does the following:
161 class that does the following:
150
162
151 1. Sets the :attr:`name` attribute of every :class:`TraitletType`
163 1. Sets the :attr:`name` attribute of every :class:`TraitletType`
152 instance in the class dict to the name of the attribute.
164 instance in the class dict to the name of the attribute.
153 2. Sets the :attr:`this_class` attribute of every :class:`TraitletType`
165 2. Sets the :attr:`this_class` attribute of every :class:`TraitletType`
154 instance in the class dict to the *class* that declared the traitlet.
166 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
167 This is used by the :class:`This` traitlet to allow subclasses to
156 accept superclasses for :class:`This` values.
168 accept superclasses for :class:`This` values.
157 """
169 """
158
170
159
171
160 metadata = {}
172 metadata = {}
161 default_value = Undefined
173 default_value = Undefined
162 info_text = 'any value'
174 info_text = 'any value'
163
175
164 def __init__(self, default_value=NoDefaultSpecified, **metadata):
176 def __init__(self, default_value=NoDefaultSpecified, **metadata):
165 """Create a TraitletType.
177 """Create a TraitletType.
166 """
178 """
167 if default_value is not NoDefaultSpecified:
179 if default_value is not NoDefaultSpecified:
168 self.default_value = default_value
180 self.default_value = default_value
169 self.metadata.update(metadata)
181
182 if len(metadata) > 0:
183 if len(self.metadata) > 0:
184 self._metadata = self.metadata.copy()
185 self._metadata.update(metadata)
186 else:
187 self._metadata = metadata
188 else:
189 self._metadata = self.metadata
190
170 self.init()
191 self.init()
171
192
172 def init(self):
193 def init(self):
173 pass
194 pass
174
195
175 def get_default_value(self):
196 def get_default_value(self):
176 """Create a new instance of the default value."""
197 """Create a new instance of the default value."""
177 dv = self.default_value
198 dv = self.default_value
178 return dv
199 return dv
179
200
180 def set_default_value(self, obj):
201 def set_default_value(self, obj):
181 dv = self.get_default_value()
202 dv = self.get_default_value()
182 newdv = self._validate(obj, dv)
203 newdv = self._validate(obj, dv)
183 obj._traitlet_values[self.name] = newdv
204 obj._traitlet_values[self.name] = newdv
184
205
185
206
186 def __get__(self, obj, cls=None):
207 def __get__(self, obj, cls=None):
187 """Get the value of the traitlet by self.name for the instance.
208 """Get the value of the traitlet by self.name for the instance.
188
209
189 Default values are instantiated when :meth:`HasTraitlets.__new__`
210 Default values are instantiated when :meth:`HasTraitlets.__new__`
190 is called. Thus by the time this method gets called either the
211 is called. Thus by the time this method gets called either the
191 default value or a user defined value (they called :meth:`__set__`)
212 default value or a user defined value (they called :meth:`__set__`)
192 is in the :class:`HasTraitlets` instance.
213 is in the :class:`HasTraitlets` instance.
193 """
214 """
194 if obj is None:
215 if obj is None:
195 return self
216 return self
196 else:
217 else:
197 try:
218 try:
198 value = obj._traitlet_values[self.name]
219 value = obj._traitlet_values[self.name]
199 except:
220 except:
200 # HasTraitlets should call set_default_value to populate
221 # HasTraitlets should call set_default_value to populate
201 # this. So this should never be reached.
222 # this. So this should never be reached.
202 raise TraitletError('Unexpected error in TraitletType: '
223 raise TraitletError('Unexpected error in TraitletType: '
203 'default value not set properly')
224 'default value not set properly')
204 else:
225 else:
205 return value
226 return value
206
227
207 def __set__(self, obj, value):
228 def __set__(self, obj, value):
208 new_value = self._validate(obj, value)
229 new_value = self._validate(obj, value)
209 old_value = self.__get__(obj)
230 old_value = self.__get__(obj)
210 if old_value != new_value:
231 if old_value != new_value:
211 obj._traitlet_values[self.name] = new_value
232 obj._traitlet_values[self.name] = new_value
212 obj._notify_traitlet(self.name, old_value, new_value)
233 obj._notify_traitlet(self.name, old_value, new_value)
213
234
214 def _validate(self, obj, value):
235 def _validate(self, obj, value):
215 if hasattr(self, 'validate'):
236 if hasattr(self, 'validate'):
216 return self.validate(obj, value)
237 return self.validate(obj, value)
217 elif hasattr(self, 'is_valid_for'):
238 elif hasattr(self, 'is_valid_for'):
218 valid = self.is_valid_for(value)
239 valid = self.is_valid_for(value)
219 if valid:
240 if valid:
220 return value
241 return value
221 else:
242 else:
222 raise TraitletError('invalid value for type: %r' % value)
243 raise TraitletError('invalid value for type: %r' % value)
223 elif hasattr(self, 'value_for'):
244 elif hasattr(self, 'value_for'):
224 return self.value_for(value)
245 return self.value_for(value)
225 else:
246 else:
226 return value
247 return value
227
248
228 def info(self):
249 def info(self):
229 return self.info_text
250 return self.info_text
230
251
231 def error(self, obj, value):
252 def error(self, obj, value):
232 if obj is not None:
253 if obj is not None:
233 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
254 e = "The '%s' traitlet of %s instance must be %s, but a value of %s was specified." \
234 % (self.name, class_of(obj),
255 % (self.name, class_of(obj),
235 self.info(), repr_type(value))
256 self.info(), repr_type(value))
236 else:
257 else:
237 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
258 e = "The '%s' traitlet must be %s, but a value of %r was specified." \
238 % (self.name, self.info(), repr_type(value))
259 % (self.name, self.info(), repr_type(value))
239 raise TraitletError(e)
260 raise TraitletError(e)
240
261
262 def get_metadata(self, key):
263 return getattr(self, '_metadata', {}).get(key, None)
264
265 def set_metadata(self, key, value):
266 getattr(self, '_metadata', {})[key] = value
267
241
268
242 #-----------------------------------------------------------------------------
269 #-----------------------------------------------------------------------------
243 # The HasTraitlets implementation
270 # The HasTraitlets implementation
244 #-----------------------------------------------------------------------------
271 #-----------------------------------------------------------------------------
245
272
246
273
247 class MetaHasTraitlets(type):
274 class MetaHasTraitlets(type):
248 """A metaclass for HasTraitlets.
275 """A metaclass for HasTraitlets.
249
276
250 This metaclass makes sure that any TraitletType class attributes are
277 This metaclass makes sure that any TraitletType class attributes are
251 instantiated and sets their name attribute.
278 instantiated and sets their name attribute.
252 """
279 """
253
280
254 def __new__(mcls, name, bases, classdict):
281 def __new__(mcls, name, bases, classdict):
255 """Create the HasTraitlets class.
282 """Create the HasTraitlets class.
256
283
257 This instantiates all TraitletTypes in the class dict and sets their
284 This instantiates all TraitletTypes in the class dict and sets their
258 :attr:`name` attribute.
285 :attr:`name` attribute.
259 """
286 """
260 # print "========================="
287 # print "========================="
261 # print "MetaHasTraitlets.__new__"
288 # print "MetaHasTraitlets.__new__"
262 # print "mcls, ", mcls
289 # print "mcls, ", mcls
263 # print "name, ", name
290 # print "name, ", name
264 # print "bases, ", bases
291 # print "bases, ", bases
265 # print "classdict, ", classdict
292 # print "classdict, ", classdict
266 for k,v in classdict.iteritems():
293 for k,v in classdict.iteritems():
267 if isinstance(v, TraitletType):
294 if isinstance(v, TraitletType):
268 v.name = k
295 v.name = k
269 elif inspect.isclass(v):
296 elif inspect.isclass(v):
270 if issubclass(v, TraitletType):
297 if issubclass(v, TraitletType):
271 vinst = v()
298 vinst = v()
272 vinst.name = k
299 vinst.name = k
273 classdict[k] = vinst
300 classdict[k] = vinst
274 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
301 return super(MetaHasTraitlets, mcls).__new__(mcls, name, bases, classdict)
275
302
276 def __init__(cls, name, bases, classdict):
303 def __init__(cls, name, bases, classdict):
277 """Finish initializing the HasTraitlets class.
304 """Finish initializing the HasTraitlets class.
278
305
279 This sets the :attr:`this_class` attribute of each TraitletType in the
306 This sets the :attr:`this_class` attribute of each TraitletType in the
280 class dict to the newly created class ``cls``.
307 class dict to the newly created class ``cls``.
281 """
308 """
282 # print "========================="
309 # print "========================="
283 # print "MetaHasTraitlets.__init__"
310 # print "MetaHasTraitlets.__init__"
284 # print "cls, ", cls
311 # print "cls, ", cls
285 # print "name, ", name
312 # print "name, ", name
286 # print "bases, ", bases
313 # print "bases, ", bases
287 # print "classdict, ", classdict
314 # print "classdict, ", classdict
288 for k, v in classdict.iteritems():
315 for k, v in classdict.iteritems():
289 if isinstance(v, TraitletType):
316 if isinstance(v, TraitletType):
290 v.this_class = cls
317 v.this_class = cls
291 super(MetaHasTraitlets, cls).__init__(name, bases, classdict)
318 super(MetaHasTraitlets, cls).__init__(name, bases, classdict)
292
319
293 class HasTraitlets(object):
320 class HasTraitlets(object):
294
321
295 __metaclass__ = MetaHasTraitlets
322 __metaclass__ = MetaHasTraitlets
296
323
297 def __new__(cls, *args, **kw):
324 def __new__(cls, *args, **kw):
298 inst = super(HasTraitlets, cls).__new__(cls, *args, **kw)
325 inst = super(HasTraitlets, cls).__new__(cls, *args, **kw)
299 inst._traitlet_values = {}
326 inst._traitlet_values = {}
300 inst._traitlet_notifiers = {}
327 inst._traitlet_notifiers = {}
301 # Here we tell all the TraitletType instances to set their default
328 # Here we tell all the TraitletType instances to set their default
302 # values on the instance.
329 # values on the instance.
303 for key in dir(cls):
330 for key in dir(cls):
304 value = getattr(cls, key)
331 value = getattr(cls, key)
305 if isinstance(value, TraitletType):
332 if isinstance(value, TraitletType):
306 # print 'value: ', value
307 value.set_default_value(inst)
333 value.set_default_value(inst)
308 return inst
334 return inst
309
335
310 # def __init__(self):
336 # def __init__(self):
311 # self._traitlet_values = {}
337 # self._traitlet_values = {}
312 # self._traitlet_notifiers = {}
338 # self._traitlet_notifiers = {}
313
339
314 def _notify_traitlet(self, name, old_value, new_value):
340 def _notify_traitlet(self, name, old_value, new_value):
315
341
316 # First dynamic ones
342 # First dynamic ones
317 callables = self._traitlet_notifiers.get(name,[])
343 callables = self._traitlet_notifiers.get(name,[])
318 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
344 more_callables = self._traitlet_notifiers.get('anytraitlet',[])
319 callables.extend(more_callables)
345 callables.extend(more_callables)
320
346
321 # Now static ones
347 # Now static ones
322 try:
348 try:
323 cb = getattr(self, '_%s_changed' % name)
349 cb = getattr(self, '_%s_changed' % name)
324 except:
350 except:
325 pass
351 pass
326 else:
352 else:
327 callables.append(cb)
353 callables.append(cb)
328
354
329 # Call them all now
355 # Call them all now
330 for c in callables:
356 for c in callables:
331 # Traits catches and logs errors here. I allow them to raise
357 # Traits catches and logs errors here. I allow them to raise
332 if callable(c):
358 if callable(c):
333 argspec = inspect.getargspec(c)
359 argspec = inspect.getargspec(c)
334 nargs = len(argspec[0])
360 nargs = len(argspec[0])
335 # Bound methods have an additional 'self' argument
361 # Bound methods have an additional 'self' argument
336 # I don't know how to treat unbound methods, but they
362 # I don't know how to treat unbound methods, but they
337 # can't really be used for callbacks.
363 # can't really be used for callbacks.
338 if isinstance(c, types.MethodType):
364 if isinstance(c, types.MethodType):
339 offset = -1
365 offset = -1
340 else:
366 else:
341 offset = 0
367 offset = 0
342 if nargs + offset == 0:
368 if nargs + offset == 0:
343 c()
369 c()
344 elif nargs + offset == 1:
370 elif nargs + offset == 1:
345 c(name)
371 c(name)
346 elif nargs + offset == 2:
372 elif nargs + offset == 2:
347 c(name, new_value)
373 c(name, new_value)
348 elif nargs + offset == 3:
374 elif nargs + offset == 3:
349 c(name, old_value, new_value)
375 c(name, old_value, new_value)
350 else:
376 else:
351 raise TraitletError('a traitlet changed callback '
377 raise TraitletError('a traitlet changed callback '
352 'must have 0-3 arguments.')
378 'must have 0-3 arguments.')
353 else:
379 else:
354 raise TraitletError('a traitlet changed callback '
380 raise TraitletError('a traitlet changed callback '
355 'must be callable.')
381 'must be callable.')
356
382
357
383
358 def _add_notifiers(self, handler, name):
384 def _add_notifiers(self, handler, name):
359 if not self._traitlet_notifiers.has_key(name):
385 if not self._traitlet_notifiers.has_key(name):
360 nlist = []
386 nlist = []
361 self._traitlet_notifiers[name] = nlist
387 self._traitlet_notifiers[name] = nlist
362 else:
388 else:
363 nlist = self._traitlet_notifiers[name]
389 nlist = self._traitlet_notifiers[name]
364 if handler not in nlist:
390 if handler not in nlist:
365 nlist.append(handler)
391 nlist.append(handler)
366
392
367 def _remove_notifiers(self, handler, name):
393 def _remove_notifiers(self, handler, name):
368 if self._traitlet_notifiers.has_key(name):
394 if self._traitlet_notifiers.has_key(name):
369 nlist = self._traitlet_notifiers[name]
395 nlist = self._traitlet_notifiers[name]
370 try:
396 try:
371 index = nlist.index(handler)
397 index = nlist.index(handler)
372 except ValueError:
398 except ValueError:
373 pass
399 pass
374 else:
400 else:
375 del nlist[index]
401 del nlist[index]
376
402
377 def on_traitlet_change(self, handler, name=None, remove=False):
403 def on_traitlet_change(self, handler, name=None, remove=False):
378 """Setup a handler to be called when a traitlet changes.
404 """Setup a handler to be called when a traitlet changes.
379
405
380 This is used to setup dynamic notifications of traitlet changes.
406 This is used to setup dynamic notifications of traitlet changes.
381
407
382 Static handlers can be created by creating methods on a HasTraitlets
408 Static handlers can be created by creating methods on a HasTraitlets
383 subclass with the naming convention '_[traitletname]_changed'. Thus,
409 subclass with the naming convention '_[traitletname]_changed'. Thus,
384 to create static handler for the traitlet 'a', create the method
410 to create static handler for the traitlet 'a', create the method
385 _a_changed(self, name, old, new) (fewer arguments can be used, see
411 _a_changed(self, name, old, new) (fewer arguments can be used, see
386 below).
412 below).
387
413
388 Parameters
414 Parameters
389 ----------
415 ----------
390 handler : callable
416 handler : callable
391 A callable that is called when a traitlet changes. Its
417 A callable that is called when a traitlet changes. Its
392 signature can be handler(), handler(name), handler(name, new)
418 signature can be handler(), handler(name), handler(name, new)
393 or handler(name, old, new).
419 or handler(name, old, new).
394 name : list, str, None
420 name : list, str, None
395 If None, the handler will apply to all traitlets. If a list
421 If None, the handler will apply to all traitlets. If a list
396 of str, handler will apply to all names in the list. If a
422 of str, handler will apply to all names in the list. If a
397 str, the handler will apply just to that name.
423 str, the handler will apply just to that name.
398 remove : bool
424 remove : bool
399 If False (the default), then install the handler. If True
425 If False (the default), then install the handler. If True
400 then unintall it.
426 then unintall it.
401 """
427 """
402 if remove:
428 if remove:
403 names = parse_notifier_name(name)
429 names = parse_notifier_name(name)
404 for n in names:
430 for n in names:
405 self._remove_notifiers(handler, n)
431 self._remove_notifiers(handler, n)
406 else:
432 else:
407 names = parse_notifier_name(name)
433 names = parse_notifier_name(name)
408 for n in names:
434 for n in names:
409 self._add_notifiers(handler, n)
435 self._add_notifiers(handler, n)
410
436
411 def traitlet_names(self):
437 def traitlet_names(self, **metadata):
412 """Get a list of all the names of this classes traitlets."""
438 """Get a list of all the names of this classes traitlets."""
413 return [memb[0] for memb in inspect.getmembers(self.__class__) if isinstance(memb[1], TraitletType)]
439 return self.traitlets(**metadata).keys()
414
440
441 def traitlets(self, **metadata):
442 """Get a list of all the traitlets of this class.
443
444 The TraitletTypes returned don't know anything about the values
445 that the various HasTraitlet's instances are holding.
446 """
447 traitlets = dict([memb for memb in inspect.getmembers(self.__class__) if \
448 isinstance(memb[1], TraitletType)])
449 if len(metadata) == 0:
450 return traitlets
451
452 for meta_name, meta_eval in metadata.items():
453 if type(meta_eval) is not FunctionType:
454 metadata[meta_name] = _SimpleTest(meta_eval)
455
456 result = {}
457 for name, traitlet in traitlets.items():
458 for meta_name, meta_eval in metadata.items():
459 if not meta_eval(traitlet.get_metadata(meta_name)):
460 break
461 else:
462 result[name] = traitlet
463
464 return result
465
466 def traitlet_metadata(self, traitletname, key):
467 """Get metadata values for traitlet by key."""
468 try:
469 traitlet = getattr(self.__class__, traitletname)
470 except AttributeError:
471 raise TraitletError("Class %s does not have a traitlet named %s" %
472 (self.__class__.__name__, traitletname))
473 else:
474 return traitlet.get_metadata(key)
415
475
416 #-----------------------------------------------------------------------------
476 #-----------------------------------------------------------------------------
417 # Actual TraitletTypes implementations/subclasses
477 # Actual TraitletTypes implementations/subclasses
418 #-----------------------------------------------------------------------------
478 #-----------------------------------------------------------------------------
419
479
420 #-----------------------------------------------------------------------------
480 #-----------------------------------------------------------------------------
421 # TraitletTypes subclasses for handling classes and instances of classes
481 # TraitletTypes subclasses for handling classes and instances of classes
422 #-----------------------------------------------------------------------------
482 #-----------------------------------------------------------------------------
423
483
424
484
425 class ClassBasedTraitletType(TraitletType):
485 class ClassBasedTraitletType(TraitletType):
426 """A traitlet with error reporting for Type, Instance and This."""
486 """A traitlet with error reporting for Type, Instance and This."""
427
487
428 def error(self, obj, value):
488 def error(self, obj, value):
429 kind = type(value)
489 kind = type(value)
430 if kind is InstanceType:
490 if kind is InstanceType:
431 msg = 'class %s' % value.__class__.__name__
491 msg = 'class %s' % value.__class__.__name__
432 else:
492 else:
433 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
493 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
434
494
435 super(ClassBasedTraitletType, self).error(obj, msg)
495 super(ClassBasedTraitletType, self).error(obj, msg)
436
496
437
497
438 class Type(ClassBasedTraitletType):
498 class Type(ClassBasedTraitletType):
439 """A traitlet whose value must be a subclass of a specified class."""
499 """A traitlet whose value must be a subclass of a specified class."""
440
500
441 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
501 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
442 """Construct a Type traitlet
502 """Construct a Type traitlet
443
503
444 A Type traitlet specifies that its values must be subclasses of
504 A Type traitlet specifies that its values must be subclasses of
445 a particular class.
505 a particular class.
446
506
447 Parameters
507 Parameters
448 ----------
508 ----------
449 default_value : class
509 default_value : class
450 The default value must be a subclass of klass.
510 The default value must be a subclass of klass.
451 klass : class, str, None
511 klass : class, str, None
452 Values of this traitlet must be a subclass of klass. The klass
512 Values of this traitlet must be a subclass of klass. The klass
453 may be specified in a string like: 'foo.bar.MyClass'.
513 may be specified in a string like: 'foo.bar.MyClass'.
454 allow_none : boolean
514 allow_none : boolean
455 Indicates whether None is allowed as an assignable value. Even if
515 Indicates whether None is allowed as an assignable value. Even if
456 ``False``, the default value may be ``None``.
516 ``False``, the default value may be ``None``.
457 """
517 """
458 if default_value is None:
518 if default_value is None:
459 if klass is None:
519 if klass is None:
460 klass = object
520 klass = object
461 elif klass is None:
521 elif klass is None:
462 klass = default_value
522 klass = default_value
463
523
464 if not inspect.isclass(klass):
524 if not inspect.isclass(klass):
465 raise TraitletError("A Type traitlet must specify a class.")
525 raise TraitletError("A Type traitlet must specify a class.")
466
526
467 self.klass = klass
527 self.klass = klass
468 self._allow_none = allow_none
528 self._allow_none = allow_none
469
529
470 super(Type, self).__init__(default_value, **metadata)
530 super(Type, self).__init__(default_value, **metadata)
471
531
472 def validate(self, obj, value):
532 def validate(self, obj, value):
473 """Validates that the value is a valid object instance."""
533 """Validates that the value is a valid object instance."""
474 try:
534 try:
475 if issubclass(value, self.klass):
535 if issubclass(value, self.klass):
476 return value
536 return value
477 except:
537 except:
478 if (value is None) and (self._allow_none):
538 if (value is None) and (self._allow_none):
479 return value
539 return value
480
540
481 self.error(obj, value)
541 self.error(obj, value)
482
542
483 def info(self):
543 def info(self):
484 """ Returns a description of the trait."""
544 """ Returns a description of the trait."""
485 klass = self.klass.__name__
545 klass = self.klass.__name__
486 result = 'a subclass of ' + klass
546 result = 'a subclass of ' + klass
487 if self._allow_none:
547 if self._allow_none:
488 return result + ' or None'
548 return result + ' or None'
489 return result
549 return result
490
550
491
551
492 class DefaultValueGenerator(object):
552 class DefaultValueGenerator(object):
493 """A class for generating new default value instances."""
553 """A class for generating new default value instances."""
494
554
495 def __init__(self, klass, *args, **kw):
555 def __init__(self, klass, *args, **kw):
496 self.klass = klass
556 self.klass = klass
497 self.args = args
557 self.args = args
498 self.kw = kw
558 self.kw = kw
499
559
500 def generate(self):
560 def generate(self):
501 return self.klass(*self.args, **self.kw)
561 return self.klass(*self.args, **self.kw)
502
562
503
563
504 class Instance(ClassBasedTraitletType):
564 class Instance(ClassBasedTraitletType):
505 """A trait whose value must be an instance of a specified class.
565 """A trait whose value must be an instance of a specified class.
506
566
507 The value can also be an instance of a subclass of the specified class.
567 The value can also be an instance of a subclass of the specified class.
508 """
568 """
509
569
510 def __init__(self, klass=None, args=None, kw=None,
570 def __init__(self, klass=None, args=None, kw=None,
511 allow_none=True, **metadata ):
571 allow_none=True, **metadata ):
512 """Construct an Instance traitlet.
572 """Construct an Instance traitlet.
513
573
514 This traitlet allows values that are instances of a particular
574 This traitlet allows values that are instances of a particular
515 class or its sublclasses. Our implementation is quite different
575 class or its sublclasses. Our implementation is quite different
516 from that of enthough.traits as we don't allow instances to be used
576 from that of enthough.traits as we don't allow instances to be used
517 for klass and we handle the ``args`` and ``kw`` arguments differently.
577 for klass and we handle the ``args`` and ``kw`` arguments differently.
518
578
519 Parameters
579 Parameters
520 ----------
580 ----------
521 klass : class
581 klass : class
522 The class that forms the basis for the traitlet. Instances
582 The class that forms the basis for the traitlet. Instances
523 and strings are not allowed.
583 and strings are not allowed.
524 args : tuple
584 args : tuple
525 Positional arguments for generating the default value.
585 Positional arguments for generating the default value.
526 kw : dict
586 kw : dict
527 Keyword arguments for generating the default value.
587 Keyword arguments for generating the default value.
528 allow_none : bool
588 allow_none : bool
529 Indicates whether None is allowed as a value.
589 Indicates whether None is allowed as a value.
530
590
531 Default Value
591 Default Value
532 -------------
592 -------------
533 If both ``args`` and ``kw`` are None, then the default value is None.
593 If both ``args`` and ``kw`` are None, then the default value is None.
534 If ``args`` is a tuple and ``kw`` is a dict, then the default is
594 If ``args`` is a tuple and ``kw`` is a dict, then the default is
535 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
595 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
536 not (but not both), None is replace by ``()`` or ``{}``.
596 not (but not both), None is replace by ``()`` or ``{}``.
537 """
597 """
538
598
539 self._allow_none = allow_none
599 self._allow_none = allow_none
540
600
541 if (klass is None) or (not inspect.isclass(klass)):
601 if (klass is None) or (not inspect.isclass(klass)):
542 raise TraitletError('The klass argument must be a class'
602 raise TraitletError('The klass argument must be a class'
543 ' you gave: %r' % klass)
603 ' you gave: %r' % klass)
544 self.klass = klass
604 self.klass = klass
545
605
546 # self.klass is a class, so handle default_value
606 # self.klass is a class, so handle default_value
547 if args is None and kw is None:
607 if args is None and kw is None:
548 default_value = None
608 default_value = None
549 else:
609 else:
550 if args is None:
610 if args is None:
551 # kw is not None
611 # kw is not None
552 args = ()
612 args = ()
553 elif kw is None:
613 elif kw is None:
554 # args is not None
614 # args is not None
555 kw = {}
615 kw = {}
556
616
557 if not isinstance(kw, dict):
617 if not isinstance(kw, dict):
558 raise TraitletError("The 'kw' argument must be a dict or None.")
618 raise TraitletError("The 'kw' argument must be a dict or None.")
559 if not isinstance(args, tuple):
619 if not isinstance(args, tuple):
560 raise TraitletError("The 'args' argument must be a tuple or None.")
620 raise TraitletError("The 'args' argument must be a tuple or None.")
561
621
562 default_value = DefaultValueGenerator(self.klass, *args, **kw)
622 default_value = DefaultValueGenerator(self.klass, *args, **kw)
563
623
564 super(Instance, self).__init__(default_value, **metadata)
624 super(Instance, self).__init__(default_value, **metadata)
565
625
566 def validate(self, obj, value):
626 def validate(self, obj, value):
567 if value is None:
627 if value is None:
568 if self._allow_none:
628 if self._allow_none:
569 return value
629 return value
570 self.error(obj, value)
630 self.error(obj, value)
571
631
572 if isinstance(value, self.klass):
632 if isinstance(value, self.klass):
573 return value
633 return value
574 else:
634 else:
575 self.error(obj, value)
635 self.error(obj, value)
576
636
577 def info(self):
637 def info(self):
578 klass = self.klass.__name__
638 klass = self.klass.__name__
579 result = class_of(klass)
639 result = class_of(klass)
580 if self._allow_none:
640 if self._allow_none:
581 return result + ' or None'
641 return result + ' or None'
582
642
583 return result
643 return result
584
644
585 def get_default_value(self):
645 def get_default_value(self):
586 """Instantiate a default value instance.
646 """Instantiate a default value instance.
587
647
588 This is called when the containing HasTraitlets classes'
648 This is called when the containing HasTraitlets classes'
589 :meth:`__new__` method is called to ensure that a unique instance
649 :meth:`__new__` method is called to ensure that a unique instance
590 is created for each HasTraitlets instance.
650 is created for each HasTraitlets instance.
591 """
651 """
592 dv = self.default_value
652 dv = self.default_value
593 if isinstance(dv, DefaultValueGenerator):
653 if isinstance(dv, DefaultValueGenerator):
594 return dv.generate()
654 return dv.generate()
595 else:
655 else:
596 return dv
656 return dv
597
657
598
658
599 class This(ClassBasedTraitletType):
659 class This(ClassBasedTraitletType):
600 """A traitlet for instances of the class containing this trait.
660 """A traitlet for instances of the class containing this trait.
601
661
602 Because how how and when class bodies are executed, the ``This``
662 Because how how and when class bodies are executed, the ``This``
603 traitlet can only have a default value of None. This, and because we
663 traitlet can only have a default value of None. This, and because we
604 always validate default values, ``allow_none`` is *always* true.
664 always validate default values, ``allow_none`` is *always* true.
605 """
665 """
606
666
607 info_text = 'an instance of the same type as the receiver or None'
667 info_text = 'an instance of the same type as the receiver or None'
608
668
609 def __init__(self, **metadata):
669 def __init__(self, **metadata):
610 super(This, self).__init__(None, **metadata)
670 super(This, self).__init__(None, **metadata)
611
671
612 def validate(self, obj, value):
672 def validate(self, obj, value):
613 # What if value is a superclass of obj.__class__? This is
673 # What if value is a superclass of obj.__class__? This is
614 # complicated if it was the superclass that defined the This
674 # complicated if it was the superclass that defined the This
615 # traitlet.
675 # traitlet.
616 if isinstance(value, self.this_class) or (value is None):
676 if isinstance(value, self.this_class) or (value is None):
617 return value
677 return value
618 else:
678 else:
619 self.error(obj, value)
679 self.error(obj, value)
620
680
621
681
622 #-----------------------------------------------------------------------------
682 #-----------------------------------------------------------------------------
623 # Basic TraitletTypes implementations/subclasses
683 # Basic TraitletTypes implementations/subclasses
624 #-----------------------------------------------------------------------------
684 #-----------------------------------------------------------------------------
625
685
626
686
627 class Any(TraitletType):
687 class Any(TraitletType):
628 default_value = None
688 default_value = None
629 info_text = 'any value'
689 info_text = 'any value'
630
690
631
691
632 class Int(TraitletType):
692 class Int(TraitletType):
633 """A integer traitlet."""
693 """A integer traitlet."""
634
694
635 evaluate = int
695 evaluate = int
636 default_value = 0
696 default_value = 0
637 info_text = 'an integer'
697 info_text = 'an integer'
638
698
639 def validate(self, obj, value):
699 def validate(self, obj, value):
640 if isinstance(value, int):
700 if isinstance(value, int):
641 return value
701 return value
642 self.error(obj, value)
702 self.error(obj, value)
643
703
644 class CInt(Int):
704 class CInt(Int):
645 """A casting version of the int traitlet."""
705 """A casting version of the int traitlet."""
646
706
647 def validate(self, obj, value):
707 def validate(self, obj, value):
648 try:
708 try:
649 return int(value)
709 return int(value)
650 except:
710 except:
651 self.error(obj, value)
711 self.error(obj, value)
652
712
653
713
654 class Long(TraitletType):
714 class Long(TraitletType):
655 """A long integer traitlet."""
715 """A long integer traitlet."""
656
716
657 evaluate = long
717 evaluate = long
658 default_value = 0L
718 default_value = 0L
659 info_text = 'a long'
719 info_text = 'a long'
660
720
661 def validate(self, obj, value):
721 def validate(self, obj, value):
662 if isinstance(value, long):
722 if isinstance(value, long):
663 return value
723 return value
664 if isinstance(value, int):
724 if isinstance(value, int):
665 return long(value)
725 return long(value)
666 self.error(obj, value)
726 self.error(obj, value)
667
727
668
728
669 class CLong(Long):
729 class CLong(Long):
670 """A casting version of the long integer traitlet."""
730 """A casting version of the long integer traitlet."""
671
731
672 def validate(self, obj, value):
732 def validate(self, obj, value):
673 try:
733 try:
674 return long(value)
734 return long(value)
675 except:
735 except:
676 self.error(obj, value)
736 self.error(obj, value)
677
737
678
738
679 class Float(TraitletType):
739 class Float(TraitletType):
680 """A float traitlet."""
740 """A float traitlet."""
681
741
682 evaluate = float
742 evaluate = float
683 default_value = 0.0
743 default_value = 0.0
684 info_text = 'a float'
744 info_text = 'a float'
685
745
686 def validate(self, obj, value):
746 def validate(self, obj, value):
687 if isinstance(value, float):
747 if isinstance(value, float):
688 return value
748 return value
689 if isinstance(value, int):
749 if isinstance(value, int):
690 return float(value)
750 return float(value)
691 self.error(obj, value)
751 self.error(obj, value)
692
752
693
753
694 class CFloat(Float):
754 class CFloat(Float):
695 """A casting version of the float traitlet."""
755 """A casting version of the float traitlet."""
696
756
697 def validate(self, obj, value):
757 def validate(self, obj, value):
698 try:
758 try:
699 return float(value)
759 return float(value)
700 except:
760 except:
701 self.error(obj, value)
761 self.error(obj, value)
702
762
703 class Complex(TraitletType):
763 class Complex(TraitletType):
704 """A traitlet for complex numbers."""
764 """A traitlet for complex numbers."""
705
765
706 evaluate = complex
766 evaluate = complex
707 default_value = 0.0 + 0.0j
767 default_value = 0.0 + 0.0j
708 info_text = 'a complex number'
768 info_text = 'a complex number'
709
769
710 def validate(self, obj, value):
770 def validate(self, obj, value):
711 if isinstance(value, complex):
771 if isinstance(value, complex):
712 return value
772 return value
713 if isinstance(value, (float, int)):
773 if isinstance(value, (float, int)):
714 return complex(value)
774 return complex(value)
715 self.error(obj, value)
775 self.error(obj, value)
716
776
717
777
718 class CComplex(Complex):
778 class CComplex(Complex):
719 """A casting version of the complex number traitlet."""
779 """A casting version of the complex number traitlet."""
720
780
721 def validate (self, obj, value):
781 def validate (self, obj, value):
722 try:
782 try:
723 return complex(value)
783 return complex(value)
724 except:
784 except:
725 self.error(obj, value)
785 self.error(obj, value)
726
786
727
787
728 class Str(TraitletType):
788 class Str(TraitletType):
729 """A traitlet for strings."""
789 """A traitlet for strings."""
730
790
731 evaluate = lambda x: x
791 evaluate = lambda x: x
732 default_value = ''
792 default_value = ''
733 info_text = 'a string'
793 info_text = 'a string'
734
794
735 def validate(self, obj, value):
795 def validate(self, obj, value):
736 if isinstance(value, str):
796 if isinstance(value, str):
737 return value
797 return value
738 self.error(obj, value)
798 self.error(obj, value)
739
799
740
800
741 class CStr(Str):
801 class CStr(Str):
742 """A casting version of the string traitlet."""
802 """A casting version of the string traitlet."""
743
803
744 def validate(self, obj, value):
804 def validate(self, obj, value):
745 try:
805 try:
746 return str(value)
806 return str(value)
747 except:
807 except:
748 try:
808 try:
749 return unicode(value)
809 return unicode(value)
750 except:
810 except:
751 self.error(obj, value)
811 self.error(obj, value)
752
812
753
813
754 class Unicode(TraitletType):
814 class Unicode(TraitletType):
755 """A traitlet for unicode strings."""
815 """A traitlet for unicode strings."""
756
816
757 evaluate = unicode
817 evaluate = unicode
758 default_value = u''
818 default_value = u''
759 info_text = 'a unicode string'
819 info_text = 'a unicode string'
760
820
761 def validate(self, obj, value):
821 def validate(self, obj, value):
762 if isinstance(value, unicode):
822 if isinstance(value, unicode):
763 return value
823 return value
764 if isinstance(value, str):
824 if isinstance(value, str):
765 return unicode(value)
825 return unicode(value)
766 self.error(obj, value)
826 self.error(obj, value)
767
827
768
828
769 class CUnicode(Unicode):
829 class CUnicode(Unicode):
770 """A casting version of the unicode traitlet."""
830 """A casting version of the unicode traitlet."""
771
831
772 def validate(self, obj, value):
832 def validate(self, obj, value):
773 try:
833 try:
774 return unicode(value)
834 return unicode(value)
775 except:
835 except:
776 self.error(obj, value)
836 self.error(obj, value)
777
837
778
838
779 class Bool(TraitletType):
839 class Bool(TraitletType):
780 """A boolean (True, False) traitlet."""
840 """A boolean (True, False) traitlet."""
781 evaluate = bool
841 evaluate = bool
782 default_value = False
842 default_value = False
783 info_text = 'a boolean'
843 info_text = 'a boolean'
784
844
785 def validate(self, obj, value):
845 def validate(self, obj, value):
786 if isinstance(value, bool):
846 if isinstance(value, bool):
787 return value
847 return value
788 self.error(obj, value)
848 self.error(obj, value)
789
849
790
850
791 class CBool(Bool):
851 class CBool(Bool):
792 """A casting version of the boolean traitlet."""
852 """A casting version of the boolean traitlet."""
793
853
794 def validate(self, obj, value):
854 def validate(self, obj, value):
795 try:
855 try:
796 return bool(value)
856 return bool(value)
797 except:
857 except:
798 self.error(obj, value) No newline at end of file
858 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now