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