##// END OF EJS Templates
Merge pull request #6228 from jdfreder/eventful-list-dict...
Thomas Kluyver -
r17618:3c464028 merge
parent child Browse files
Show More
@@ -0,0 +1,299 b''
1 """Contains eventful dict and list implementations."""
2
3 # void function used as a callback placeholder.
4 def _void(*p, **k): return None
5
6 class EventfulDict(dict):
7 """Eventful dictionary.
8
9 This class inherits from the Python intrinsic dictionary class, dict. It
10 adds events to the get, set, and del actions and optionally allows you to
11 intercept and cancel these actions. The eventfulness isn't recursive. In
12 other words, if you add a dict as a child, the events of that dict won't be
13 listened to. If you find you need something recursive, listen to the `add`
14 and `set` methods, and then cancel `dict` values from being set, and instead
15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
16 to the same handlers if necessary.
17
18 See the on_events, on_add, on_set, and on_del methods for registering
19 event handlers."""
20
21 def __init__(self, *args, **kwargs):
22 """Public constructor"""
23 self._add_callback = _void
24 self._del_callback = _void
25 self._set_callback = _void
26 dict.__init__(self, *args, **kwargs)
27
28 def on_events(self, add_callback=None, set_callback=None, del_callback=None):
29 """Register callbacks for add, set, and del actions.
30
31 See the doctstrings for on_(add/set/del) for details about each
32 callback.
33
34 add_callback: [callback = None]
35 set_callback: [callback = None]
36 del_callback: [callback = None]"""
37 self.on_add(add_callback)
38 self.on_set(set_callback)
39 self.on_del(del_callback)
40
41 def on_add(self, callback):
42 """Register a callback for when an item is added to the dict.
43
44 Allows the listener to detect when items are added to the dictionary and
45 optionally cancel the addition.
46
47 callback: callable or None
48 If you want to ignore the addition event, pass None as the callback.
49 The callback should have a signature of callback(key, value). The
50 callback should return a boolean True if the additon should be
51 canceled, False or None otherwise."""
52 self._add_callback = callback if callable(callback) else _void
53
54 def on_del(self, callback):
55 """Register a callback for when an item is deleted from the dict.
56
57 Allows the listener to detect when items are deleted from the dictionary
58 and optionally cancel the deletion.
59
60 callback: callable or None
61 If you want to ignore the deletion event, pass None as the callback.
62 The callback should have a signature of callback(key). The
63 callback should return a boolean True if the deletion should be
64 canceled, False or None otherwise."""
65 self._del_callback = callback if callable(callback) else _void
66
67 def on_set(self, callback):
68 """Register a callback for when an item is changed in the dict.
69
70 Allows the listener to detect when items are changed in the dictionary
71 and optionally cancel the change.
72
73 callback: callable or None
74 If you want to ignore the change event, pass None as the callback.
75 The callback should have a signature of callback(key, value). The
76 callback should return a boolean True if the change should be
77 canceled, False or None otherwise."""
78 self._set_callback = callback if callable(callback) else _void
79
80 def pop(self, key):
81 """Returns the value of an item in the dictionary and then deletes the
82 item from the dictionary."""
83 if self._can_del(key):
84 return dict.pop(self, key)
85 else:
86 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
87
88 def popitem(self):
89 """Pop the next key/value pair from the dictionary."""
90 key = next(iter(self))
91 return key, self.pop(key)
92
93 def update(self, other_dict):
94 """Copy the key/value pairs from another dictionary into this dictionary,
95 overwriting any conflicting keys in this dictionary."""
96 for (key, value) in other_dict.items():
97 self[key] = value
98
99 def clear(self):
100 """Clear the dictionary."""
101 for key in list(self.keys()):
102 del self[key]
103
104 def __setitem__(self, key, value):
105 if (key in self and self._can_set(key, value)) or \
106 (key not in self and self._can_add(key, value)):
107 return dict.__setitem__(self, key, value)
108
109 def __delitem__(self, key):
110 if self._can_del(key):
111 return dict.__delitem__(self, key)
112
113 def _can_add(self, key, value):
114 """Check if the item can be added to the dict."""
115 return not bool(self._add_callback(key, value))
116
117 def _can_del(self, key):
118 """Check if the item can be deleted from the dict."""
119 return not bool(self._del_callback(key))
120
121 def _can_set(self, key, value):
122 """Check if the item can be changed in the dict."""
123 return not bool(self._set_callback(key, value))
124
125
126 class EventfulList(list):
127 """Eventful list.
128
129 This class inherits from the Python intrinsic `list` class. It adds events
130 that allow you to listen for actions that modify the list. You can
131 optionally cancel the actions.
132
133 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
134 registering an event handler.
135
136 Some of the method docstrings were taken from the Python documentation at
137 https://docs.python.org/2/tutorial/datastructures.html"""
138
139 def __init__(self, *pargs, **kwargs):
140 """Public constructor"""
141 self._insert_callback = _void
142 self._set_callback = _void
143 self._del_callback = _void
144 self._sort_callback = _void
145 self._reverse_callback = _void
146 list.__init__(self, *pargs, **kwargs)
147
148 def on_events(self, insert_callback=None, set_callback=None,
149 del_callback=None, reverse_callback=None, sort_callback=None):
150 """Register callbacks for add, set, and del actions.
151
152 See the doctstrings for on_(insert/set/del/reverse/sort) for details
153 about each callback.
154
155 insert_callback: [callback = None]
156 set_callback: [callback = None]
157 del_callback: [callback = None]
158 reverse_callback: [callback = None]
159 sort_callback: [callback = None]"""
160 self.on_insert(insert_callback)
161 self.on_set(set_callback)
162 self.on_del(del_callback)
163 self.on_reverse(reverse_callback)
164 self.on_sort(sort_callback)
165
166 def on_insert(self, callback):
167 """Register a callback for when an item is inserted into the list.
168
169 Allows the listener to detect when items are inserted into the list and
170 optionally cancel the insertion.
171
172 callback: callable or None
173 If you want to ignore the insertion event, pass None as the callback.
174 The callback should have a signature of callback(index, value). The
175 callback should return a boolean True if the insertion should be
176 canceled, False or None otherwise."""
177 self._insert_callback = callback if callable(callback) else _void
178
179 def on_del(self, callback):
180 """Register a callback for item deletion.
181
182 Allows the listener to detect when items are deleted from the list and
183 optionally cancel the deletion.
184
185 callback: callable or None
186 If you want to ignore the deletion event, pass None as the callback.
187 The callback should have a signature of callback(index). The
188 callback should return a boolean True if the deletion should be
189 canceled, False or None otherwise."""
190 self._del_callback = callback if callable(callback) else _void
191
192 def on_set(self, callback):
193 """Register a callback for items are set.
194
195 Allows the listener to detect when items are set and optionally cancel
196 the setting. Note, `set` is also called when one or more items are
197 added to the end of the list.
198
199 callback: callable or None
200 If you want to ignore the set event, pass None as the callback.
201 The callback should have a signature of callback(index, value). The
202 callback should return a boolean True if the set should be
203 canceled, False or None otherwise."""
204 self._set_callback = callback if callable(callback) else _void
205
206 def on_reverse(self, callback):
207 """Register a callback for list reversal.
208
209 callback: callable or None
210 If you want to ignore the reverse event, pass None as the callback.
211 The callback should have a signature of callback(). The
212 callback should return a boolean True if the reverse should be
213 canceled, False or None otherwise."""
214 self._reverse_callback = callback if callable(callback) else _void
215
216 def on_sort(self, callback):
217 """Register a callback for sortting of the list.
218
219 callback: callable or None
220 If you want to ignore the sort event, pass None as the callback.
221 The callback signature should match that of Python list's `.sort`
222 method or `callback(*pargs, **kwargs)` as a catch all. The callback
223 should return a boolean True if the reverse should be canceled,
224 False or None otherwise."""
225 self._sort_callback = callback if callable(callback) else _void
226
227 def append(self, x):
228 """Add an item to the end of the list."""
229 self[len(self):] = [x]
230
231 def extend(self, L):
232 """Extend the list by appending all the items in the given list."""
233 self[len(self):] = L
234
235 def remove(self, x):
236 """Remove the first item from the list whose value is x. It is an error
237 if there is no such item."""
238 del self[self.index(x)]
239
240 def pop(self, i=None):
241 """Remove the item at the given position in the list, and return it. If
242 no index is specified, a.pop() removes and returns the last item in the
243 list."""
244 if i is None:
245 i = len(self) - 1
246 val = self[i]
247 del self[i]
248 return val
249
250 def reverse(self):
251 """Reverse the elements of the list, in place."""
252 if self._can_reverse():
253 list.reverse(self)
254
255 def insert(self, index, value):
256 """Insert an item at a given position. The first argument is the index
257 of the element before which to insert, so a.insert(0, x) inserts at the
258 front of the list, and a.insert(len(a), x) is equivalent to
259 a.append(x)."""
260 if self._can_insert(index, value):
261 list.insert(self, index, value)
262
263 def sort(self, *pargs, **kwargs):
264 """Sort the items of the list in place (the arguments can be used for
265 sort customization, see Python's sorted() for their explanation)."""
266 if self._can_sort(*pargs, **kwargs):
267 list.sort(self, *pargs, **kwargs)
268
269 def __delitem__(self, index):
270 if self._can_del(index):
271 list.__delitem__(self, index)
272
273 def __setitem__(self, index, value):
274 if self._can_set(index, value):
275 list.__setitem__(self, index, value)
276
277 def __setslice__(self, start, end, value):
278 if self._can_set(slice(start, end), value):
279 list.__setslice__(self, start, end, value)
280
281 def _can_insert(self, index, value):
282 """Check if the item can be inserted."""
283 return not bool(self._insert_callback(index, value))
284
285 def _can_del(self, index):
286 """Check if the item can be deleted."""
287 return not bool(self._del_callback(index))
288
289 def _can_set(self, index, value):
290 """Check if the item can be set."""
291 return not bool(self._set_callback(index, value))
292
293 def _can_reverse(self):
294 """Check if the list can be reversed."""
295 return not bool(self._reverse_callback())
296
297 def _can_sort(self, *pargs, **kwargs):
298 """Check if the list can be sorted."""
299 return not bool(self._sort_callback(*pargs, **kwargs))
@@ -1,1174 +1,1235 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """Tests for IPython.utils.traitlets."""
2 """Tests for IPython.utils.traitlets."""
3
3
4 # Copyright (c) IPython Development Team.
4 # Copyright (c) IPython Development Team.
5 # Distributed under the terms of the Modified BSD License.
5 # Distributed under the terms of the Modified BSD License.
6 #
6 #
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
8 # also under the terms of the Modified BSD License.
8 # also under the terms of the Modified BSD License.
9
9
10 import pickle
10 import pickle
11 import re
11 import re
12 import sys
12 import sys
13 from unittest import TestCase
13 from unittest import TestCase
14
14
15 import nose.tools as nt
15 import nose.tools as nt
16 from nose import SkipTest
16 from nose import SkipTest
17
17
18 from IPython.utils.traitlets import (
18 from IPython.utils.traitlets import (
19 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
19 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
21 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 ObjectName, DottedObjectName, CRegExp, link
22 ObjectName, DottedObjectName, CRegExp, link, EventfulList, EventfulDict
23 )
23 )
24 from IPython.utils import py3compat
24 from IPython.utils import py3compat
25 from IPython.testing.decorators import skipif
25 from IPython.testing.decorators import skipif
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Helper classes for testing
28 # Helper classes for testing
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31
31
32 class HasTraitsStub(HasTraits):
32 class HasTraitsStub(HasTraits):
33
33
34 def _notify_trait(self, name, old, new):
34 def _notify_trait(self, name, old, new):
35 self._notify_name = name
35 self._notify_name = name
36 self._notify_old = old
36 self._notify_old = old
37 self._notify_new = new
37 self._notify_new = new
38
38
39
39
40 #-----------------------------------------------------------------------------
40 #-----------------------------------------------------------------------------
41 # Test classes
41 # Test classes
42 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
43
43
44
44
45 class TestTraitType(TestCase):
45 class TestTraitType(TestCase):
46
46
47 def test_get_undefined(self):
47 def test_get_undefined(self):
48 class A(HasTraits):
48 class A(HasTraits):
49 a = TraitType
49 a = TraitType
50 a = A()
50 a = A()
51 self.assertEqual(a.a, Undefined)
51 self.assertEqual(a.a, Undefined)
52
52
53 def test_set(self):
53 def test_set(self):
54 class A(HasTraitsStub):
54 class A(HasTraitsStub):
55 a = TraitType
55 a = TraitType
56
56
57 a = A()
57 a = A()
58 a.a = 10
58 a.a = 10
59 self.assertEqual(a.a, 10)
59 self.assertEqual(a.a, 10)
60 self.assertEqual(a._notify_name, 'a')
60 self.assertEqual(a._notify_name, 'a')
61 self.assertEqual(a._notify_old, Undefined)
61 self.assertEqual(a._notify_old, Undefined)
62 self.assertEqual(a._notify_new, 10)
62 self.assertEqual(a._notify_new, 10)
63
63
64 def test_validate(self):
64 def test_validate(self):
65 class MyTT(TraitType):
65 class MyTT(TraitType):
66 def validate(self, inst, value):
66 def validate(self, inst, value):
67 return -1
67 return -1
68 class A(HasTraitsStub):
68 class A(HasTraitsStub):
69 tt = MyTT
69 tt = MyTT
70
70
71 a = A()
71 a = A()
72 a.tt = 10
72 a.tt = 10
73 self.assertEqual(a.tt, -1)
73 self.assertEqual(a.tt, -1)
74
74
75 def test_default_validate(self):
75 def test_default_validate(self):
76 class MyIntTT(TraitType):
76 class MyIntTT(TraitType):
77 def validate(self, obj, value):
77 def validate(self, obj, value):
78 if isinstance(value, int):
78 if isinstance(value, int):
79 return value
79 return value
80 self.error(obj, value)
80 self.error(obj, value)
81 class A(HasTraits):
81 class A(HasTraits):
82 tt = MyIntTT(10)
82 tt = MyIntTT(10)
83 a = A()
83 a = A()
84 self.assertEqual(a.tt, 10)
84 self.assertEqual(a.tt, 10)
85
85
86 # Defaults are validated when the HasTraits is instantiated
86 # Defaults are validated when the HasTraits is instantiated
87 class B(HasTraits):
87 class B(HasTraits):
88 tt = MyIntTT('bad default')
88 tt = MyIntTT('bad default')
89 self.assertRaises(TraitError, B)
89 self.assertRaises(TraitError, B)
90
90
91 def test_is_valid_for(self):
91 def test_is_valid_for(self):
92 class MyTT(TraitType):
92 class MyTT(TraitType):
93 def is_valid_for(self, value):
93 def is_valid_for(self, value):
94 return True
94 return True
95 class A(HasTraits):
95 class A(HasTraits):
96 tt = MyTT
96 tt = MyTT
97
97
98 a = A()
98 a = A()
99 a.tt = 10
99 a.tt = 10
100 self.assertEqual(a.tt, 10)
100 self.assertEqual(a.tt, 10)
101
101
102 def test_value_for(self):
102 def test_value_for(self):
103 class MyTT(TraitType):
103 class MyTT(TraitType):
104 def value_for(self, value):
104 def value_for(self, value):
105 return 20
105 return 20
106 class A(HasTraits):
106 class A(HasTraits):
107 tt = MyTT
107 tt = MyTT
108
108
109 a = A()
109 a = A()
110 a.tt = 10
110 a.tt = 10
111 self.assertEqual(a.tt, 20)
111 self.assertEqual(a.tt, 20)
112
112
113 def test_info(self):
113 def test_info(self):
114 class A(HasTraits):
114 class A(HasTraits):
115 tt = TraitType
115 tt = TraitType
116 a = A()
116 a = A()
117 self.assertEqual(A.tt.info(), 'any value')
117 self.assertEqual(A.tt.info(), 'any value')
118
118
119 def test_error(self):
119 def test_error(self):
120 class A(HasTraits):
120 class A(HasTraits):
121 tt = TraitType
121 tt = TraitType
122 a = A()
122 a = A()
123 self.assertRaises(TraitError, A.tt.error, a, 10)
123 self.assertRaises(TraitError, A.tt.error, a, 10)
124
124
125 def test_dynamic_initializer(self):
125 def test_dynamic_initializer(self):
126 class A(HasTraits):
126 class A(HasTraits):
127 x = Int(10)
127 x = Int(10)
128 def _x_default(self):
128 def _x_default(self):
129 return 11
129 return 11
130 class B(A):
130 class B(A):
131 x = Int(20)
131 x = Int(20)
132 class C(A):
132 class C(A):
133 def _x_default(self):
133 def _x_default(self):
134 return 21
134 return 21
135
135
136 a = A()
136 a = A()
137 self.assertEqual(a._trait_values, {})
137 self.assertEqual(a._trait_values, {})
138 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
138 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
139 self.assertEqual(a.x, 11)
139 self.assertEqual(a.x, 11)
140 self.assertEqual(a._trait_values, {'x': 11})
140 self.assertEqual(a._trait_values, {'x': 11})
141 b = B()
141 b = B()
142 self.assertEqual(b._trait_values, {'x': 20})
142 self.assertEqual(b._trait_values, {'x': 20})
143 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
143 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
144 self.assertEqual(b.x, 20)
144 self.assertEqual(b.x, 20)
145 c = C()
145 c = C()
146 self.assertEqual(c._trait_values, {})
146 self.assertEqual(c._trait_values, {})
147 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
147 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
148 self.assertEqual(c.x, 21)
148 self.assertEqual(c.x, 21)
149 self.assertEqual(c._trait_values, {'x': 21})
149 self.assertEqual(c._trait_values, {'x': 21})
150 # Ensure that the base class remains unmolested when the _default
150 # Ensure that the base class remains unmolested when the _default
151 # initializer gets overridden in a subclass.
151 # initializer gets overridden in a subclass.
152 a = A()
152 a = A()
153 c = C()
153 c = C()
154 self.assertEqual(a._trait_values, {})
154 self.assertEqual(a._trait_values, {})
155 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
155 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
156 self.assertEqual(a.x, 11)
156 self.assertEqual(a.x, 11)
157 self.assertEqual(a._trait_values, {'x': 11})
157 self.assertEqual(a._trait_values, {'x': 11})
158
158
159
159
160
160
161 class TestHasTraitsMeta(TestCase):
161 class TestHasTraitsMeta(TestCase):
162
162
163 def test_metaclass(self):
163 def test_metaclass(self):
164 self.assertEqual(type(HasTraits), MetaHasTraits)
164 self.assertEqual(type(HasTraits), MetaHasTraits)
165
165
166 class A(HasTraits):
166 class A(HasTraits):
167 a = Int
167 a = Int
168
168
169 a = A()
169 a = A()
170 self.assertEqual(type(a.__class__), MetaHasTraits)
170 self.assertEqual(type(a.__class__), MetaHasTraits)
171 self.assertEqual(a.a,0)
171 self.assertEqual(a.a,0)
172 a.a = 10
172 a.a = 10
173 self.assertEqual(a.a,10)
173 self.assertEqual(a.a,10)
174
174
175 class B(HasTraits):
175 class B(HasTraits):
176 b = Int()
176 b = Int()
177
177
178 b = B()
178 b = B()
179 self.assertEqual(b.b,0)
179 self.assertEqual(b.b,0)
180 b.b = 10
180 b.b = 10
181 self.assertEqual(b.b,10)
181 self.assertEqual(b.b,10)
182
182
183 class C(HasTraits):
183 class C(HasTraits):
184 c = Int(30)
184 c = Int(30)
185
185
186 c = C()
186 c = C()
187 self.assertEqual(c.c,30)
187 self.assertEqual(c.c,30)
188 c.c = 10
188 c.c = 10
189 self.assertEqual(c.c,10)
189 self.assertEqual(c.c,10)
190
190
191 def test_this_class(self):
191 def test_this_class(self):
192 class A(HasTraits):
192 class A(HasTraits):
193 t = This()
193 t = This()
194 tt = This()
194 tt = This()
195 class B(A):
195 class B(A):
196 tt = This()
196 tt = This()
197 ttt = This()
197 ttt = This()
198 self.assertEqual(A.t.this_class, A)
198 self.assertEqual(A.t.this_class, A)
199 self.assertEqual(B.t.this_class, A)
199 self.assertEqual(B.t.this_class, A)
200 self.assertEqual(B.tt.this_class, B)
200 self.assertEqual(B.tt.this_class, B)
201 self.assertEqual(B.ttt.this_class, B)
201 self.assertEqual(B.ttt.this_class, B)
202
202
203 class TestHasTraitsNotify(TestCase):
203 class TestHasTraitsNotify(TestCase):
204
204
205 def setUp(self):
205 def setUp(self):
206 self._notify1 = []
206 self._notify1 = []
207 self._notify2 = []
207 self._notify2 = []
208
208
209 def notify1(self, name, old, new):
209 def notify1(self, name, old, new):
210 self._notify1.append((name, old, new))
210 self._notify1.append((name, old, new))
211
211
212 def notify2(self, name, old, new):
212 def notify2(self, name, old, new):
213 self._notify2.append((name, old, new))
213 self._notify2.append((name, old, new))
214
214
215 def test_notify_all(self):
215 def test_notify_all(self):
216
216
217 class A(HasTraits):
217 class A(HasTraits):
218 a = Int
218 a = Int
219 b = Float
219 b = Float
220
220
221 a = A()
221 a = A()
222 a.on_trait_change(self.notify1)
222 a.on_trait_change(self.notify1)
223 a.a = 0
223 a.a = 0
224 self.assertEqual(len(self._notify1),0)
224 self.assertEqual(len(self._notify1),0)
225 a.b = 0.0
225 a.b = 0.0
226 self.assertEqual(len(self._notify1),0)
226 self.assertEqual(len(self._notify1),0)
227 a.a = 10
227 a.a = 10
228 self.assertTrue(('a',0,10) in self._notify1)
228 self.assertTrue(('a',0,10) in self._notify1)
229 a.b = 10.0
229 a.b = 10.0
230 self.assertTrue(('b',0.0,10.0) in self._notify1)
230 self.assertTrue(('b',0.0,10.0) in self._notify1)
231 self.assertRaises(TraitError,setattr,a,'a','bad string')
231 self.assertRaises(TraitError,setattr,a,'a','bad string')
232 self.assertRaises(TraitError,setattr,a,'b','bad string')
232 self.assertRaises(TraitError,setattr,a,'b','bad string')
233 self._notify1 = []
233 self._notify1 = []
234 a.on_trait_change(self.notify1,remove=True)
234 a.on_trait_change(self.notify1,remove=True)
235 a.a = 20
235 a.a = 20
236 a.b = 20.0
236 a.b = 20.0
237 self.assertEqual(len(self._notify1),0)
237 self.assertEqual(len(self._notify1),0)
238
238
239 def test_notify_one(self):
239 def test_notify_one(self):
240
240
241 class A(HasTraits):
241 class A(HasTraits):
242 a = Int
242 a = Int
243 b = Float
243 b = Float
244
244
245 a = A()
245 a = A()
246 a.on_trait_change(self.notify1, 'a')
246 a.on_trait_change(self.notify1, 'a')
247 a.a = 0
247 a.a = 0
248 self.assertEqual(len(self._notify1),0)
248 self.assertEqual(len(self._notify1),0)
249 a.a = 10
249 a.a = 10
250 self.assertTrue(('a',0,10) in self._notify1)
250 self.assertTrue(('a',0,10) in self._notify1)
251 self.assertRaises(TraitError,setattr,a,'a','bad string')
251 self.assertRaises(TraitError,setattr,a,'a','bad string')
252
252
253 def test_subclass(self):
253 def test_subclass(self):
254
254
255 class A(HasTraits):
255 class A(HasTraits):
256 a = Int
256 a = Int
257
257
258 class B(A):
258 class B(A):
259 b = Float
259 b = Float
260
260
261 b = B()
261 b = B()
262 self.assertEqual(b.a,0)
262 self.assertEqual(b.a,0)
263 self.assertEqual(b.b,0.0)
263 self.assertEqual(b.b,0.0)
264 b.a = 100
264 b.a = 100
265 b.b = 100.0
265 b.b = 100.0
266 self.assertEqual(b.a,100)
266 self.assertEqual(b.a,100)
267 self.assertEqual(b.b,100.0)
267 self.assertEqual(b.b,100.0)
268
268
269 def test_notify_subclass(self):
269 def test_notify_subclass(self):
270
270
271 class A(HasTraits):
271 class A(HasTraits):
272 a = Int
272 a = Int
273
273
274 class B(A):
274 class B(A):
275 b = Float
275 b = Float
276
276
277 b = B()
277 b = B()
278 b.on_trait_change(self.notify1, 'a')
278 b.on_trait_change(self.notify1, 'a')
279 b.on_trait_change(self.notify2, 'b')
279 b.on_trait_change(self.notify2, 'b')
280 b.a = 0
280 b.a = 0
281 b.b = 0.0
281 b.b = 0.0
282 self.assertEqual(len(self._notify1),0)
282 self.assertEqual(len(self._notify1),0)
283 self.assertEqual(len(self._notify2),0)
283 self.assertEqual(len(self._notify2),0)
284 b.a = 10
284 b.a = 10
285 b.b = 10.0
285 b.b = 10.0
286 self.assertTrue(('a',0,10) in self._notify1)
286 self.assertTrue(('a',0,10) in self._notify1)
287 self.assertTrue(('b',0.0,10.0) in self._notify2)
287 self.assertTrue(('b',0.0,10.0) in self._notify2)
288
288
289 def test_static_notify(self):
289 def test_static_notify(self):
290
290
291 class A(HasTraits):
291 class A(HasTraits):
292 a = Int
292 a = Int
293 _notify1 = []
293 _notify1 = []
294 def _a_changed(self, name, old, new):
294 def _a_changed(self, name, old, new):
295 self._notify1.append((name, old, new))
295 self._notify1.append((name, old, new))
296
296
297 a = A()
297 a = A()
298 a.a = 0
298 a.a = 0
299 # This is broken!!!
299 # This is broken!!!
300 self.assertEqual(len(a._notify1),0)
300 self.assertEqual(len(a._notify1),0)
301 a.a = 10
301 a.a = 10
302 self.assertTrue(('a',0,10) in a._notify1)
302 self.assertTrue(('a',0,10) in a._notify1)
303
303
304 class B(A):
304 class B(A):
305 b = Float
305 b = Float
306 _notify2 = []
306 _notify2 = []
307 def _b_changed(self, name, old, new):
307 def _b_changed(self, name, old, new):
308 self._notify2.append((name, old, new))
308 self._notify2.append((name, old, new))
309
309
310 b = B()
310 b = B()
311 b.a = 10
311 b.a = 10
312 b.b = 10.0
312 b.b = 10.0
313 self.assertTrue(('a',0,10) in b._notify1)
313 self.assertTrue(('a',0,10) in b._notify1)
314 self.assertTrue(('b',0.0,10.0) in b._notify2)
314 self.assertTrue(('b',0.0,10.0) in b._notify2)
315
315
316 def test_notify_args(self):
316 def test_notify_args(self):
317
317
318 def callback0():
318 def callback0():
319 self.cb = ()
319 self.cb = ()
320 def callback1(name):
320 def callback1(name):
321 self.cb = (name,)
321 self.cb = (name,)
322 def callback2(name, new):
322 def callback2(name, new):
323 self.cb = (name, new)
323 self.cb = (name, new)
324 def callback3(name, old, new):
324 def callback3(name, old, new):
325 self.cb = (name, old, new)
325 self.cb = (name, old, new)
326
326
327 class A(HasTraits):
327 class A(HasTraits):
328 a = Int
328 a = Int
329
329
330 a = A()
330 a = A()
331 a.on_trait_change(callback0, 'a')
331 a.on_trait_change(callback0, 'a')
332 a.a = 10
332 a.a = 10
333 self.assertEqual(self.cb,())
333 self.assertEqual(self.cb,())
334 a.on_trait_change(callback0, 'a', remove=True)
334 a.on_trait_change(callback0, 'a', remove=True)
335
335
336 a.on_trait_change(callback1, 'a')
336 a.on_trait_change(callback1, 'a')
337 a.a = 100
337 a.a = 100
338 self.assertEqual(self.cb,('a',))
338 self.assertEqual(self.cb,('a',))
339 a.on_trait_change(callback1, 'a', remove=True)
339 a.on_trait_change(callback1, 'a', remove=True)
340
340
341 a.on_trait_change(callback2, 'a')
341 a.on_trait_change(callback2, 'a')
342 a.a = 1000
342 a.a = 1000
343 self.assertEqual(self.cb,('a',1000))
343 self.assertEqual(self.cb,('a',1000))
344 a.on_trait_change(callback2, 'a', remove=True)
344 a.on_trait_change(callback2, 'a', remove=True)
345
345
346 a.on_trait_change(callback3, 'a')
346 a.on_trait_change(callback3, 'a')
347 a.a = 10000
347 a.a = 10000
348 self.assertEqual(self.cb,('a',1000,10000))
348 self.assertEqual(self.cb,('a',1000,10000))
349 a.on_trait_change(callback3, 'a', remove=True)
349 a.on_trait_change(callback3, 'a', remove=True)
350
350
351 self.assertEqual(len(a._trait_notifiers['a']),0)
351 self.assertEqual(len(a._trait_notifiers['a']),0)
352
352
353 def test_notify_only_once(self):
353 def test_notify_only_once(self):
354
354
355 class A(HasTraits):
355 class A(HasTraits):
356 listen_to = ['a']
356 listen_to = ['a']
357
357
358 a = Int(0)
358 a = Int(0)
359 b = 0
359 b = 0
360
360
361 def __init__(self, **kwargs):
361 def __init__(self, **kwargs):
362 super(A, self).__init__(**kwargs)
362 super(A, self).__init__(**kwargs)
363 self.on_trait_change(self.listener1, ['a'])
363 self.on_trait_change(self.listener1, ['a'])
364
364
365 def listener1(self, name, old, new):
365 def listener1(self, name, old, new):
366 self.b += 1
366 self.b += 1
367
367
368 class B(A):
368 class B(A):
369
369
370 c = 0
370 c = 0
371 d = 0
371 d = 0
372
372
373 def __init__(self, **kwargs):
373 def __init__(self, **kwargs):
374 super(B, self).__init__(**kwargs)
374 super(B, self).__init__(**kwargs)
375 self.on_trait_change(self.listener2)
375 self.on_trait_change(self.listener2)
376
376
377 def listener2(self, name, old, new):
377 def listener2(self, name, old, new):
378 self.c += 1
378 self.c += 1
379
379
380 def _a_changed(self, name, old, new):
380 def _a_changed(self, name, old, new):
381 self.d += 1
381 self.d += 1
382
382
383 b = B()
383 b = B()
384 b.a += 1
384 b.a += 1
385 self.assertEqual(b.b, b.c)
385 self.assertEqual(b.b, b.c)
386 self.assertEqual(b.b, b.d)
386 self.assertEqual(b.b, b.d)
387 b.a += 1
387 b.a += 1
388 self.assertEqual(b.b, b.c)
388 self.assertEqual(b.b, b.c)
389 self.assertEqual(b.b, b.d)
389 self.assertEqual(b.b, b.d)
390
390
391
391
392 class TestHasTraits(TestCase):
392 class TestHasTraits(TestCase):
393
393
394 def test_trait_names(self):
394 def test_trait_names(self):
395 class A(HasTraits):
395 class A(HasTraits):
396 i = Int
396 i = Int
397 f = Float
397 f = Float
398 a = A()
398 a = A()
399 self.assertEqual(sorted(a.trait_names()),['f','i'])
399 self.assertEqual(sorted(a.trait_names()),['f','i'])
400 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
400 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
401
401
402 def test_trait_metadata(self):
402 def test_trait_metadata(self):
403 class A(HasTraits):
403 class A(HasTraits):
404 i = Int(config_key='MY_VALUE')
404 i = Int(config_key='MY_VALUE')
405 a = A()
405 a = A()
406 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
406 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
407
407
408 def test_traits(self):
408 def test_traits(self):
409 class A(HasTraits):
409 class A(HasTraits):
410 i = Int
410 i = Int
411 f = Float
411 f = Float
412 a = A()
412 a = A()
413 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
413 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
414 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
414 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
415
415
416 def test_traits_metadata(self):
416 def test_traits_metadata(self):
417 class A(HasTraits):
417 class A(HasTraits):
418 i = Int(config_key='VALUE1', other_thing='VALUE2')
418 i = Int(config_key='VALUE1', other_thing='VALUE2')
419 f = Float(config_key='VALUE3', other_thing='VALUE2')
419 f = Float(config_key='VALUE3', other_thing='VALUE2')
420 j = Int(0)
420 j = Int(0)
421 a = A()
421 a = A()
422 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
422 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
423 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
423 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
424 self.assertEqual(traits, dict(i=A.i))
424 self.assertEqual(traits, dict(i=A.i))
425
425
426 # This passes, but it shouldn't because I am replicating a bug in
426 # This passes, but it shouldn't because I am replicating a bug in
427 # traits.
427 # traits.
428 traits = a.traits(config_key=lambda v: True)
428 traits = a.traits(config_key=lambda v: True)
429 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
429 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
430
430
431 def test_init(self):
431 def test_init(self):
432 class A(HasTraits):
432 class A(HasTraits):
433 i = Int()
433 i = Int()
434 x = Float()
434 x = Float()
435 a = A(i=1, x=10.0)
435 a = A(i=1, x=10.0)
436 self.assertEqual(a.i, 1)
436 self.assertEqual(a.i, 1)
437 self.assertEqual(a.x, 10.0)
437 self.assertEqual(a.x, 10.0)
438
438
439 def test_positional_args(self):
439 def test_positional_args(self):
440 class A(HasTraits):
440 class A(HasTraits):
441 i = Int(0)
441 i = Int(0)
442 def __init__(self, i):
442 def __init__(self, i):
443 super(A, self).__init__()
443 super(A, self).__init__()
444 self.i = i
444 self.i = i
445
445
446 a = A(5)
446 a = A(5)
447 self.assertEqual(a.i, 5)
447 self.assertEqual(a.i, 5)
448 # should raise TypeError if no positional arg given
448 # should raise TypeError if no positional arg given
449 self.assertRaises(TypeError, A)
449 self.assertRaises(TypeError, A)
450
450
451 #-----------------------------------------------------------------------------
451 #-----------------------------------------------------------------------------
452 # Tests for specific trait types
452 # Tests for specific trait types
453 #-----------------------------------------------------------------------------
453 #-----------------------------------------------------------------------------
454
454
455
455
456 class TestType(TestCase):
456 class TestType(TestCase):
457
457
458 def test_default(self):
458 def test_default(self):
459
459
460 class B(object): pass
460 class B(object): pass
461 class A(HasTraits):
461 class A(HasTraits):
462 klass = Type
462 klass = Type
463
463
464 a = A()
464 a = A()
465 self.assertEqual(a.klass, None)
465 self.assertEqual(a.klass, None)
466
466
467 a.klass = B
467 a.klass = B
468 self.assertEqual(a.klass, B)
468 self.assertEqual(a.klass, B)
469 self.assertRaises(TraitError, setattr, a, 'klass', 10)
469 self.assertRaises(TraitError, setattr, a, 'klass', 10)
470
470
471 def test_value(self):
471 def test_value(self):
472
472
473 class B(object): pass
473 class B(object): pass
474 class C(object): pass
474 class C(object): pass
475 class A(HasTraits):
475 class A(HasTraits):
476 klass = Type(B)
476 klass = Type(B)
477
477
478 a = A()
478 a = A()
479 self.assertEqual(a.klass, B)
479 self.assertEqual(a.klass, B)
480 self.assertRaises(TraitError, setattr, a, 'klass', C)
480 self.assertRaises(TraitError, setattr, a, 'klass', C)
481 self.assertRaises(TraitError, setattr, a, 'klass', object)
481 self.assertRaises(TraitError, setattr, a, 'klass', object)
482 a.klass = B
482 a.klass = B
483
483
484 def test_allow_none(self):
484 def test_allow_none(self):
485
485
486 class B(object): pass
486 class B(object): pass
487 class C(B): pass
487 class C(B): pass
488 class A(HasTraits):
488 class A(HasTraits):
489 klass = Type(B, allow_none=False)
489 klass = Type(B, allow_none=False)
490
490
491 a = A()
491 a = A()
492 self.assertEqual(a.klass, B)
492 self.assertEqual(a.klass, B)
493 self.assertRaises(TraitError, setattr, a, 'klass', None)
493 self.assertRaises(TraitError, setattr, a, 'klass', None)
494 a.klass = C
494 a.klass = C
495 self.assertEqual(a.klass, C)
495 self.assertEqual(a.klass, C)
496
496
497 def test_validate_klass(self):
497 def test_validate_klass(self):
498
498
499 class A(HasTraits):
499 class A(HasTraits):
500 klass = Type('no strings allowed')
500 klass = Type('no strings allowed')
501
501
502 self.assertRaises(ImportError, A)
502 self.assertRaises(ImportError, A)
503
503
504 class A(HasTraits):
504 class A(HasTraits):
505 klass = Type('rub.adub.Duck')
505 klass = Type('rub.adub.Duck')
506
506
507 self.assertRaises(ImportError, A)
507 self.assertRaises(ImportError, A)
508
508
509 def test_validate_default(self):
509 def test_validate_default(self):
510
510
511 class B(object): pass
511 class B(object): pass
512 class A(HasTraits):
512 class A(HasTraits):
513 klass = Type('bad default', B)
513 klass = Type('bad default', B)
514
514
515 self.assertRaises(ImportError, A)
515 self.assertRaises(ImportError, A)
516
516
517 class C(HasTraits):
517 class C(HasTraits):
518 klass = Type(None, B, allow_none=False)
518 klass = Type(None, B, allow_none=False)
519
519
520 self.assertRaises(TraitError, C)
520 self.assertRaises(TraitError, C)
521
521
522 def test_str_klass(self):
522 def test_str_klass(self):
523
523
524 class A(HasTraits):
524 class A(HasTraits):
525 klass = Type('IPython.utils.ipstruct.Struct')
525 klass = Type('IPython.utils.ipstruct.Struct')
526
526
527 from IPython.utils.ipstruct import Struct
527 from IPython.utils.ipstruct import Struct
528 a = A()
528 a = A()
529 a.klass = Struct
529 a.klass = Struct
530 self.assertEqual(a.klass, Struct)
530 self.assertEqual(a.klass, Struct)
531
531
532 self.assertRaises(TraitError, setattr, a, 'klass', 10)
532 self.assertRaises(TraitError, setattr, a, 'klass', 10)
533
533
534 def test_set_str_klass(self):
534 def test_set_str_klass(self):
535
535
536 class A(HasTraits):
536 class A(HasTraits):
537 klass = Type()
537 klass = Type()
538
538
539 a = A(klass='IPython.utils.ipstruct.Struct')
539 a = A(klass='IPython.utils.ipstruct.Struct')
540 from IPython.utils.ipstruct import Struct
540 from IPython.utils.ipstruct import Struct
541 self.assertEqual(a.klass, Struct)
541 self.assertEqual(a.klass, Struct)
542
542
543 class TestInstance(TestCase):
543 class TestInstance(TestCase):
544
544
545 def test_basic(self):
545 def test_basic(self):
546 class Foo(object): pass
546 class Foo(object): pass
547 class Bar(Foo): pass
547 class Bar(Foo): pass
548 class Bah(object): pass
548 class Bah(object): pass
549
549
550 class A(HasTraits):
550 class A(HasTraits):
551 inst = Instance(Foo)
551 inst = Instance(Foo)
552
552
553 a = A()
553 a = A()
554 self.assertTrue(a.inst is None)
554 self.assertTrue(a.inst is None)
555 a.inst = Foo()
555 a.inst = Foo()
556 self.assertTrue(isinstance(a.inst, Foo))
556 self.assertTrue(isinstance(a.inst, Foo))
557 a.inst = Bar()
557 a.inst = Bar()
558 self.assertTrue(isinstance(a.inst, Foo))
558 self.assertTrue(isinstance(a.inst, Foo))
559 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
559 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
561 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
561 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
562
562
563 def test_default_klass(self):
563 def test_default_klass(self):
564 class Foo(object): pass
564 class Foo(object): pass
565 class Bar(Foo): pass
565 class Bar(Foo): pass
566 class Bah(object): pass
566 class Bah(object): pass
567
567
568 class FooInstance(Instance):
568 class FooInstance(Instance):
569 klass = Foo
569 klass = Foo
570
570
571 class A(HasTraits):
571 class A(HasTraits):
572 inst = FooInstance()
572 inst = FooInstance()
573
573
574 a = A()
574 a = A()
575 self.assertTrue(a.inst is None)
575 self.assertTrue(a.inst is None)
576 a.inst = Foo()
576 a.inst = Foo()
577 self.assertTrue(isinstance(a.inst, Foo))
577 self.assertTrue(isinstance(a.inst, Foo))
578 a.inst = Bar()
578 a.inst = Bar()
579 self.assertTrue(isinstance(a.inst, Foo))
579 self.assertTrue(isinstance(a.inst, Foo))
580 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
580 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
581 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
581 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
582 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
582 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
583
583
584 def test_unique_default_value(self):
584 def test_unique_default_value(self):
585 class Foo(object): pass
585 class Foo(object): pass
586 class A(HasTraits):
586 class A(HasTraits):
587 inst = Instance(Foo,(),{})
587 inst = Instance(Foo,(),{})
588
588
589 a = A()
589 a = A()
590 b = A()
590 b = A()
591 self.assertTrue(a.inst is not b.inst)
591 self.assertTrue(a.inst is not b.inst)
592
592
593 def test_args_kw(self):
593 def test_args_kw(self):
594 class Foo(object):
594 class Foo(object):
595 def __init__(self, c): self.c = c
595 def __init__(self, c): self.c = c
596 class Bar(object): pass
596 class Bar(object): pass
597 class Bah(object):
597 class Bah(object):
598 def __init__(self, c, d):
598 def __init__(self, c, d):
599 self.c = c; self.d = d
599 self.c = c; self.d = d
600
600
601 class A(HasTraits):
601 class A(HasTraits):
602 inst = Instance(Foo, (10,))
602 inst = Instance(Foo, (10,))
603 a = A()
603 a = A()
604 self.assertEqual(a.inst.c, 10)
604 self.assertEqual(a.inst.c, 10)
605
605
606 class B(HasTraits):
606 class B(HasTraits):
607 inst = Instance(Bah, args=(10,), kw=dict(d=20))
607 inst = Instance(Bah, args=(10,), kw=dict(d=20))
608 b = B()
608 b = B()
609 self.assertEqual(b.inst.c, 10)
609 self.assertEqual(b.inst.c, 10)
610 self.assertEqual(b.inst.d, 20)
610 self.assertEqual(b.inst.d, 20)
611
611
612 class C(HasTraits):
612 class C(HasTraits):
613 inst = Instance(Foo)
613 inst = Instance(Foo)
614 c = C()
614 c = C()
615 self.assertTrue(c.inst is None)
615 self.assertTrue(c.inst is None)
616
616
617 def test_bad_default(self):
617 def test_bad_default(self):
618 class Foo(object): pass
618 class Foo(object): pass
619
619
620 class A(HasTraits):
620 class A(HasTraits):
621 inst = Instance(Foo, allow_none=False)
621 inst = Instance(Foo, allow_none=False)
622
622
623 self.assertRaises(TraitError, A)
623 self.assertRaises(TraitError, A)
624
624
625 def test_instance(self):
625 def test_instance(self):
626 class Foo(object): pass
626 class Foo(object): pass
627
627
628 def inner():
628 def inner():
629 class A(HasTraits):
629 class A(HasTraits):
630 inst = Instance(Foo())
630 inst = Instance(Foo())
631
631
632 self.assertRaises(TraitError, inner)
632 self.assertRaises(TraitError, inner)
633
633
634
634
635 class TestThis(TestCase):
635 class TestThis(TestCase):
636
636
637 def test_this_class(self):
637 def test_this_class(self):
638 class Foo(HasTraits):
638 class Foo(HasTraits):
639 this = This
639 this = This
640
640
641 f = Foo()
641 f = Foo()
642 self.assertEqual(f.this, None)
642 self.assertEqual(f.this, None)
643 g = Foo()
643 g = Foo()
644 f.this = g
644 f.this = g
645 self.assertEqual(f.this, g)
645 self.assertEqual(f.this, g)
646 self.assertRaises(TraitError, setattr, f, 'this', 10)
646 self.assertRaises(TraitError, setattr, f, 'this', 10)
647
647
648 def test_this_inst(self):
648 def test_this_inst(self):
649 class Foo(HasTraits):
649 class Foo(HasTraits):
650 this = This()
650 this = This()
651
651
652 f = Foo()
652 f = Foo()
653 f.this = Foo()
653 f.this = Foo()
654 self.assertTrue(isinstance(f.this, Foo))
654 self.assertTrue(isinstance(f.this, Foo))
655
655
656 def test_subclass(self):
656 def test_subclass(self):
657 class Foo(HasTraits):
657 class Foo(HasTraits):
658 t = This()
658 t = This()
659 class Bar(Foo):
659 class Bar(Foo):
660 pass
660 pass
661 f = Foo()
661 f = Foo()
662 b = Bar()
662 b = Bar()
663 f.t = b
663 f.t = b
664 b.t = f
664 b.t = f
665 self.assertEqual(f.t, b)
665 self.assertEqual(f.t, b)
666 self.assertEqual(b.t, f)
666 self.assertEqual(b.t, f)
667
667
668 def test_subclass_override(self):
668 def test_subclass_override(self):
669 class Foo(HasTraits):
669 class Foo(HasTraits):
670 t = This()
670 t = This()
671 class Bar(Foo):
671 class Bar(Foo):
672 t = This()
672 t = This()
673 f = Foo()
673 f = Foo()
674 b = Bar()
674 b = Bar()
675 f.t = b
675 f.t = b
676 self.assertEqual(f.t, b)
676 self.assertEqual(f.t, b)
677 self.assertRaises(TraitError, setattr, b, 't', f)
677 self.assertRaises(TraitError, setattr, b, 't', f)
678
678
679 class TraitTestBase(TestCase):
679 class TraitTestBase(TestCase):
680 """A best testing class for basic trait types."""
680 """A best testing class for basic trait types."""
681
681
682 def assign(self, value):
682 def assign(self, value):
683 self.obj.value = value
683 self.obj.value = value
684
684
685 def coerce(self, value):
685 def coerce(self, value):
686 return value
686 return value
687
687
688 def test_good_values(self):
688 def test_good_values(self):
689 if hasattr(self, '_good_values'):
689 if hasattr(self, '_good_values'):
690 for value in self._good_values:
690 for value in self._good_values:
691 self.assign(value)
691 self.assign(value)
692 self.assertEqual(self.obj.value, self.coerce(value))
692 self.assertEqual(self.obj.value, self.coerce(value))
693
693
694 def test_bad_values(self):
694 def test_bad_values(self):
695 if hasattr(self, '_bad_values'):
695 if hasattr(self, '_bad_values'):
696 for value in self._bad_values:
696 for value in self._bad_values:
697 try:
697 try:
698 self.assertRaises(TraitError, self.assign, value)
698 self.assertRaises(TraitError, self.assign, value)
699 except AssertionError:
699 except AssertionError:
700 assert False, value
700 assert False, value
701
701
702 def test_default_value(self):
702 def test_default_value(self):
703 if hasattr(self, '_default_value'):
703 if hasattr(self, '_default_value'):
704 self.assertEqual(self._default_value, self.obj.value)
704 self.assertEqual(self._default_value, self.obj.value)
705
705
706 def test_allow_none(self):
706 def test_allow_none(self):
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
708 None in self._bad_values):
708 None in self._bad_values):
709 trait=self.obj.traits()['value']
709 trait=self.obj.traits()['value']
710 try:
710 try:
711 trait.allow_none = True
711 trait.allow_none = True
712 self._bad_values.remove(None)
712 self._bad_values.remove(None)
713 #skip coerce. Allow None casts None to None.
713 #skip coerce. Allow None casts None to None.
714 self.assign(None)
714 self.assign(None)
715 self.assertEqual(self.obj.value,None)
715 self.assertEqual(self.obj.value,None)
716 self.test_good_values()
716 self.test_good_values()
717 self.test_bad_values()
717 self.test_bad_values()
718 finally:
718 finally:
719 #tear down
719 #tear down
720 trait.allow_none = False
720 trait.allow_none = False
721 self._bad_values.append(None)
721 self._bad_values.append(None)
722
722
723 def tearDown(self):
723 def tearDown(self):
724 # restore default value after tests, if set
724 # restore default value after tests, if set
725 if hasattr(self, '_default_value'):
725 if hasattr(self, '_default_value'):
726 self.obj.value = self._default_value
726 self.obj.value = self._default_value
727
727
728
728
729 class AnyTrait(HasTraits):
729 class AnyTrait(HasTraits):
730
730
731 value = Any
731 value = Any
732
732
733 class AnyTraitTest(TraitTestBase):
733 class AnyTraitTest(TraitTestBase):
734
734
735 obj = AnyTrait()
735 obj = AnyTrait()
736
736
737 _default_value = None
737 _default_value = None
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
739 _bad_values = []
739 _bad_values = []
740
740
741
741
742 class IntTrait(HasTraits):
742 class IntTrait(HasTraits):
743
743
744 value = Int(99)
744 value = Int(99)
745
745
746 class TestInt(TraitTestBase):
746 class TestInt(TraitTestBase):
747
747
748 obj = IntTrait()
748 obj = IntTrait()
749 _default_value = 99
749 _default_value = 99
750 _good_values = [10, -10]
750 _good_values = [10, -10]
751 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
751 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
752 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
752 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
753 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
753 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
754 if not py3compat.PY3:
754 if not py3compat.PY3:
755 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
755 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
756
756
757
757
758 class LongTrait(HasTraits):
758 class LongTrait(HasTraits):
759
759
760 value = Long(99 if py3compat.PY3 else long(99))
760 value = Long(99 if py3compat.PY3 else long(99))
761
761
762 class TestLong(TraitTestBase):
762 class TestLong(TraitTestBase):
763
763
764 obj = LongTrait()
764 obj = LongTrait()
765
765
766 _default_value = 99 if py3compat.PY3 else long(99)
766 _default_value = 99 if py3compat.PY3 else long(99)
767 _good_values = [10, -10]
767 _good_values = [10, -10]
768 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
768 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
769 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
769 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
770 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
770 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
771 u'-10.1']
771 u'-10.1']
772 if not py3compat.PY3:
772 if not py3compat.PY3:
773 # maxint undefined on py3, because int == long
773 # maxint undefined on py3, because int == long
774 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
774 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775 _bad_values.extend([[long(10)], (long(10),)])
775 _bad_values.extend([[long(10)], (long(10),)])
776
776
777 @skipif(py3compat.PY3, "not relevant on py3")
777 @skipif(py3compat.PY3, "not relevant on py3")
778 def test_cast_small(self):
778 def test_cast_small(self):
779 """Long casts ints to long"""
779 """Long casts ints to long"""
780 self.obj.value = 10
780 self.obj.value = 10
781 self.assertEqual(type(self.obj.value), long)
781 self.assertEqual(type(self.obj.value), long)
782
782
783
783
784 class IntegerTrait(HasTraits):
784 class IntegerTrait(HasTraits):
785 value = Integer(1)
785 value = Integer(1)
786
786
787 class TestInteger(TestLong):
787 class TestInteger(TestLong):
788 obj = IntegerTrait()
788 obj = IntegerTrait()
789 _default_value = 1
789 _default_value = 1
790
790
791 def coerce(self, n):
791 def coerce(self, n):
792 return int(n)
792 return int(n)
793
793
794 @skipif(py3compat.PY3, "not relevant on py3")
794 @skipif(py3compat.PY3, "not relevant on py3")
795 def test_cast_small(self):
795 def test_cast_small(self):
796 """Integer casts small longs to int"""
796 """Integer casts small longs to int"""
797 if py3compat.PY3:
797 if py3compat.PY3:
798 raise SkipTest("not relevant on py3")
798 raise SkipTest("not relevant on py3")
799
799
800 self.obj.value = long(100)
800 self.obj.value = long(100)
801 self.assertEqual(type(self.obj.value), int)
801 self.assertEqual(type(self.obj.value), int)
802
802
803
803
804 class FloatTrait(HasTraits):
804 class FloatTrait(HasTraits):
805
805
806 value = Float(99.0)
806 value = Float(99.0)
807
807
808 class TestFloat(TraitTestBase):
808 class TestFloat(TraitTestBase):
809
809
810 obj = FloatTrait()
810 obj = FloatTrait()
811
811
812 _default_value = 99.0
812 _default_value = 99.0
813 _good_values = [10, -10, 10.1, -10.1]
813 _good_values = [10, -10, 10.1, -10.1]
814 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
814 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
815 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
815 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
816 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
816 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
817 if not py3compat.PY3:
817 if not py3compat.PY3:
818 _bad_values.extend([long(10), long(-10)])
818 _bad_values.extend([long(10), long(-10)])
819
819
820
820
821 class ComplexTrait(HasTraits):
821 class ComplexTrait(HasTraits):
822
822
823 value = Complex(99.0-99.0j)
823 value = Complex(99.0-99.0j)
824
824
825 class TestComplex(TraitTestBase):
825 class TestComplex(TraitTestBase):
826
826
827 obj = ComplexTrait()
827 obj = ComplexTrait()
828
828
829 _default_value = 99.0-99.0j
829 _default_value = 99.0-99.0j
830 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
830 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
831 10.1j, 10.1+10.1j, 10.1-10.1j]
831 10.1j, 10.1+10.1j, 10.1-10.1j]
832 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
832 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
833 if not py3compat.PY3:
833 if not py3compat.PY3:
834 _bad_values.extend([long(10), long(-10)])
834 _bad_values.extend([long(10), long(-10)])
835
835
836
836
837 class BytesTrait(HasTraits):
837 class BytesTrait(HasTraits):
838
838
839 value = Bytes(b'string')
839 value = Bytes(b'string')
840
840
841 class TestBytes(TraitTestBase):
841 class TestBytes(TraitTestBase):
842
842
843 obj = BytesTrait()
843 obj = BytesTrait()
844
844
845 _default_value = b'string'
845 _default_value = b'string'
846 _good_values = [b'10', b'-10', b'10L',
846 _good_values = [b'10', b'-10', b'10L',
847 b'-10L', b'10.1', b'-10.1', b'string']
847 b'-10L', b'10.1', b'-10.1', b'string']
848 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
848 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
849 ['ten'],{'ten': 10},(10,), None, u'string']
849 ['ten'],{'ten': 10},(10,), None, u'string']
850 if not py3compat.PY3:
850 if not py3compat.PY3:
851 _bad_values.extend([long(10), long(-10)])
851 _bad_values.extend([long(10), long(-10)])
852
852
853
853
854 class UnicodeTrait(HasTraits):
854 class UnicodeTrait(HasTraits):
855
855
856 value = Unicode(u'unicode')
856 value = Unicode(u'unicode')
857
857
858 class TestUnicode(TraitTestBase):
858 class TestUnicode(TraitTestBase):
859
859
860 obj = UnicodeTrait()
860 obj = UnicodeTrait()
861
861
862 _default_value = u'unicode'
862 _default_value = u'unicode'
863 _good_values = ['10', '-10', '10L', '-10L', '10.1',
863 _good_values = ['10', '-10', '10L', '-10L', '10.1',
864 '-10.1', '', u'', 'string', u'string', u"€"]
864 '-10.1', '', u'', 'string', u'string', u"€"]
865 _bad_values = [10, -10, 10.1, -10.1, 1j,
865 _bad_values = [10, -10, 10.1, -10.1, 1j,
866 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
866 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
867 if not py3compat.PY3:
867 if not py3compat.PY3:
868 _bad_values.extend([long(10), long(-10)])
868 _bad_values.extend([long(10), long(-10)])
869
869
870
870
871 class ObjectNameTrait(HasTraits):
871 class ObjectNameTrait(HasTraits):
872 value = ObjectName("abc")
872 value = ObjectName("abc")
873
873
874 class TestObjectName(TraitTestBase):
874 class TestObjectName(TraitTestBase):
875 obj = ObjectNameTrait()
875 obj = ObjectNameTrait()
876
876
877 _default_value = "abc"
877 _default_value = "abc"
878 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
878 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
879 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
879 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
880 None, object(), object]
880 None, object(), object]
881 if sys.version_info[0] < 3:
881 if sys.version_info[0] < 3:
882 _bad_values.append(u"ΓΎ")
882 _bad_values.append(u"ΓΎ")
883 else:
883 else:
884 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
884 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
885
885
886
886
887 class DottedObjectNameTrait(HasTraits):
887 class DottedObjectNameTrait(HasTraits):
888 value = DottedObjectName("a.b")
888 value = DottedObjectName("a.b")
889
889
890 class TestDottedObjectName(TraitTestBase):
890 class TestDottedObjectName(TraitTestBase):
891 obj = DottedObjectNameTrait()
891 obj = DottedObjectNameTrait()
892
892
893 _default_value = "a.b"
893 _default_value = "a.b"
894 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
894 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
895 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
895 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
896 if sys.version_info[0] < 3:
896 if sys.version_info[0] < 3:
897 _bad_values.append(u"t.ΓΎ")
897 _bad_values.append(u"t.ΓΎ")
898 else:
898 else:
899 _good_values.append(u"t.ΓΎ")
899 _good_values.append(u"t.ΓΎ")
900
900
901
901
902 class TCPAddressTrait(HasTraits):
902 class TCPAddressTrait(HasTraits):
903
903
904 value = TCPAddress()
904 value = TCPAddress()
905
905
906 class TestTCPAddress(TraitTestBase):
906 class TestTCPAddress(TraitTestBase):
907
907
908 obj = TCPAddressTrait()
908 obj = TCPAddressTrait()
909
909
910 _default_value = ('127.0.0.1',0)
910 _default_value = ('127.0.0.1',0)
911 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
911 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
912 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
912 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
913
913
914 class ListTrait(HasTraits):
914 class ListTrait(HasTraits):
915
915
916 value = List(Int)
916 value = List(Int)
917
917
918 class TestList(TraitTestBase):
918 class TestList(TraitTestBase):
919
919
920 obj = ListTrait()
920 obj = ListTrait()
921
921
922 _default_value = []
922 _default_value = []
923 _good_values = [[], [1], list(range(10)), (1,2)]
923 _good_values = [[], [1], list(range(10)), (1,2)]
924 _bad_values = [10, [1,'a'], 'a']
924 _bad_values = [10, [1,'a'], 'a']
925
925
926 def coerce(self, value):
926 def coerce(self, value):
927 if value is not None:
927 if value is not None:
928 value = list(value)
928 value = list(value)
929 return value
929 return value
930
930
931 class Foo(object):
931 class Foo(object):
932 pass
932 pass
933
933
934 class InstanceListTrait(HasTraits):
934 class InstanceListTrait(HasTraits):
935
935
936 value = List(Instance(__name__+'.Foo'))
936 value = List(Instance(__name__+'.Foo'))
937
937
938 class TestInstanceList(TraitTestBase):
938 class TestInstanceList(TraitTestBase):
939
939
940 obj = InstanceListTrait()
940 obj = InstanceListTrait()
941
941
942 def test_klass(self):
942 def test_klass(self):
943 """Test that the instance klass is properly assigned."""
943 """Test that the instance klass is properly assigned."""
944 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
944 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
945
945
946 _default_value = []
946 _default_value = []
947 _good_values = [[Foo(), Foo(), None], None]
947 _good_values = [[Foo(), Foo(), None], None]
948 _bad_values = [['1', 2,], '1', [Foo]]
948 _bad_values = [['1', 2,], '1', [Foo]]
949
949
950 class LenListTrait(HasTraits):
950 class LenListTrait(HasTraits):
951
951
952 value = List(Int, [0], minlen=1, maxlen=2)
952 value = List(Int, [0], minlen=1, maxlen=2)
953
953
954 class TestLenList(TraitTestBase):
954 class TestLenList(TraitTestBase):
955
955
956 obj = LenListTrait()
956 obj = LenListTrait()
957
957
958 _default_value = [0]
958 _default_value = [0]
959 _good_values = [[1], [1,2], (1,2)]
959 _good_values = [[1], [1,2], (1,2)]
960 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
960 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
961
961
962 def coerce(self, value):
962 def coerce(self, value):
963 if value is not None:
963 if value is not None:
964 value = list(value)
964 value = list(value)
965 return value
965 return value
966
966
967 class TupleTrait(HasTraits):
967 class TupleTrait(HasTraits):
968
968
969 value = Tuple(Int(allow_none=True))
969 value = Tuple(Int(allow_none=True))
970
970
971 class TestTupleTrait(TraitTestBase):
971 class TestTupleTrait(TraitTestBase):
972
972
973 obj = TupleTrait()
973 obj = TupleTrait()
974
974
975 _default_value = None
975 _default_value = None
976 _good_values = [(1,), None, (0,), [1], (None,)]
976 _good_values = [(1,), None, (0,), [1], (None,)]
977 _bad_values = [10, (1,2), ('a'), ()]
977 _bad_values = [10, (1,2), ('a'), ()]
978
978
979 def coerce(self, value):
979 def coerce(self, value):
980 if value is not None:
980 if value is not None:
981 value = tuple(value)
981 value = tuple(value)
982 return value
982 return value
983
983
984 def test_invalid_args(self):
984 def test_invalid_args(self):
985 self.assertRaises(TypeError, Tuple, 5)
985 self.assertRaises(TypeError, Tuple, 5)
986 self.assertRaises(TypeError, Tuple, default_value='hello')
986 self.assertRaises(TypeError, Tuple, default_value='hello')
987 t = Tuple(Int, CBytes, default_value=(1,5))
987 t = Tuple(Int, CBytes, default_value=(1,5))
988
988
989 class LooseTupleTrait(HasTraits):
989 class LooseTupleTrait(HasTraits):
990
990
991 value = Tuple((1,2,3))
991 value = Tuple((1,2,3))
992
992
993 class TestLooseTupleTrait(TraitTestBase):
993 class TestLooseTupleTrait(TraitTestBase):
994
994
995 obj = LooseTupleTrait()
995 obj = LooseTupleTrait()
996
996
997 _default_value = (1,2,3)
997 _default_value = (1,2,3)
998 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
998 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
999 _bad_values = [10, 'hello', {}]
999 _bad_values = [10, 'hello', {}]
1000
1000
1001 def coerce(self, value):
1001 def coerce(self, value):
1002 if value is not None:
1002 if value is not None:
1003 value = tuple(value)
1003 value = tuple(value)
1004 return value
1004 return value
1005
1005
1006 def test_invalid_args(self):
1006 def test_invalid_args(self):
1007 self.assertRaises(TypeError, Tuple, 5)
1007 self.assertRaises(TypeError, Tuple, 5)
1008 self.assertRaises(TypeError, Tuple, default_value='hello')
1008 self.assertRaises(TypeError, Tuple, default_value='hello')
1009 t = Tuple(Int, CBytes, default_value=(1,5))
1009 t = Tuple(Int, CBytes, default_value=(1,5))
1010
1010
1011
1011
1012 class MultiTupleTrait(HasTraits):
1012 class MultiTupleTrait(HasTraits):
1013
1013
1014 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1014 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1015
1015
1016 class TestMultiTuple(TraitTestBase):
1016 class TestMultiTuple(TraitTestBase):
1017
1017
1018 obj = MultiTupleTrait()
1018 obj = MultiTupleTrait()
1019
1019
1020 _default_value = (99,b'bottles')
1020 _default_value = (99,b'bottles')
1021 _good_values = [(1,b'a'), (2,b'b')]
1021 _good_values = [(1,b'a'), (2,b'b')]
1022 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1022 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1023
1023
1024 class CRegExpTrait(HasTraits):
1024 class CRegExpTrait(HasTraits):
1025
1025
1026 value = CRegExp(r'')
1026 value = CRegExp(r'')
1027
1027
1028 class TestCRegExp(TraitTestBase):
1028 class TestCRegExp(TraitTestBase):
1029
1029
1030 def coerce(self, value):
1030 def coerce(self, value):
1031 return re.compile(value)
1031 return re.compile(value)
1032
1032
1033 obj = CRegExpTrait()
1033 obj = CRegExpTrait()
1034
1034
1035 _default_value = re.compile(r'')
1035 _default_value = re.compile(r'')
1036 _good_values = [r'\d+', re.compile(r'\d+')]
1036 _good_values = [r'\d+', re.compile(r'\d+')]
1037 _bad_values = [r'(', None, ()]
1037 _bad_values = ['(', None, ()]
1038
1038
1039 class DictTrait(HasTraits):
1039 class DictTrait(HasTraits):
1040 value = Dict()
1040 value = Dict()
1041
1041
1042 def test_dict_assignment():
1042 def test_dict_assignment():
1043 d = dict()
1043 d = dict()
1044 c = DictTrait()
1044 c = DictTrait()
1045 c.value = d
1045 c.value = d
1046 d['a'] = 5
1046 d['a'] = 5
1047 nt.assert_equal(d, c.value)
1047 nt.assert_equal(d, c.value)
1048 nt.assert_true(c.value is d)
1048 nt.assert_true(c.value is d)
1049
1049
1050 class TestLink(TestCase):
1050 class TestLink(TestCase):
1051 def test_connect_same(self):
1051 def test_connect_same(self):
1052 """Verify two traitlets of the same type can be linked together using link."""
1052 """Verify two traitlets of the same type can be linked together using link."""
1053
1053
1054 # Create two simple classes with Int traitlets.
1054 # Create two simple classes with Int traitlets.
1055 class A(HasTraits):
1055 class A(HasTraits):
1056 value = Int()
1056 value = Int()
1057 a = A(value=9)
1057 a = A(value=9)
1058 b = A(value=8)
1058 b = A(value=8)
1059
1059
1060 # Conenct the two classes.
1060 # Conenct the two classes.
1061 c = link((a, 'value'), (b, 'value'))
1061 c = link((a, 'value'), (b, 'value'))
1062
1062
1063 # Make sure the values are the same at the point of linking.
1063 # Make sure the values are the same at the point of linking.
1064 self.assertEqual(a.value, b.value)
1064 self.assertEqual(a.value, b.value)
1065
1065
1066 # Change one of the values to make sure they stay in sync.
1066 # Change one of the values to make sure they stay in sync.
1067 a.value = 5
1067 a.value = 5
1068 self.assertEqual(a.value, b.value)
1068 self.assertEqual(a.value, b.value)
1069 b.value = 6
1069 b.value = 6
1070 self.assertEqual(a.value, b.value)
1070 self.assertEqual(a.value, b.value)
1071
1071
1072 def test_link_different(self):
1072 def test_link_different(self):
1073 """Verify two traitlets of different types can be linked together using link."""
1073 """Verify two traitlets of different types can be linked together using link."""
1074
1074
1075 # Create two simple classes with Int traitlets.
1075 # Create two simple classes with Int traitlets.
1076 class A(HasTraits):
1076 class A(HasTraits):
1077 value = Int()
1077 value = Int()
1078 class B(HasTraits):
1078 class B(HasTraits):
1079 count = Int()
1079 count = Int()
1080 a = A(value=9)
1080 a = A(value=9)
1081 b = B(count=8)
1081 b = B(count=8)
1082
1082
1083 # Conenct the two classes.
1083 # Conenct the two classes.
1084 c = link((a, 'value'), (b, 'count'))
1084 c = link((a, 'value'), (b, 'count'))
1085
1085
1086 # Make sure the values are the same at the point of linking.
1086 # Make sure the values are the same at the point of linking.
1087 self.assertEqual(a.value, b.count)
1087 self.assertEqual(a.value, b.count)
1088
1088
1089 # Change one of the values to make sure they stay in sync.
1089 # Change one of the values to make sure they stay in sync.
1090 a.value = 5
1090 a.value = 5
1091 self.assertEqual(a.value, b.count)
1091 self.assertEqual(a.value, b.count)
1092 b.count = 4
1092 b.count = 4
1093 self.assertEqual(a.value, b.count)
1093 self.assertEqual(a.value, b.count)
1094
1094
1095 def test_unlink(self):
1095 def test_unlink(self):
1096 """Verify two linked traitlets can be unlinked."""
1096 """Verify two linked traitlets can be unlinked."""
1097
1097
1098 # Create two simple classes with Int traitlets.
1098 # Create two simple classes with Int traitlets.
1099 class A(HasTraits):
1099 class A(HasTraits):
1100 value = Int()
1100 value = Int()
1101 a = A(value=9)
1101 a = A(value=9)
1102 b = A(value=8)
1102 b = A(value=8)
1103
1103
1104 # Connect the two classes.
1104 # Connect the two classes.
1105 c = link((a, 'value'), (b, 'value'))
1105 c = link((a, 'value'), (b, 'value'))
1106 a.value = 4
1106 a.value = 4
1107 c.unlink()
1107 c.unlink()
1108
1108
1109 # Change one of the values to make sure they don't stay in sync.
1109 # Change one of the values to make sure they don't stay in sync.
1110 a.value = 5
1110 a.value = 5
1111 self.assertNotEqual(a.value, b.value)
1111 self.assertNotEqual(a.value, b.value)
1112
1112
1113 def test_callbacks(self):
1113 def test_callbacks(self):
1114 """Verify two linked traitlets have their callbacks called once."""
1114 """Verify two linked traitlets have their callbacks called once."""
1115
1115
1116 # Create two simple classes with Int traitlets.
1116 # Create two simple classes with Int traitlets.
1117 class A(HasTraits):
1117 class A(HasTraits):
1118 value = Int()
1118 value = Int()
1119 class B(HasTraits):
1119 class B(HasTraits):
1120 count = Int()
1120 count = Int()
1121 a = A(value=9)
1121 a = A(value=9)
1122 b = B(count=8)
1122 b = B(count=8)
1123
1123
1124 # Register callbacks that count.
1124 # Register callbacks that count.
1125 callback_count = []
1125 callback_count = []
1126 def a_callback(name, old, new):
1126 def a_callback(name, old, new):
1127 callback_count.append('a')
1127 callback_count.append('a')
1128 a.on_trait_change(a_callback, 'value')
1128 a.on_trait_change(a_callback, 'value')
1129 def b_callback(name, old, new):
1129 def b_callback(name, old, new):
1130 callback_count.append('b')
1130 callback_count.append('b')
1131 b.on_trait_change(b_callback, 'count')
1131 b.on_trait_change(b_callback, 'count')
1132
1132
1133 # Connect the two classes.
1133 # Connect the two classes.
1134 c = link((a, 'value'), (b, 'count'))
1134 c = link((a, 'value'), (b, 'count'))
1135
1135
1136 # Make sure b's count was set to a's value once.
1136 # Make sure b's count was set to a's value once.
1137 self.assertEqual(''.join(callback_count), 'b')
1137 self.assertEqual(''.join(callback_count), 'b')
1138 del callback_count[:]
1138 del callback_count[:]
1139
1139
1140 # Make sure a's value was set to b's count once.
1140 # Make sure a's value was set to b's count once.
1141 b.count = 5
1141 b.count = 5
1142 self.assertEqual(''.join(callback_count), 'ba')
1142 self.assertEqual(''.join(callback_count), 'ba')
1143 del callback_count[:]
1143 del callback_count[:]
1144
1144
1145 # Make sure b's count was set to a's value once.
1145 # Make sure b's count was set to a's value once.
1146 a.value = 4
1146 a.value = 4
1147 self.assertEqual(''.join(callback_count), 'ab')
1147 self.assertEqual(''.join(callback_count), 'ab')
1148 del callback_count[:]
1148 del callback_count[:]
1149
1149
1150 class Pickleable(HasTraits):
1150 class Pickleable(HasTraits):
1151 i = Int()
1151 i = Int()
1152 j = Int()
1152 j = Int()
1153
1153
1154 def _i_default(self):
1154 def _i_default(self):
1155 return 1
1155 return 1
1156
1156
1157 def _i_changed(self, name, old, new):
1157 def _i_changed(self, name, old, new):
1158 self.j = new
1158 self.j = new
1159
1159
1160 def test_pickle_hastraits():
1160 def test_pickle_hastraits():
1161 c = Pickleable()
1161 c = Pickleable()
1162 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1162 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1163 p = pickle.dumps(c, protocol)
1163 p = pickle.dumps(c, protocol)
1164 c2 = pickle.loads(p)
1164 c2 = pickle.loads(p)
1165 nt.assert_equal(c2.i, c.i)
1165 nt.assert_equal(c2.i, c.i)
1166 nt.assert_equal(c2.j, c.j)
1166 nt.assert_equal(c2.j, c.j)
1167
1167
1168 c.i = 5
1168 c.i = 5
1169 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1169 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1170 p = pickle.dumps(c, protocol)
1170 p = pickle.dumps(c, protocol)
1171 c2 = pickle.loads(p)
1171 c2 = pickle.loads(p)
1172 nt.assert_equal(c2.i, c.i)
1172 nt.assert_equal(c2.i, c.i)
1173 nt.assert_equal(c2.j, c.j)
1173 nt.assert_equal(c2.j, c.j)
1174
1174
1175 class TestEventful(TestCase):
1176
1177 def test_list(self):
1178 """Does the EventfulList work?"""
1179 event_cache = []
1180
1181 class A(HasTraits):
1182 x = EventfulList([c for c in 'abc'])
1183 a = A()
1184 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1185 lambda i, x: event_cache.append('set'), \
1186 lambda i: event_cache.append('del'), \
1187 lambda: event_cache.append('reverse'), \
1188 lambda *p, **k: event_cache.append('sort'))
1189
1190 a.x.remove('c')
1191 # ab
1192 a.x.insert(0, 'z')
1193 # zab
1194 del a.x[1]
1195 # zb
1196 a.x.reverse()
1197 # bz
1198 a.x[1] = 'o'
1199 # bo
1200 a.x.append('a')
1201 # boa
1202 a.x.sort()
1203 # abo
1204
1205 # Were the correct events captured?
1206 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1207
1208 # Is the output correct?
1209 self.assertEqual(a.x, [c for c in 'abo'])
1210
1211 def test_dict(self):
1212 """Does the EventfulDict work?"""
1213 event_cache = []
1214
1215 class A(HasTraits):
1216 x = EventfulDict({c: c for c in 'abc'})
1217 a = A()
1218 a.x.on_events(lambda k, v: event_cache.append('add'), \
1219 lambda k, v: event_cache.append('set'), \
1220 lambda k: event_cache.append('del'))
1221
1222 del a.x['c']
1223 # ab
1224 a.x['z'] = 1
1225 # abz
1226 a.x['z'] = 'z'
1227 # abz
1228 a.x.pop('a')
1229 # bz
1230
1231 # Were the correct events captured?
1232 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1233
1234 # Is the output correct?
1235 self.assertEqual(a.x, {c: c for c in 'bz'})
@@ -1,1523 +1,1567 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A lightweight Traits like module.
3 A lightweight Traits like module.
4
4
5 This is designed to provide a lightweight, simple, pure Python version of
5 This is designed to provide a lightweight, simple, pure Python version of
6 many of the capabilities of enthought.traits. This includes:
6 many of the capabilities of enthought.traits. This includes:
7
7
8 * Validation
8 * Validation
9 * Type specification with defaults
9 * Type specification with defaults
10 * Static and dynamic notification
10 * Static and dynamic notification
11 * Basic predefined types
11 * Basic predefined types
12 * An API that is similar to enthought.traits
12 * An API that is similar to enthought.traits
13
13
14 We don't support:
14 We don't support:
15
15
16 * Delegation
16 * Delegation
17 * Automatic GUI generation
17 * Automatic GUI generation
18 * A full set of trait types. Most importantly, we don't provide container
18 * A full set of trait types. Most importantly, we don't provide container
19 traits (list, dict, tuple) that can trigger notifications if their
19 traits (list, dict, tuple) that can trigger notifications if their
20 contents change.
20 contents change.
21 * API compatibility with enthought.traits
21 * API compatibility with enthought.traits
22
22
23 There are also some important difference in our design:
23 There are also some important difference in our design:
24
24
25 * enthought.traits does not validate default values. We do.
25 * enthought.traits does not validate default values. We do.
26
26
27 We choose to create this module because we need these capabilities, but
27 We choose to create this module because we need these capabilities, but
28 we need them to be pure Python so they work in all Python implementations,
28 we need them to be pure Python so they work in all Python implementations,
29 including Jython and IronPython.
29 including Jython and IronPython.
30
30
31 Inheritance diagram:
31 Inheritance diagram:
32
32
33 .. inheritance-diagram:: IPython.utils.traitlets
33 .. inheritance-diagram:: IPython.utils.traitlets
34 :parts: 3
34 :parts: 3
35 """
35 """
36
36
37 # Copyright (c) IPython Development Team.
37 # Copyright (c) IPython Development Team.
38 # Distributed under the terms of the Modified BSD License.
38 # Distributed under the terms of the Modified BSD License.
39 #
39 #
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 # also under the terms of the Modified BSD License.
41 # also under the terms of the Modified BSD License.
42
42
43 import contextlib
43 import contextlib
44 import inspect
44 import inspect
45 import re
45 import re
46 import sys
46 import sys
47 import types
47 import types
48 from types import FunctionType
48 from types import FunctionType
49 try:
49 try:
50 from types import ClassType, InstanceType
50 from types import ClassType, InstanceType
51 ClassTypes = (ClassType, type)
51 ClassTypes = (ClassType, type)
52 except:
52 except:
53 ClassTypes = (type,)
53 ClassTypes = (type,)
54
54
55 from .importstring import import_item
55 from .importstring import import_item
56 from IPython.utils import py3compat
56 from IPython.utils import py3compat
57 from IPython.utils import eventful
57 from IPython.utils.py3compat import iteritems
58 from IPython.utils.py3compat import iteritems
58 from IPython.testing.skipdoctest import skip_doctest
59 from IPython.testing.skipdoctest import skip_doctest
59
60
60 SequenceTypes = (list, tuple, set, frozenset)
61 SequenceTypes = (list, tuple, set, frozenset)
61
62
62 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
63 # Basic classes
64 # Basic classes
64 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
65
66
66
67
67 class NoDefaultSpecified ( object ): pass
68 class NoDefaultSpecified ( object ): pass
68 NoDefaultSpecified = NoDefaultSpecified()
69 NoDefaultSpecified = NoDefaultSpecified()
69
70
70
71
71 class Undefined ( object ): pass
72 class Undefined ( object ): pass
72 Undefined = Undefined()
73 Undefined = Undefined()
73
74
74 class TraitError(Exception):
75 class TraitError(Exception):
75 pass
76 pass
76
77
77 #-----------------------------------------------------------------------------
78 #-----------------------------------------------------------------------------
78 # Utilities
79 # Utilities
79 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
80
81
81
82
82 def class_of ( object ):
83 def class_of ( object ):
83 """ Returns a string containing the class name of an object with the
84 """ Returns a string containing the class name of an object with the
84 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
85 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
85 'a PlotValue').
86 'a PlotValue').
86 """
87 """
87 if isinstance( object, py3compat.string_types ):
88 if isinstance( object, py3compat.string_types ):
88 return add_article( object )
89 return add_article( object )
89
90
90 return add_article( object.__class__.__name__ )
91 return add_article( object.__class__.__name__ )
91
92
92
93
93 def add_article ( name ):
94 def add_article ( name ):
94 """ Returns a string containing the correct indefinite article ('a' or 'an')
95 """ Returns a string containing the correct indefinite article ('a' or 'an')
95 prefixed to the specified string.
96 prefixed to the specified string.
96 """
97 """
97 if name[:1].lower() in 'aeiou':
98 if name[:1].lower() in 'aeiou':
98 return 'an ' + name
99 return 'an ' + name
99
100
100 return 'a ' + name
101 return 'a ' + name
101
102
102
103
103 def repr_type(obj):
104 def repr_type(obj):
104 """ Return a string representation of a value and its type for readable
105 """ Return a string representation of a value and its type for readable
105 error messages.
106 error messages.
106 """
107 """
107 the_type = type(obj)
108 the_type = type(obj)
108 if (not py3compat.PY3) and the_type is InstanceType:
109 if (not py3compat.PY3) and the_type is InstanceType:
109 # Old-style class.
110 # Old-style class.
110 the_type = obj.__class__
111 the_type = obj.__class__
111 msg = '%r %r' % (obj, the_type)
112 msg = '%r %r' % (obj, the_type)
112 return msg
113 return msg
113
114
114
115
115 def is_trait(t):
116 def is_trait(t):
116 """ Returns whether the given value is an instance or subclass of TraitType.
117 """ Returns whether the given value is an instance or subclass of TraitType.
117 """
118 """
118 return (isinstance(t, TraitType) or
119 return (isinstance(t, TraitType) or
119 (isinstance(t, type) and issubclass(t, TraitType)))
120 (isinstance(t, type) and issubclass(t, TraitType)))
120
121
121
122
122 def parse_notifier_name(name):
123 def parse_notifier_name(name):
123 """Convert the name argument to a list of names.
124 """Convert the name argument to a list of names.
124
125
125 Examples
126 Examples
126 --------
127 --------
127
128
128 >>> parse_notifier_name('a')
129 >>> parse_notifier_name('a')
129 ['a']
130 ['a']
130 >>> parse_notifier_name(['a','b'])
131 >>> parse_notifier_name(['a','b'])
131 ['a', 'b']
132 ['a', 'b']
132 >>> parse_notifier_name(None)
133 >>> parse_notifier_name(None)
133 ['anytrait']
134 ['anytrait']
134 """
135 """
135 if isinstance(name, str):
136 if isinstance(name, str):
136 return [name]
137 return [name]
137 elif name is None:
138 elif name is None:
138 return ['anytrait']
139 return ['anytrait']
139 elif isinstance(name, (list, tuple)):
140 elif isinstance(name, (list, tuple)):
140 for n in name:
141 for n in name:
141 assert isinstance(n, str), "names must be strings"
142 assert isinstance(n, str), "names must be strings"
142 return name
143 return name
143
144
144
145
145 class _SimpleTest:
146 class _SimpleTest:
146 def __init__ ( self, value ): self.value = value
147 def __init__ ( self, value ): self.value = value
147 def __call__ ( self, test ):
148 def __call__ ( self, test ):
148 return test == self.value
149 return test == self.value
149 def __repr__(self):
150 def __repr__(self):
150 return "<SimpleTest(%r)" % self.value
151 return "<SimpleTest(%r)" % self.value
151 def __str__(self):
152 def __str__(self):
152 return self.__repr__()
153 return self.__repr__()
153
154
154
155
155 def getmembers(object, predicate=None):
156 def getmembers(object, predicate=None):
156 """A safe version of inspect.getmembers that handles missing attributes.
157 """A safe version of inspect.getmembers that handles missing attributes.
157
158
158 This is useful when there are descriptor based attributes that for
159 This is useful when there are descriptor based attributes that for
159 some reason raise AttributeError even though they exist. This happens
160 some reason raise AttributeError even though they exist. This happens
160 in zope.inteface with the __provides__ attribute.
161 in zope.inteface with the __provides__ attribute.
161 """
162 """
162 results = []
163 results = []
163 for key in dir(object):
164 for key in dir(object):
164 try:
165 try:
165 value = getattr(object, key)
166 value = getattr(object, key)
166 except AttributeError:
167 except AttributeError:
167 pass
168 pass
168 else:
169 else:
169 if not predicate or predicate(value):
170 if not predicate or predicate(value):
170 results.append((key, value))
171 results.append((key, value))
171 results.sort()
172 results.sort()
172 return results
173 return results
173
174
174 @skip_doctest
175 @skip_doctest
175 class link(object):
176 class link(object):
176 """Link traits from different objects together so they remain in sync.
177 """Link traits from different objects together so they remain in sync.
177
178
178 Parameters
179 Parameters
179 ----------
180 ----------
180 obj : pairs of objects/attributes
181 obj : pairs of objects/attributes
181
182
182 Examples
183 Examples
183 --------
184 --------
184
185
185 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
186 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
186 >>> obj1.value = 5 # updates other objects as well
187 >>> obj1.value = 5 # updates other objects as well
187 """
188 """
188 updating = False
189 updating = False
189 def __init__(self, *args):
190 def __init__(self, *args):
190 if len(args) < 2:
191 if len(args) < 2:
191 raise TypeError('At least two traitlets must be provided.')
192 raise TypeError('At least two traitlets must be provided.')
192
193
193 self.objects = {}
194 self.objects = {}
194 initial = getattr(args[0][0], args[0][1])
195 initial = getattr(args[0][0], args[0][1])
195 for obj,attr in args:
196 for obj,attr in args:
196 if getattr(obj, attr) != initial:
197 if getattr(obj, attr) != initial:
197 setattr(obj, attr, initial)
198 setattr(obj, attr, initial)
198
199
199 callback = self._make_closure(obj,attr)
200 callback = self._make_closure(obj,attr)
200 obj.on_trait_change(callback, attr)
201 obj.on_trait_change(callback, attr)
201 self.objects[(obj,attr)] = callback
202 self.objects[(obj,attr)] = callback
202
203
203 @contextlib.contextmanager
204 @contextlib.contextmanager
204 def _busy_updating(self):
205 def _busy_updating(self):
205 self.updating = True
206 self.updating = True
206 try:
207 try:
207 yield
208 yield
208 finally:
209 finally:
209 self.updating = False
210 self.updating = False
210
211
211 def _make_closure(self, sending_obj, sending_attr):
212 def _make_closure(self, sending_obj, sending_attr):
212 def update(name, old, new):
213 def update(name, old, new):
213 self._update(sending_obj, sending_attr, new)
214 self._update(sending_obj, sending_attr, new)
214 return update
215 return update
215
216
216 def _update(self, sending_obj, sending_attr, new):
217 def _update(self, sending_obj, sending_attr, new):
217 if self.updating:
218 if self.updating:
218 return
219 return
219 with self._busy_updating():
220 with self._busy_updating():
220 for obj,attr in self.objects.keys():
221 for obj,attr in self.objects.keys():
221 if obj is not sending_obj or attr != sending_attr:
222 if obj is not sending_obj or attr != sending_attr:
222 setattr(obj, attr, new)
223 setattr(obj, attr, new)
223
224
224 def unlink(self):
225 def unlink(self):
225 for key, callback in self.objects.items():
226 for key, callback in self.objects.items():
226 (obj,attr) = key
227 (obj,attr) = key
227 obj.on_trait_change(callback, attr, remove=True)
228 obj.on_trait_change(callback, attr, remove=True)
228
229
229 #-----------------------------------------------------------------------------
230 #-----------------------------------------------------------------------------
230 # Base TraitType for all traits
231 # Base TraitType for all traits
231 #-----------------------------------------------------------------------------
232 #-----------------------------------------------------------------------------
232
233
233
234
234 class TraitType(object):
235 class TraitType(object):
235 """A base class for all trait descriptors.
236 """A base class for all trait descriptors.
236
237
237 Notes
238 Notes
238 -----
239 -----
239 Our implementation of traits is based on Python's descriptor
240 Our implementation of traits is based on Python's descriptor
240 prototol. This class is the base class for all such descriptors. The
241 prototol. This class is the base class for all such descriptors. The
241 only magic we use is a custom metaclass for the main :class:`HasTraits`
242 only magic we use is a custom metaclass for the main :class:`HasTraits`
242 class that does the following:
243 class that does the following:
243
244
244 1. Sets the :attr:`name` attribute of every :class:`TraitType`
245 1. Sets the :attr:`name` attribute of every :class:`TraitType`
245 instance in the class dict to the name of the attribute.
246 instance in the class dict to the name of the attribute.
246 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
247 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
247 instance in the class dict to the *class* that declared the trait.
248 instance in the class dict to the *class* that declared the trait.
248 This is used by the :class:`This` trait to allow subclasses to
249 This is used by the :class:`This` trait to allow subclasses to
249 accept superclasses for :class:`This` values.
250 accept superclasses for :class:`This` values.
250 """
251 """
251
252
252
253
253 metadata = {}
254 metadata = {}
254 default_value = Undefined
255 default_value = Undefined
255 allow_none = False
256 allow_none = False
256 info_text = 'any value'
257 info_text = 'any value'
257
258
258 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
259 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
259 """Create a TraitType.
260 """Create a TraitType.
260 """
261 """
261 if default_value is not NoDefaultSpecified:
262 if default_value is not NoDefaultSpecified:
262 self.default_value = default_value
263 self.default_value = default_value
263 if allow_none is not None:
264 if allow_none is not None:
264 self.allow_none = allow_none
265 self.allow_none = allow_none
265
266
266 if len(metadata) > 0:
267 if len(metadata) > 0:
267 if len(self.metadata) > 0:
268 if len(self.metadata) > 0:
268 self._metadata = self.metadata.copy()
269 self._metadata = self.metadata.copy()
269 self._metadata.update(metadata)
270 self._metadata.update(metadata)
270 else:
271 else:
271 self._metadata = metadata
272 self._metadata = metadata
272 else:
273 else:
273 self._metadata = self.metadata
274 self._metadata = self.metadata
274
275
275 self.init()
276 self.init()
276
277
277 def init(self):
278 def init(self):
278 pass
279 pass
279
280
280 def get_default_value(self):
281 def get_default_value(self):
281 """Create a new instance of the default value."""
282 """Create a new instance of the default value."""
282 return self.default_value
283 return self.default_value
283
284
284 def instance_init(self, obj):
285 def instance_init(self, obj):
285 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
286 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
286
287
287 Some stages of initialization must be delayed until the parent
288 Some stages of initialization must be delayed until the parent
288 :class:`HasTraits` instance has been created. This method is
289 :class:`HasTraits` instance has been created. This method is
289 called in :meth:`HasTraits.__new__` after the instance has been
290 called in :meth:`HasTraits.__new__` after the instance has been
290 created.
291 created.
291
292
292 This method trigger the creation and validation of default values
293 This method trigger the creation and validation of default values
293 and also things like the resolution of str given class names in
294 and also things like the resolution of str given class names in
294 :class:`Type` and :class`Instance`.
295 :class:`Type` and :class`Instance`.
295
296
296 Parameters
297 Parameters
297 ----------
298 ----------
298 obj : :class:`HasTraits` instance
299 obj : :class:`HasTraits` instance
299 The parent :class:`HasTraits` instance that has just been
300 The parent :class:`HasTraits` instance that has just been
300 created.
301 created.
301 """
302 """
302 self.set_default_value(obj)
303 self.set_default_value(obj)
303
304
304 def set_default_value(self, obj):
305 def set_default_value(self, obj):
305 """Set the default value on a per instance basis.
306 """Set the default value on a per instance basis.
306
307
307 This method is called by :meth:`instance_init` to create and
308 This method is called by :meth:`instance_init` to create and
308 validate the default value. The creation and validation of
309 validate the default value. The creation and validation of
309 default values must be delayed until the parent :class:`HasTraits`
310 default values must be delayed until the parent :class:`HasTraits`
310 class has been instantiated.
311 class has been instantiated.
311 """
312 """
312 # Check for a deferred initializer defined in the same class as the
313 # Check for a deferred initializer defined in the same class as the
313 # trait declaration or above.
314 # trait declaration or above.
314 mro = type(obj).mro()
315 mro = type(obj).mro()
315 meth_name = '_%s_default' % self.name
316 meth_name = '_%s_default' % self.name
316 for cls in mro[:mro.index(self.this_class)+1]:
317 for cls in mro[:mro.index(self.this_class)+1]:
317 if meth_name in cls.__dict__:
318 if meth_name in cls.__dict__:
318 break
319 break
319 else:
320 else:
320 # We didn't find one. Do static initialization.
321 # We didn't find one. Do static initialization.
321 dv = self.get_default_value()
322 dv = self.get_default_value()
322 newdv = self._validate(obj, dv)
323 newdv = self._validate(obj, dv)
323 obj._trait_values[self.name] = newdv
324 obj._trait_values[self.name] = newdv
324 return
325 return
325 # Complete the dynamic initialization.
326 # Complete the dynamic initialization.
326 obj._trait_dyn_inits[self.name] = meth_name
327 obj._trait_dyn_inits[self.name] = meth_name
327
328
328 def __get__(self, obj, cls=None):
329 def __get__(self, obj, cls=None):
329 """Get the value of the trait by self.name for the instance.
330 """Get the value of the trait by self.name for the instance.
330
331
331 Default values are instantiated when :meth:`HasTraits.__new__`
332 Default values are instantiated when :meth:`HasTraits.__new__`
332 is called. Thus by the time this method gets called either the
333 is called. Thus by the time this method gets called either the
333 default value or a user defined value (they called :meth:`__set__`)
334 default value or a user defined value (they called :meth:`__set__`)
334 is in the :class:`HasTraits` instance.
335 is in the :class:`HasTraits` instance.
335 """
336 """
336 if obj is None:
337 if obj is None:
337 return self
338 return self
338 else:
339 else:
339 try:
340 try:
340 value = obj._trait_values[self.name]
341 value = obj._trait_values[self.name]
341 except KeyError:
342 except KeyError:
342 # Check for a dynamic initializer.
343 # Check for a dynamic initializer.
343 if self.name in obj._trait_dyn_inits:
344 if self.name in obj._trait_dyn_inits:
344 method = getattr(obj, obj._trait_dyn_inits[self.name])
345 method = getattr(obj, obj._trait_dyn_inits[self.name])
345 value = method()
346 value = method()
346 # FIXME: Do we really validate here?
347 # FIXME: Do we really validate here?
347 value = self._validate(obj, value)
348 value = self._validate(obj, value)
348 obj._trait_values[self.name] = value
349 obj._trait_values[self.name] = value
349 return value
350 return value
350 else:
351 else:
351 raise TraitError('Unexpected error in TraitType: '
352 raise TraitError('Unexpected error in TraitType: '
352 'both default value and dynamic initializer are '
353 'both default value and dynamic initializer are '
353 'absent.')
354 'absent.')
354 except Exception:
355 except Exception:
355 # HasTraits should call set_default_value to populate
356 # HasTraits should call set_default_value to populate
356 # this. So this should never be reached.
357 # this. So this should never be reached.
357 raise TraitError('Unexpected error in TraitType: '
358 raise TraitError('Unexpected error in TraitType: '
358 'default value not set properly')
359 'default value not set properly')
359 else:
360 else:
360 return value
361 return value
361
362
362 def __set__(self, obj, value):
363 def __set__(self, obj, value):
363 new_value = self._validate(obj, value)
364 new_value = self._validate(obj, value)
364 old_value = self.__get__(obj)
365 old_value = self.__get__(obj)
365 obj._trait_values[self.name] = new_value
366 obj._trait_values[self.name] = new_value
366 try:
367 try:
367 silent = bool(old_value == new_value)
368 silent = bool(old_value == new_value)
368 except:
369 except:
369 # if there is an error in comparing, default to notify
370 # if there is an error in comparing, default to notify
370 silent = False
371 silent = False
371 if silent is not True:
372 if silent is not True:
372 # we explicitly compare silent to True just in case the equality
373 # we explicitly compare silent to True just in case the equality
373 # comparison above returns something other than True/False
374 # comparison above returns something other than True/False
374 obj._notify_trait(self.name, old_value, new_value)
375 obj._notify_trait(self.name, old_value, new_value)
375
376
376 def _validate(self, obj, value):
377 def _validate(self, obj, value):
377 if value is None and self.allow_none:
378 if value is None and self.allow_none:
378 return value
379 return value
379 if hasattr(self, 'validate'):
380 if hasattr(self, 'validate'):
380 return self.validate(obj, value)
381 return self.validate(obj, value)
381 elif hasattr(self, 'is_valid_for'):
382 elif hasattr(self, 'is_valid_for'):
382 valid = self.is_valid_for(value)
383 valid = self.is_valid_for(value)
383 if valid:
384 if valid:
384 return value
385 return value
385 else:
386 else:
386 raise TraitError('invalid value for type: %r' % value)
387 raise TraitError('invalid value for type: %r' % value)
387 elif hasattr(self, 'value_for'):
388 elif hasattr(self, 'value_for'):
388 return self.value_for(value)
389 return self.value_for(value)
389 else:
390 else:
390 return value
391 return value
391
392
392 def info(self):
393 def info(self):
393 return self.info_text
394 return self.info_text
394
395
395 def error(self, obj, value):
396 def error(self, obj, value):
396 if obj is not None:
397 if obj is not None:
397 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
398 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
398 % (self.name, class_of(obj),
399 % (self.name, class_of(obj),
399 self.info(), repr_type(value))
400 self.info(), repr_type(value))
400 else:
401 else:
401 e = "The '%s' trait must be %s, but a value of %r was specified." \
402 e = "The '%s' trait must be %s, but a value of %r was specified." \
402 % (self.name, self.info(), repr_type(value))
403 % (self.name, self.info(), repr_type(value))
403 raise TraitError(e)
404 raise TraitError(e)
404
405
405 def get_metadata(self, key):
406 def get_metadata(self, key):
406 return getattr(self, '_metadata', {}).get(key, None)
407 return getattr(self, '_metadata', {}).get(key, None)
407
408
408 def set_metadata(self, key, value):
409 def set_metadata(self, key, value):
409 getattr(self, '_metadata', {})[key] = value
410 getattr(self, '_metadata', {})[key] = value
410
411
411
412
412 #-----------------------------------------------------------------------------
413 #-----------------------------------------------------------------------------
413 # The HasTraits implementation
414 # The HasTraits implementation
414 #-----------------------------------------------------------------------------
415 #-----------------------------------------------------------------------------
415
416
416
417
417 class MetaHasTraits(type):
418 class MetaHasTraits(type):
418 """A metaclass for HasTraits.
419 """A metaclass for HasTraits.
419
420
420 This metaclass makes sure that any TraitType class attributes are
421 This metaclass makes sure that any TraitType class attributes are
421 instantiated and sets their name attribute.
422 instantiated and sets their name attribute.
422 """
423 """
423
424
424 def __new__(mcls, name, bases, classdict):
425 def __new__(mcls, name, bases, classdict):
425 """Create the HasTraits class.
426 """Create the HasTraits class.
426
427
427 This instantiates all TraitTypes in the class dict and sets their
428 This instantiates all TraitTypes in the class dict and sets their
428 :attr:`name` attribute.
429 :attr:`name` attribute.
429 """
430 """
430 # print "MetaHasTraitlets (mcls, name): ", mcls, name
431 # print "MetaHasTraitlets (mcls, name): ", mcls, name
431 # print "MetaHasTraitlets (bases): ", bases
432 # print "MetaHasTraitlets (bases): ", bases
432 # print "MetaHasTraitlets (classdict): ", classdict
433 # print "MetaHasTraitlets (classdict): ", classdict
433 for k,v in iteritems(classdict):
434 for k,v in iteritems(classdict):
434 if isinstance(v, TraitType):
435 if isinstance(v, TraitType):
435 v.name = k
436 v.name = k
436 elif inspect.isclass(v):
437 elif inspect.isclass(v):
437 if issubclass(v, TraitType):
438 if issubclass(v, TraitType):
438 vinst = v()
439 vinst = v()
439 vinst.name = k
440 vinst.name = k
440 classdict[k] = vinst
441 classdict[k] = vinst
441 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
442 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
442
443
443 def __init__(cls, name, bases, classdict):
444 def __init__(cls, name, bases, classdict):
444 """Finish initializing the HasTraits class.
445 """Finish initializing the HasTraits class.
445
446
446 This sets the :attr:`this_class` attribute of each TraitType in the
447 This sets the :attr:`this_class` attribute of each TraitType in the
447 class dict to the newly created class ``cls``.
448 class dict to the newly created class ``cls``.
448 """
449 """
449 for k, v in iteritems(classdict):
450 for k, v in iteritems(classdict):
450 if isinstance(v, TraitType):
451 if isinstance(v, TraitType):
451 v.this_class = cls
452 v.this_class = cls
452 super(MetaHasTraits, cls).__init__(name, bases, classdict)
453 super(MetaHasTraits, cls).__init__(name, bases, classdict)
453
454
454 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
455 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
455
456
456 def __new__(cls, *args, **kw):
457 def __new__(cls, *args, **kw):
457 # This is needed because object.__new__ only accepts
458 # This is needed because object.__new__ only accepts
458 # the cls argument.
459 # the cls argument.
459 new_meth = super(HasTraits, cls).__new__
460 new_meth = super(HasTraits, cls).__new__
460 if new_meth is object.__new__:
461 if new_meth is object.__new__:
461 inst = new_meth(cls)
462 inst = new_meth(cls)
462 else:
463 else:
463 inst = new_meth(cls, **kw)
464 inst = new_meth(cls, **kw)
464 inst._trait_values = {}
465 inst._trait_values = {}
465 inst._trait_notifiers = {}
466 inst._trait_notifiers = {}
466 inst._trait_dyn_inits = {}
467 inst._trait_dyn_inits = {}
467 # Here we tell all the TraitType instances to set their default
468 # Here we tell all the TraitType instances to set their default
468 # values on the instance.
469 # values on the instance.
469 for key in dir(cls):
470 for key in dir(cls):
470 # Some descriptors raise AttributeError like zope.interface's
471 # Some descriptors raise AttributeError like zope.interface's
471 # __provides__ attributes even though they exist. This causes
472 # __provides__ attributes even though they exist. This causes
472 # AttributeErrors even though they are listed in dir(cls).
473 # AttributeErrors even though they are listed in dir(cls).
473 try:
474 try:
474 value = getattr(cls, key)
475 value = getattr(cls, key)
475 except AttributeError:
476 except AttributeError:
476 pass
477 pass
477 else:
478 else:
478 if isinstance(value, TraitType):
479 if isinstance(value, TraitType):
479 value.instance_init(inst)
480 value.instance_init(inst)
480
481
481 return inst
482 return inst
482
483
483 def __init__(self, *args, **kw):
484 def __init__(self, *args, **kw):
484 # Allow trait values to be set using keyword arguments.
485 # Allow trait values to be set using keyword arguments.
485 # We need to use setattr for this to trigger validation and
486 # We need to use setattr for this to trigger validation and
486 # notifications.
487 # notifications.
487 for key, value in iteritems(kw):
488 for key, value in iteritems(kw):
488 setattr(self, key, value)
489 setattr(self, key, value)
489
490
490 def _notify_trait(self, name, old_value, new_value):
491 def _notify_trait(self, name, old_value, new_value):
491
492
492 # First dynamic ones
493 # First dynamic ones
493 callables = []
494 callables = []
494 callables.extend(self._trait_notifiers.get(name,[]))
495 callables.extend(self._trait_notifiers.get(name,[]))
495 callables.extend(self._trait_notifiers.get('anytrait',[]))
496 callables.extend(self._trait_notifiers.get('anytrait',[]))
496
497
497 # Now static ones
498 # Now static ones
498 try:
499 try:
499 cb = getattr(self, '_%s_changed' % name)
500 cb = getattr(self, '_%s_changed' % name)
500 except:
501 except:
501 pass
502 pass
502 else:
503 else:
503 callables.append(cb)
504 callables.append(cb)
504
505
505 # Call them all now
506 # Call them all now
506 for c in callables:
507 for c in callables:
507 # Traits catches and logs errors here. I allow them to raise
508 # Traits catches and logs errors here. I allow them to raise
508 if callable(c):
509 if callable(c):
509 argspec = inspect.getargspec(c)
510 argspec = inspect.getargspec(c)
510 nargs = len(argspec[0])
511 nargs = len(argspec[0])
511 # Bound methods have an additional 'self' argument
512 # Bound methods have an additional 'self' argument
512 # I don't know how to treat unbound methods, but they
513 # I don't know how to treat unbound methods, but they
513 # can't really be used for callbacks.
514 # can't really be used for callbacks.
514 if isinstance(c, types.MethodType):
515 if isinstance(c, types.MethodType):
515 offset = -1
516 offset = -1
516 else:
517 else:
517 offset = 0
518 offset = 0
518 if nargs + offset == 0:
519 if nargs + offset == 0:
519 c()
520 c()
520 elif nargs + offset == 1:
521 elif nargs + offset == 1:
521 c(name)
522 c(name)
522 elif nargs + offset == 2:
523 elif nargs + offset == 2:
523 c(name, new_value)
524 c(name, new_value)
524 elif nargs + offset == 3:
525 elif nargs + offset == 3:
525 c(name, old_value, new_value)
526 c(name, old_value, new_value)
526 else:
527 else:
527 raise TraitError('a trait changed callback '
528 raise TraitError('a trait changed callback '
528 'must have 0-3 arguments.')
529 'must have 0-3 arguments.')
529 else:
530 else:
530 raise TraitError('a trait changed callback '
531 raise TraitError('a trait changed callback '
531 'must be callable.')
532 'must be callable.')
532
533
533
534
534 def _add_notifiers(self, handler, name):
535 def _add_notifiers(self, handler, name):
535 if name not in self._trait_notifiers:
536 if name not in self._trait_notifiers:
536 nlist = []
537 nlist = []
537 self._trait_notifiers[name] = nlist
538 self._trait_notifiers[name] = nlist
538 else:
539 else:
539 nlist = self._trait_notifiers[name]
540 nlist = self._trait_notifiers[name]
540 if handler not in nlist:
541 if handler not in nlist:
541 nlist.append(handler)
542 nlist.append(handler)
542
543
543 def _remove_notifiers(self, handler, name):
544 def _remove_notifiers(self, handler, name):
544 if name in self._trait_notifiers:
545 if name in self._trait_notifiers:
545 nlist = self._trait_notifiers[name]
546 nlist = self._trait_notifiers[name]
546 try:
547 try:
547 index = nlist.index(handler)
548 index = nlist.index(handler)
548 except ValueError:
549 except ValueError:
549 pass
550 pass
550 else:
551 else:
551 del nlist[index]
552 del nlist[index]
552
553
553 def on_trait_change(self, handler, name=None, remove=False):
554 def on_trait_change(self, handler, name=None, remove=False):
554 """Setup a handler to be called when a trait changes.
555 """Setup a handler to be called when a trait changes.
555
556
556 This is used to setup dynamic notifications of trait changes.
557 This is used to setup dynamic notifications of trait changes.
557
558
558 Static handlers can be created by creating methods on a HasTraits
559 Static handlers can be created by creating methods on a HasTraits
559 subclass with the naming convention '_[traitname]_changed'. Thus,
560 subclass with the naming convention '_[traitname]_changed'. Thus,
560 to create static handler for the trait 'a', create the method
561 to create static handler for the trait 'a', create the method
561 _a_changed(self, name, old, new) (fewer arguments can be used, see
562 _a_changed(self, name, old, new) (fewer arguments can be used, see
562 below).
563 below).
563
564
564 Parameters
565 Parameters
565 ----------
566 ----------
566 handler : callable
567 handler : callable
567 A callable that is called when a trait changes. Its
568 A callable that is called when a trait changes. Its
568 signature can be handler(), handler(name), handler(name, new)
569 signature can be handler(), handler(name), handler(name, new)
569 or handler(name, old, new).
570 or handler(name, old, new).
570 name : list, str, None
571 name : list, str, None
571 If None, the handler will apply to all traits. If a list
572 If None, the handler will apply to all traits. If a list
572 of str, handler will apply to all names in the list. If a
573 of str, handler will apply to all names in the list. If a
573 str, the handler will apply just to that name.
574 str, the handler will apply just to that name.
574 remove : bool
575 remove : bool
575 If False (the default), then install the handler. If True
576 If False (the default), then install the handler. If True
576 then unintall it.
577 then unintall it.
577 """
578 """
578 if remove:
579 if remove:
579 names = parse_notifier_name(name)
580 names = parse_notifier_name(name)
580 for n in names:
581 for n in names:
581 self._remove_notifiers(handler, n)
582 self._remove_notifiers(handler, n)
582 else:
583 else:
583 names = parse_notifier_name(name)
584 names = parse_notifier_name(name)
584 for n in names:
585 for n in names:
585 self._add_notifiers(handler, n)
586 self._add_notifiers(handler, n)
586
587
587 @classmethod
588 @classmethod
588 def class_trait_names(cls, **metadata):
589 def class_trait_names(cls, **metadata):
589 """Get a list of all the names of this class' traits.
590 """Get a list of all the names of this class' traits.
590
591
591 This method is just like the :meth:`trait_names` method,
592 This method is just like the :meth:`trait_names` method,
592 but is unbound.
593 but is unbound.
593 """
594 """
594 return cls.class_traits(**metadata).keys()
595 return cls.class_traits(**metadata).keys()
595
596
596 @classmethod
597 @classmethod
597 def class_traits(cls, **metadata):
598 def class_traits(cls, **metadata):
598 """Get a `dict` of all the traits of this class. The dictionary
599 """Get a `dict` of all the traits of this class. The dictionary
599 is keyed on the name and the values are the TraitType objects.
600 is keyed on the name and the values are the TraitType objects.
600
601
601 This method is just like the :meth:`traits` method, but is unbound.
602 This method is just like the :meth:`traits` method, but is unbound.
602
603
603 The TraitTypes returned don't know anything about the values
604 The TraitTypes returned don't know anything about the values
604 that the various HasTrait's instances are holding.
605 that the various HasTrait's instances are holding.
605
606
606 The metadata kwargs allow functions to be passed in which
607 The metadata kwargs allow functions to be passed in which
607 filter traits based on metadata values. The functions should
608 filter traits based on metadata values. The functions should
608 take a single value as an argument and return a boolean. If
609 take a single value as an argument and return a boolean. If
609 any function returns False, then the trait is not included in
610 any function returns False, then the trait is not included in
610 the output. This does not allow for any simple way of
611 the output. This does not allow for any simple way of
611 testing that a metadata name exists and has any
612 testing that a metadata name exists and has any
612 value because get_metadata returns None if a metadata key
613 value because get_metadata returns None if a metadata key
613 doesn't exist.
614 doesn't exist.
614 """
615 """
615 traits = dict([memb for memb in getmembers(cls) if
616 traits = dict([memb for memb in getmembers(cls) if
616 isinstance(memb[1], TraitType)])
617 isinstance(memb[1], TraitType)])
617
618
618 if len(metadata) == 0:
619 if len(metadata) == 0:
619 return traits
620 return traits
620
621
621 for meta_name, meta_eval in metadata.items():
622 for meta_name, meta_eval in metadata.items():
622 if type(meta_eval) is not FunctionType:
623 if type(meta_eval) is not FunctionType:
623 metadata[meta_name] = _SimpleTest(meta_eval)
624 metadata[meta_name] = _SimpleTest(meta_eval)
624
625
625 result = {}
626 result = {}
626 for name, trait in traits.items():
627 for name, trait in traits.items():
627 for meta_name, meta_eval in metadata.items():
628 for meta_name, meta_eval in metadata.items():
628 if not meta_eval(trait.get_metadata(meta_name)):
629 if not meta_eval(trait.get_metadata(meta_name)):
629 break
630 break
630 else:
631 else:
631 result[name] = trait
632 result[name] = trait
632
633
633 return result
634 return result
634
635
635 def trait_names(self, **metadata):
636 def trait_names(self, **metadata):
636 """Get a list of all the names of this class' traits."""
637 """Get a list of all the names of this class' traits."""
637 return self.traits(**metadata).keys()
638 return self.traits(**metadata).keys()
638
639
639 def traits(self, **metadata):
640 def traits(self, **metadata):
640 """Get a `dict` of all the traits of this class. The dictionary
641 """Get a `dict` of all the traits of this class. The dictionary
641 is keyed on the name and the values are the TraitType objects.
642 is keyed on the name and the values are the TraitType objects.
642
643
643 The TraitTypes returned don't know anything about the values
644 The TraitTypes returned don't know anything about the values
644 that the various HasTrait's instances are holding.
645 that the various HasTrait's instances are holding.
645
646
646 The metadata kwargs allow functions to be passed in which
647 The metadata kwargs allow functions to be passed in which
647 filter traits based on metadata values. The functions should
648 filter traits based on metadata values. The functions should
648 take a single value as an argument and return a boolean. If
649 take a single value as an argument and return a boolean. If
649 any function returns False, then the trait is not included in
650 any function returns False, then the trait is not included in
650 the output. This does not allow for any simple way of
651 the output. This does not allow for any simple way of
651 testing that a metadata name exists and has any
652 testing that a metadata name exists and has any
652 value because get_metadata returns None if a metadata key
653 value because get_metadata returns None if a metadata key
653 doesn't exist.
654 doesn't exist.
654 """
655 """
655 traits = dict([memb for memb in getmembers(self.__class__) if
656 traits = dict([memb for memb in getmembers(self.__class__) if
656 isinstance(memb[1], TraitType)])
657 isinstance(memb[1], TraitType)])
657
658
658 if len(metadata) == 0:
659 if len(metadata) == 0:
659 return traits
660 return traits
660
661
661 for meta_name, meta_eval in metadata.items():
662 for meta_name, meta_eval in metadata.items():
662 if type(meta_eval) is not FunctionType:
663 if type(meta_eval) is not FunctionType:
663 metadata[meta_name] = _SimpleTest(meta_eval)
664 metadata[meta_name] = _SimpleTest(meta_eval)
664
665
665 result = {}
666 result = {}
666 for name, trait in traits.items():
667 for name, trait in traits.items():
667 for meta_name, meta_eval in metadata.items():
668 for meta_name, meta_eval in metadata.items():
668 if not meta_eval(trait.get_metadata(meta_name)):
669 if not meta_eval(trait.get_metadata(meta_name)):
669 break
670 break
670 else:
671 else:
671 result[name] = trait
672 result[name] = trait
672
673
673 return result
674 return result
674
675
675 def trait_metadata(self, traitname, key):
676 def trait_metadata(self, traitname, key):
676 """Get metadata values for trait by key."""
677 """Get metadata values for trait by key."""
677 try:
678 try:
678 trait = getattr(self.__class__, traitname)
679 trait = getattr(self.__class__, traitname)
679 except AttributeError:
680 except AttributeError:
680 raise TraitError("Class %s does not have a trait named %s" %
681 raise TraitError("Class %s does not have a trait named %s" %
681 (self.__class__.__name__, traitname))
682 (self.__class__.__name__, traitname))
682 else:
683 else:
683 return trait.get_metadata(key)
684 return trait.get_metadata(key)
684
685
685 #-----------------------------------------------------------------------------
686 #-----------------------------------------------------------------------------
686 # Actual TraitTypes implementations/subclasses
687 # Actual TraitTypes implementations/subclasses
687 #-----------------------------------------------------------------------------
688 #-----------------------------------------------------------------------------
688
689
689 #-----------------------------------------------------------------------------
690 #-----------------------------------------------------------------------------
690 # TraitTypes subclasses for handling classes and instances of classes
691 # TraitTypes subclasses for handling classes and instances of classes
691 #-----------------------------------------------------------------------------
692 #-----------------------------------------------------------------------------
692
693
693
694
694 class ClassBasedTraitType(TraitType):
695 class ClassBasedTraitType(TraitType):
695 """A trait with error reporting for Type, Instance and This."""
696 """A trait with error reporting for Type, Instance and This."""
696
697
697 def error(self, obj, value):
698 def error(self, obj, value):
698 kind = type(value)
699 kind = type(value)
699 if (not py3compat.PY3) and kind is InstanceType:
700 if (not py3compat.PY3) and kind is InstanceType:
700 msg = 'class %s' % value.__class__.__name__
701 msg = 'class %s' % value.__class__.__name__
701 else:
702 else:
702 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
703 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
703
704
704 if obj is not None:
705 if obj is not None:
705 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
706 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
706 % (self.name, class_of(obj),
707 % (self.name, class_of(obj),
707 self.info(), msg)
708 self.info(), msg)
708 else:
709 else:
709 e = "The '%s' trait must be %s, but a value of %r was specified." \
710 e = "The '%s' trait must be %s, but a value of %r was specified." \
710 % (self.name, self.info(), msg)
711 % (self.name, self.info(), msg)
711
712
712 raise TraitError(e)
713 raise TraitError(e)
713
714
714
715
715 class Type(ClassBasedTraitType):
716 class Type(ClassBasedTraitType):
716 """A trait whose value must be a subclass of a specified class."""
717 """A trait whose value must be a subclass of a specified class."""
717
718
718 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
719 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
719 """Construct a Type trait
720 """Construct a Type trait
720
721
721 A Type trait specifies that its values must be subclasses of
722 A Type trait specifies that its values must be subclasses of
722 a particular class.
723 a particular class.
723
724
724 If only ``default_value`` is given, it is used for the ``klass`` as
725 If only ``default_value`` is given, it is used for the ``klass`` as
725 well.
726 well.
726
727
727 Parameters
728 Parameters
728 ----------
729 ----------
729 default_value : class, str or None
730 default_value : class, str or None
730 The default value must be a subclass of klass. If an str,
731 The default value must be a subclass of klass. If an str,
731 the str must be a fully specified class name, like 'foo.bar.Bah'.
732 the str must be a fully specified class name, like 'foo.bar.Bah'.
732 The string is resolved into real class, when the parent
733 The string is resolved into real class, when the parent
733 :class:`HasTraits` class is instantiated.
734 :class:`HasTraits` class is instantiated.
734 klass : class, str, None
735 klass : class, str, None
735 Values of this trait must be a subclass of klass. The klass
736 Values of this trait must be a subclass of klass. The klass
736 may be specified in a string like: 'foo.bar.MyClass'.
737 may be specified in a string like: 'foo.bar.MyClass'.
737 The string is resolved into real class, when the parent
738 The string is resolved into real class, when the parent
738 :class:`HasTraits` class is instantiated.
739 :class:`HasTraits` class is instantiated.
739 allow_none : boolean
740 allow_none : boolean
740 Indicates whether None is allowed as an assignable value. Even if
741 Indicates whether None is allowed as an assignable value. Even if
741 ``False``, the default value may be ``None``.
742 ``False``, the default value may be ``None``.
742 """
743 """
743 if default_value is None:
744 if default_value is None:
744 if klass is None:
745 if klass is None:
745 klass = object
746 klass = object
746 elif klass is None:
747 elif klass is None:
747 klass = default_value
748 klass = default_value
748
749
749 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
750 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
750 raise TraitError("A Type trait must specify a class.")
751 raise TraitError("A Type trait must specify a class.")
751
752
752 self.klass = klass
753 self.klass = klass
753
754
754 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
755 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
755
756
756 def validate(self, obj, value):
757 def validate(self, obj, value):
757 """Validates that the value is a valid object instance."""
758 """Validates that the value is a valid object instance."""
758 if isinstance(value, py3compat.string_types):
759 if isinstance(value, py3compat.string_types):
759 try:
760 try:
760 value = import_item(value)
761 value = import_item(value)
761 except ImportError:
762 except ImportError:
762 raise TraitError("The '%s' trait of %s instance must be a type, but "
763 raise TraitError("The '%s' trait of %s instance must be a type, but "
763 "%r could not be imported" % (self.name, obj, value))
764 "%r could not be imported" % (self.name, obj, value))
764 try:
765 try:
765 if issubclass(value, self.klass):
766 if issubclass(value, self.klass):
766 return value
767 return value
767 except:
768 except:
768 pass
769 pass
769
770
770 self.error(obj, value)
771 self.error(obj, value)
771
772
772 def info(self):
773 def info(self):
773 """ Returns a description of the trait."""
774 """ Returns a description of the trait."""
774 if isinstance(self.klass, py3compat.string_types):
775 if isinstance(self.klass, py3compat.string_types):
775 klass = self.klass
776 klass = self.klass
776 else:
777 else:
777 klass = self.klass.__name__
778 klass = self.klass.__name__
778 result = 'a subclass of ' + klass
779 result = 'a subclass of ' + klass
779 if self.allow_none:
780 if self.allow_none:
780 return result + ' or None'
781 return result + ' or None'
781 return result
782 return result
782
783
783 def instance_init(self, obj):
784 def instance_init(self, obj):
784 self._resolve_classes()
785 self._resolve_classes()
785 super(Type, self).instance_init(obj)
786 super(Type, self).instance_init(obj)
786
787
787 def _resolve_classes(self):
788 def _resolve_classes(self):
788 if isinstance(self.klass, py3compat.string_types):
789 if isinstance(self.klass, py3compat.string_types):
789 self.klass = import_item(self.klass)
790 self.klass = import_item(self.klass)
790 if isinstance(self.default_value, py3compat.string_types):
791 if isinstance(self.default_value, py3compat.string_types):
791 self.default_value = import_item(self.default_value)
792 self.default_value = import_item(self.default_value)
792
793
793 def get_default_value(self):
794 def get_default_value(self):
794 return self.default_value
795 return self.default_value
795
796
796
797
797 class DefaultValueGenerator(object):
798 class DefaultValueGenerator(object):
798 """A class for generating new default value instances."""
799 """A class for generating new default value instances."""
799
800
800 def __init__(self, *args, **kw):
801 def __init__(self, *args, **kw):
801 self.args = args
802 self.args = args
802 self.kw = kw
803 self.kw = kw
803
804
804 def generate(self, klass):
805 def generate(self, klass):
805 return klass(*self.args, **self.kw)
806 return klass(*self.args, **self.kw)
806
807
807
808
808 class Instance(ClassBasedTraitType):
809 class Instance(ClassBasedTraitType):
809 """A trait whose value must be an instance of a specified class.
810 """A trait whose value must be an instance of a specified class.
810
811
811 The value can also be an instance of a subclass of the specified class.
812 The value can also be an instance of a subclass of the specified class.
812
813
813 Subclasses can declare default classes by overriding the klass attribute
814 Subclasses can declare default classes by overriding the klass attribute
814 """
815 """
815
816
816 klass = None
817 klass = None
817
818
818 def __init__(self, klass=None, args=None, kw=None,
819 def __init__(self, klass=None, args=None, kw=None,
819 allow_none=True, **metadata ):
820 allow_none=True, **metadata ):
820 """Construct an Instance trait.
821 """Construct an Instance trait.
821
822
822 This trait allows values that are instances of a particular
823 This trait allows values that are instances of a particular
823 class or its sublclasses. Our implementation is quite different
824 class or its sublclasses. Our implementation is quite different
824 from that of enthough.traits as we don't allow instances to be used
825 from that of enthough.traits as we don't allow instances to be used
825 for klass and we handle the ``args`` and ``kw`` arguments differently.
826 for klass and we handle the ``args`` and ``kw`` arguments differently.
826
827
827 Parameters
828 Parameters
828 ----------
829 ----------
829 klass : class, str
830 klass : class, str
830 The class that forms the basis for the trait. Class names
831 The class that forms the basis for the trait. Class names
831 can also be specified as strings, like 'foo.bar.Bar'.
832 can also be specified as strings, like 'foo.bar.Bar'.
832 args : tuple
833 args : tuple
833 Positional arguments for generating the default value.
834 Positional arguments for generating the default value.
834 kw : dict
835 kw : dict
835 Keyword arguments for generating the default value.
836 Keyword arguments for generating the default value.
836 allow_none : bool
837 allow_none : bool
837 Indicates whether None is allowed as a value.
838 Indicates whether None is allowed as a value.
838
839
839 Notes
840 Notes
840 -----
841 -----
841 If both ``args`` and ``kw`` are None, then the default value is None.
842 If both ``args`` and ``kw`` are None, then the default value is None.
842 If ``args`` is a tuple and ``kw`` is a dict, then the default is
843 If ``args`` is a tuple and ``kw`` is a dict, then the default is
843 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
844 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
844 None, the None is replaced by ``()`` or ``{}``, respectively.
845 None, the None is replaced by ``()`` or ``{}``, respectively.
845 """
846 """
846 if klass is None:
847 if klass is None:
847 klass = self.klass
848 klass = self.klass
848
849
849 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
850 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
850 self.klass = klass
851 self.klass = klass
851 else:
852 else:
852 raise TraitError('The klass attribute must be a class'
853 raise TraitError('The klass attribute must be a class'
853 ' not: %r' % klass)
854 ' not: %r' % klass)
854
855
855 # self.klass is a class, so handle default_value
856 # self.klass is a class, so handle default_value
856 if args is None and kw is None:
857 if args is None and kw is None:
857 default_value = None
858 default_value = None
858 else:
859 else:
859 if args is None:
860 if args is None:
860 # kw is not None
861 # kw is not None
861 args = ()
862 args = ()
862 elif kw is None:
863 elif kw is None:
863 # args is not None
864 # args is not None
864 kw = {}
865 kw = {}
865
866
866 if not isinstance(kw, dict):
867 if not isinstance(kw, dict):
867 raise TraitError("The 'kw' argument must be a dict or None.")
868 raise TraitError("The 'kw' argument must be a dict or None.")
868 if not isinstance(args, tuple):
869 if not isinstance(args, tuple):
869 raise TraitError("The 'args' argument must be a tuple or None.")
870 raise TraitError("The 'args' argument must be a tuple or None.")
870
871
871 default_value = DefaultValueGenerator(*args, **kw)
872 default_value = DefaultValueGenerator(*args, **kw)
872
873
873 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
874 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
874
875
875 def validate(self, obj, value):
876 def validate(self, obj, value):
876 if isinstance(value, self.klass):
877 if isinstance(value, self.klass):
877 return value
878 return value
878 else:
879 else:
879 self.error(obj, value)
880 self.error(obj, value)
880
881
881 def info(self):
882 def info(self):
882 if isinstance(self.klass, py3compat.string_types):
883 if isinstance(self.klass, py3compat.string_types):
883 klass = self.klass
884 klass = self.klass
884 else:
885 else:
885 klass = self.klass.__name__
886 klass = self.klass.__name__
886 result = class_of(klass)
887 result = class_of(klass)
887 if self.allow_none:
888 if self.allow_none:
888 return result + ' or None'
889 return result + ' or None'
889
890
890 return result
891 return result
891
892
892 def instance_init(self, obj):
893 def instance_init(self, obj):
893 self._resolve_classes()
894 self._resolve_classes()
894 super(Instance, self).instance_init(obj)
895 super(Instance, self).instance_init(obj)
895
896
896 def _resolve_classes(self):
897 def _resolve_classes(self):
897 if isinstance(self.klass, py3compat.string_types):
898 if isinstance(self.klass, py3compat.string_types):
898 self.klass = import_item(self.klass)
899 self.klass = import_item(self.klass)
899
900
900 def get_default_value(self):
901 def get_default_value(self):
901 """Instantiate a default value instance.
902 """Instantiate a default value instance.
902
903
903 This is called when the containing HasTraits classes'
904 This is called when the containing HasTraits classes'
904 :meth:`__new__` method is called to ensure that a unique instance
905 :meth:`__new__` method is called to ensure that a unique instance
905 is created for each HasTraits instance.
906 is created for each HasTraits instance.
906 """
907 """
907 dv = self.default_value
908 dv = self.default_value
908 if isinstance(dv, DefaultValueGenerator):
909 if isinstance(dv, DefaultValueGenerator):
909 return dv.generate(self.klass)
910 return dv.generate(self.klass)
910 else:
911 else:
911 return dv
912 return dv
912
913
913
914
914 class This(ClassBasedTraitType):
915 class This(ClassBasedTraitType):
915 """A trait for instances of the class containing this trait.
916 """A trait for instances of the class containing this trait.
916
917
917 Because how how and when class bodies are executed, the ``This``
918 Because how how and when class bodies are executed, the ``This``
918 trait can only have a default value of None. This, and because we
919 trait can only have a default value of None. This, and because we
919 always validate default values, ``allow_none`` is *always* true.
920 always validate default values, ``allow_none`` is *always* true.
920 """
921 """
921
922
922 info_text = 'an instance of the same type as the receiver or None'
923 info_text = 'an instance of the same type as the receiver or None'
923
924
924 def __init__(self, **metadata):
925 def __init__(self, **metadata):
925 super(This, self).__init__(None, **metadata)
926 super(This, self).__init__(None, **metadata)
926
927
927 def validate(self, obj, value):
928 def validate(self, obj, value):
928 # What if value is a superclass of obj.__class__? This is
929 # What if value is a superclass of obj.__class__? This is
929 # complicated if it was the superclass that defined the This
930 # complicated if it was the superclass that defined the This
930 # trait.
931 # trait.
931 if isinstance(value, self.this_class) or (value is None):
932 if isinstance(value, self.this_class) or (value is None):
932 return value
933 return value
933 else:
934 else:
934 self.error(obj, value)
935 self.error(obj, value)
935
936
936
937
937 #-----------------------------------------------------------------------------
938 #-----------------------------------------------------------------------------
938 # Basic TraitTypes implementations/subclasses
939 # Basic TraitTypes implementations/subclasses
939 #-----------------------------------------------------------------------------
940 #-----------------------------------------------------------------------------
940
941
941
942
942 class Any(TraitType):
943 class Any(TraitType):
943 default_value = None
944 default_value = None
944 info_text = 'any value'
945 info_text = 'any value'
945
946
946
947
947 class Int(TraitType):
948 class Int(TraitType):
948 """An int trait."""
949 """An int trait."""
949
950
950 default_value = 0
951 default_value = 0
951 info_text = 'an int'
952 info_text = 'an int'
952
953
953 def validate(self, obj, value):
954 def validate(self, obj, value):
954 if isinstance(value, int):
955 if isinstance(value, int):
955 return value
956 return value
956 self.error(obj, value)
957 self.error(obj, value)
957
958
958 class CInt(Int):
959 class CInt(Int):
959 """A casting version of the int trait."""
960 """A casting version of the int trait."""
960
961
961 def validate(self, obj, value):
962 def validate(self, obj, value):
962 try:
963 try:
963 return int(value)
964 return int(value)
964 except:
965 except:
965 self.error(obj, value)
966 self.error(obj, value)
966
967
967 if py3compat.PY3:
968 if py3compat.PY3:
968 Long, CLong = Int, CInt
969 Long, CLong = Int, CInt
969 Integer = Int
970 Integer = Int
970 else:
971 else:
971 class Long(TraitType):
972 class Long(TraitType):
972 """A long integer trait."""
973 """A long integer trait."""
973
974
974 default_value = 0
975 default_value = 0
975 info_text = 'a long'
976 info_text = 'a long'
976
977
977 def validate(self, obj, value):
978 def validate(self, obj, value):
978 if isinstance(value, long):
979 if isinstance(value, long):
979 return value
980 return value
980 if isinstance(value, int):
981 if isinstance(value, int):
981 return long(value)
982 return long(value)
982 self.error(obj, value)
983 self.error(obj, value)
983
984
984
985
985 class CLong(Long):
986 class CLong(Long):
986 """A casting version of the long integer trait."""
987 """A casting version of the long integer trait."""
987
988
988 def validate(self, obj, value):
989 def validate(self, obj, value):
989 try:
990 try:
990 return long(value)
991 return long(value)
991 except:
992 except:
992 self.error(obj, value)
993 self.error(obj, value)
993
994
994 class Integer(TraitType):
995 class Integer(TraitType):
995 """An integer trait.
996 """An integer trait.
996
997
997 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
998 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
998
999
999 default_value = 0
1000 default_value = 0
1000 info_text = 'an integer'
1001 info_text = 'an integer'
1001
1002
1002 def validate(self, obj, value):
1003 def validate(self, obj, value):
1003 if isinstance(value, int):
1004 if isinstance(value, int):
1004 return value
1005 return value
1005 if isinstance(value, long):
1006 if isinstance(value, long):
1006 # downcast longs that fit in int:
1007 # downcast longs that fit in int:
1007 # note that int(n > sys.maxint) returns a long, so
1008 # note that int(n > sys.maxint) returns a long, so
1008 # we don't need a condition on this cast
1009 # we don't need a condition on this cast
1009 return int(value)
1010 return int(value)
1010 if sys.platform == "cli":
1011 if sys.platform == "cli":
1011 from System import Int64
1012 from System import Int64
1012 if isinstance(value, Int64):
1013 if isinstance(value, Int64):
1013 return int(value)
1014 return int(value)
1014 self.error(obj, value)
1015 self.error(obj, value)
1015
1016
1016
1017
1017 class Float(TraitType):
1018 class Float(TraitType):
1018 """A float trait."""
1019 """A float trait."""
1019
1020
1020 default_value = 0.0
1021 default_value = 0.0
1021 info_text = 'a float'
1022 info_text = 'a float'
1022
1023
1023 def validate(self, obj, value):
1024 def validate(self, obj, value):
1024 if isinstance(value, float):
1025 if isinstance(value, float):
1025 return value
1026 return value
1026 if isinstance(value, int):
1027 if isinstance(value, int):
1027 return float(value)
1028 return float(value)
1028 self.error(obj, value)
1029 self.error(obj, value)
1029
1030
1030
1031
1031 class CFloat(Float):
1032 class CFloat(Float):
1032 """A casting version of the float trait."""
1033 """A casting version of the float trait."""
1033
1034
1034 def validate(self, obj, value):
1035 def validate(self, obj, value):
1035 try:
1036 try:
1036 return float(value)
1037 return float(value)
1037 except:
1038 except:
1038 self.error(obj, value)
1039 self.error(obj, value)
1039
1040
1040 class Complex(TraitType):
1041 class Complex(TraitType):
1041 """A trait for complex numbers."""
1042 """A trait for complex numbers."""
1042
1043
1043 default_value = 0.0 + 0.0j
1044 default_value = 0.0 + 0.0j
1044 info_text = 'a complex number'
1045 info_text = 'a complex number'
1045
1046
1046 def validate(self, obj, value):
1047 def validate(self, obj, value):
1047 if isinstance(value, complex):
1048 if isinstance(value, complex):
1048 return value
1049 return value
1049 if isinstance(value, (float, int)):
1050 if isinstance(value, (float, int)):
1050 return complex(value)
1051 return complex(value)
1051 self.error(obj, value)
1052 self.error(obj, value)
1052
1053
1053
1054
1054 class CComplex(Complex):
1055 class CComplex(Complex):
1055 """A casting version of the complex number trait."""
1056 """A casting version of the complex number trait."""
1056
1057
1057 def validate (self, obj, value):
1058 def validate (self, obj, value):
1058 try:
1059 try:
1059 return complex(value)
1060 return complex(value)
1060 except:
1061 except:
1061 self.error(obj, value)
1062 self.error(obj, value)
1062
1063
1063 # We should always be explicit about whether we're using bytes or unicode, both
1064 # We should always be explicit about whether we're using bytes or unicode, both
1064 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1065 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1065 # we don't have a Str type.
1066 # we don't have a Str type.
1066 class Bytes(TraitType):
1067 class Bytes(TraitType):
1067 """A trait for byte strings."""
1068 """A trait for byte strings."""
1068
1069
1069 default_value = b''
1070 default_value = b''
1070 info_text = 'a bytes object'
1071 info_text = 'a bytes object'
1071
1072
1072 def validate(self, obj, value):
1073 def validate(self, obj, value):
1073 if isinstance(value, bytes):
1074 if isinstance(value, bytes):
1074 return value
1075 return value
1075 self.error(obj, value)
1076 self.error(obj, value)
1076
1077
1077
1078
1078 class CBytes(Bytes):
1079 class CBytes(Bytes):
1079 """A casting version of the byte string trait."""
1080 """A casting version of the byte string trait."""
1080
1081
1081 def validate(self, obj, value):
1082 def validate(self, obj, value):
1082 try:
1083 try:
1083 return bytes(value)
1084 return bytes(value)
1084 except:
1085 except:
1085 self.error(obj, value)
1086 self.error(obj, value)
1086
1087
1087
1088
1088 class Unicode(TraitType):
1089 class Unicode(TraitType):
1089 """A trait for unicode strings."""
1090 """A trait for unicode strings."""
1090
1091
1091 default_value = u''
1092 default_value = u''
1092 info_text = 'a unicode string'
1093 info_text = 'a unicode string'
1093
1094
1094 def validate(self, obj, value):
1095 def validate(self, obj, value):
1095 if isinstance(value, py3compat.unicode_type):
1096 if isinstance(value, py3compat.unicode_type):
1096 return value
1097 return value
1097 if isinstance(value, bytes):
1098 if isinstance(value, bytes):
1098 try:
1099 try:
1099 return value.decode('ascii', 'strict')
1100 return value.decode('ascii', 'strict')
1100 except UnicodeDecodeError:
1101 except UnicodeDecodeError:
1101 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1102 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1102 raise TraitError(msg.format(value, self.name, class_of(obj)))
1103 raise TraitError(msg.format(value, self.name, class_of(obj)))
1103 self.error(obj, value)
1104 self.error(obj, value)
1104
1105
1105
1106
1106 class CUnicode(Unicode):
1107 class CUnicode(Unicode):
1107 """A casting version of the unicode trait."""
1108 """A casting version of the unicode trait."""
1108
1109
1109 def validate(self, obj, value):
1110 def validate(self, obj, value):
1110 try:
1111 try:
1111 return py3compat.unicode_type(value)
1112 return py3compat.unicode_type(value)
1112 except:
1113 except:
1113 self.error(obj, value)
1114 self.error(obj, value)
1114
1115
1115
1116
1116 class ObjectName(TraitType):
1117 class ObjectName(TraitType):
1117 """A string holding a valid object name in this version of Python.
1118 """A string holding a valid object name in this version of Python.
1118
1119
1119 This does not check that the name exists in any scope."""
1120 This does not check that the name exists in any scope."""
1120 info_text = "a valid object identifier in Python"
1121 info_text = "a valid object identifier in Python"
1121
1122
1122 if py3compat.PY3:
1123 if py3compat.PY3:
1123 # Python 3:
1124 # Python 3:
1124 coerce_str = staticmethod(lambda _,s: s)
1125 coerce_str = staticmethod(lambda _,s: s)
1125
1126
1126 else:
1127 else:
1127 # Python 2:
1128 # Python 2:
1128 def coerce_str(self, obj, value):
1129 def coerce_str(self, obj, value):
1129 "In Python 2, coerce ascii-only unicode to str"
1130 "In Python 2, coerce ascii-only unicode to str"
1130 if isinstance(value, unicode):
1131 if isinstance(value, unicode):
1131 try:
1132 try:
1132 return str(value)
1133 return str(value)
1133 except UnicodeEncodeError:
1134 except UnicodeEncodeError:
1134 self.error(obj, value)
1135 self.error(obj, value)
1135 return value
1136 return value
1136
1137
1137 def validate(self, obj, value):
1138 def validate(self, obj, value):
1138 value = self.coerce_str(obj, value)
1139 value = self.coerce_str(obj, value)
1139
1140
1140 if isinstance(value, str) and py3compat.isidentifier(value):
1141 if isinstance(value, str) and py3compat.isidentifier(value):
1141 return value
1142 return value
1142 self.error(obj, value)
1143 self.error(obj, value)
1143
1144
1144 class DottedObjectName(ObjectName):
1145 class DottedObjectName(ObjectName):
1145 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1146 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1146 def validate(self, obj, value):
1147 def validate(self, obj, value):
1147 value = self.coerce_str(obj, value)
1148 value = self.coerce_str(obj, value)
1148
1149
1149 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1150 if isinstance(value, str) and py3compat.isidentifier(value, dotted=True):
1150 return value
1151 return value
1151 self.error(obj, value)
1152 self.error(obj, value)
1152
1153
1153
1154
1154 class Bool(TraitType):
1155 class Bool(TraitType):
1155 """A boolean (True, False) trait."""
1156 """A boolean (True, False) trait."""
1156
1157
1157 default_value = False
1158 default_value = False
1158 info_text = 'a boolean'
1159 info_text = 'a boolean'
1159
1160
1160 def validate(self, obj, value):
1161 def validate(self, obj, value):
1161 if isinstance(value, bool):
1162 if isinstance(value, bool):
1162 return value
1163 return value
1163 self.error(obj, value)
1164 self.error(obj, value)
1164
1165
1165
1166
1166 class CBool(Bool):
1167 class CBool(Bool):
1167 """A casting version of the boolean trait."""
1168 """A casting version of the boolean trait."""
1168
1169
1169 def validate(self, obj, value):
1170 def validate(self, obj, value):
1170 try:
1171 try:
1171 return bool(value)
1172 return bool(value)
1172 except:
1173 except:
1173 self.error(obj, value)
1174 self.error(obj, value)
1174
1175
1175
1176
1176 class Enum(TraitType):
1177 class Enum(TraitType):
1177 """An enum that whose value must be in a given sequence."""
1178 """An enum that whose value must be in a given sequence."""
1178
1179
1179 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1180 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1180 self.values = values
1181 self.values = values
1181 super(Enum, self).__init__(default_value, allow_none=allow_none, **metadata)
1182 super(Enum, self).__init__(default_value, allow_none=allow_none, **metadata)
1182
1183
1183 def validate(self, obj, value):
1184 def validate(self, obj, value):
1184 if value in self.values:
1185 if value in self.values:
1185 return value
1186 return value
1186 self.error(obj, value)
1187 self.error(obj, value)
1187
1188
1188 def info(self):
1189 def info(self):
1189 """ Returns a description of the trait."""
1190 """ Returns a description of the trait."""
1190 result = 'any of ' + repr(self.values)
1191 result = 'any of ' + repr(self.values)
1191 if self.allow_none:
1192 if self.allow_none:
1192 return result + ' or None'
1193 return result + ' or None'
1193 return result
1194 return result
1194
1195
1195 class CaselessStrEnum(Enum):
1196 class CaselessStrEnum(Enum):
1196 """An enum of strings that are caseless in validate."""
1197 """An enum of strings that are caseless in validate."""
1197
1198
1198 def validate(self, obj, value):
1199 def validate(self, obj, value):
1199 if not isinstance(value, py3compat.string_types):
1200 if not isinstance(value, py3compat.string_types):
1200 self.error(obj, value)
1201 self.error(obj, value)
1201
1202
1202 for v in self.values:
1203 for v in self.values:
1203 if v.lower() == value.lower():
1204 if v.lower() == value.lower():
1204 return v
1205 return v
1205 self.error(obj, value)
1206 self.error(obj, value)
1206
1207
1207 class Container(Instance):
1208 class Container(Instance):
1208 """An instance of a container (list, set, etc.)
1209 """An instance of a container (list, set, etc.)
1209
1210
1210 To be subclassed by overriding klass.
1211 To be subclassed by overriding klass.
1211 """
1212 """
1212 klass = None
1213 klass = None
1213 _cast_types = ()
1214 _cast_types = ()
1214 _valid_defaults = SequenceTypes
1215 _valid_defaults = SequenceTypes
1215 _trait = None
1216 _trait = None
1216
1217
1217 def __init__(self, trait=None, default_value=None, allow_none=True,
1218 def __init__(self, trait=None, default_value=None, allow_none=True,
1218 **metadata):
1219 **metadata):
1219 """Create a container trait type from a list, set, or tuple.
1220 """Create a container trait type from a list, set, or tuple.
1220
1221
1221 The default value is created by doing ``List(default_value)``,
1222 The default value is created by doing ``List(default_value)``,
1222 which creates a copy of the ``default_value``.
1223 which creates a copy of the ``default_value``.
1223
1224
1224 ``trait`` can be specified, which restricts the type of elements
1225 ``trait`` can be specified, which restricts the type of elements
1225 in the container to that TraitType.
1226 in the container to that TraitType.
1226
1227
1227 If only one arg is given and it is not a Trait, it is taken as
1228 If only one arg is given and it is not a Trait, it is taken as
1228 ``default_value``:
1229 ``default_value``:
1229
1230
1230 ``c = List([1,2,3])``
1231 ``c = List([1,2,3])``
1231
1232
1232 Parameters
1233 Parameters
1233 ----------
1234 ----------
1234
1235
1235 trait : TraitType [ optional ]
1236 trait : TraitType [ optional ]
1236 the type for restricting the contents of the Container. If unspecified,
1237 the type for restricting the contents of the Container. If unspecified,
1237 types are not checked.
1238 types are not checked.
1238
1239
1239 default_value : SequenceType [ optional ]
1240 default_value : SequenceType [ optional ]
1240 The default value for the Trait. Must be list/tuple/set, and
1241 The default value for the Trait. Must be list/tuple/set, and
1241 will be cast to the container type.
1242 will be cast to the container type.
1242
1243
1243 allow_none : Bool [ default True ]
1244 allow_none : Bool [ default True ]
1244 Whether to allow the value to be None
1245 Whether to allow the value to be None
1245
1246
1246 **metadata : any
1247 **metadata : any
1247 further keys for extensions to the Trait (e.g. config)
1248 further keys for extensions to the Trait (e.g. config)
1248
1249
1249 """
1250 """
1250 # allow List([values]):
1251 # allow List([values]):
1251 if default_value is None and not is_trait(trait):
1252 if default_value is None and not is_trait(trait):
1252 default_value = trait
1253 default_value = trait
1253 trait = None
1254 trait = None
1254
1255
1255 if default_value is None:
1256 if default_value is None:
1256 args = ()
1257 args = ()
1257 elif isinstance(default_value, self._valid_defaults):
1258 elif isinstance(default_value, self._valid_defaults):
1258 args = (default_value,)
1259 args = (default_value,)
1259 else:
1260 else:
1260 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1261 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1261
1262
1262 if is_trait(trait):
1263 if is_trait(trait):
1263 self._trait = trait() if isinstance(trait, type) else trait
1264 self._trait = trait() if isinstance(trait, type) else trait
1264 self._trait.name = 'element'
1265 self._trait.name = 'element'
1265 elif trait is not None:
1266 elif trait is not None:
1266 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1267 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1267
1268
1268 super(Container,self).__init__(klass=self.klass, args=args,
1269 super(Container,self).__init__(klass=self.klass, args=args,
1269 allow_none=allow_none, **metadata)
1270 allow_none=allow_none, **metadata)
1270
1271
1271 def element_error(self, obj, element, validator):
1272 def element_error(self, obj, element, validator):
1272 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1273 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1273 % (self.name, class_of(obj), validator.info(), repr_type(element))
1274 % (self.name, class_of(obj), validator.info(), repr_type(element))
1274 raise TraitError(e)
1275 raise TraitError(e)
1275
1276
1276 def validate(self, obj, value):
1277 def validate(self, obj, value):
1277 if isinstance(value, self._cast_types):
1278 if isinstance(value, self._cast_types):
1278 value = self.klass(value)
1279 value = self.klass(value)
1279 value = super(Container, self).validate(obj, value)
1280 value = super(Container, self).validate(obj, value)
1280 if value is None:
1281 if value is None:
1281 return value
1282 return value
1282
1283
1283 value = self.validate_elements(obj, value)
1284 value = self.validate_elements(obj, value)
1284
1285
1285 return value
1286 return value
1286
1287
1287 def validate_elements(self, obj, value):
1288 def validate_elements(self, obj, value):
1288 validated = []
1289 validated = []
1289 if self._trait is None or isinstance(self._trait, Any):
1290 if self._trait is None or isinstance(self._trait, Any):
1290 return value
1291 return value
1291 for v in value:
1292 for v in value:
1292 try:
1293 try:
1293 v = self._trait._validate(obj, v)
1294 v = self._trait._validate(obj, v)
1294 except TraitError:
1295 except TraitError:
1295 self.element_error(obj, v, self._trait)
1296 self.element_error(obj, v, self._trait)
1296 else:
1297 else:
1297 validated.append(v)
1298 validated.append(v)
1298 return self.klass(validated)
1299 return self.klass(validated)
1299
1300
1300 def instance_init(self, obj):
1301 def instance_init(self, obj):
1301 if isinstance(self._trait, Instance):
1302 if isinstance(self._trait, Instance):
1302 self._trait._resolve_classes()
1303 self._trait._resolve_classes()
1303 super(Container, self).instance_init(obj)
1304 super(Container, self).instance_init(obj)
1304
1305
1305
1306
1306 class List(Container):
1307 class List(Container):
1307 """An instance of a Python list."""
1308 """An instance of a Python list."""
1308 klass = list
1309 klass = list
1309 _cast_types = (tuple,)
1310 _cast_types = (tuple,)
1310
1311
1311 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1312 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize,
1312 allow_none=True, **metadata):
1313 allow_none=True, **metadata):
1313 """Create a List trait type from a list, set, or tuple.
1314 """Create a List trait type from a list, set, or tuple.
1314
1315
1315 The default value is created by doing ``List(default_value)``,
1316 The default value is created by doing ``List(default_value)``,
1316 which creates a copy of the ``default_value``.
1317 which creates a copy of the ``default_value``.
1317
1318
1318 ``trait`` can be specified, which restricts the type of elements
1319 ``trait`` can be specified, which restricts the type of elements
1319 in the container to that TraitType.
1320 in the container to that TraitType.
1320
1321
1321 If only one arg is given and it is not a Trait, it is taken as
1322 If only one arg is given and it is not a Trait, it is taken as
1322 ``default_value``:
1323 ``default_value``:
1323
1324
1324 ``c = List([1,2,3])``
1325 ``c = List([1,2,3])``
1325
1326
1326 Parameters
1327 Parameters
1327 ----------
1328 ----------
1328
1329
1329 trait : TraitType [ optional ]
1330 trait : TraitType [ optional ]
1330 the type for restricting the contents of the Container. If unspecified,
1331 the type for restricting the contents of the Container. If unspecified,
1331 types are not checked.
1332 types are not checked.
1332
1333
1333 default_value : SequenceType [ optional ]
1334 default_value : SequenceType [ optional ]
1334 The default value for the Trait. Must be list/tuple/set, and
1335 The default value for the Trait. Must be list/tuple/set, and
1335 will be cast to the container type.
1336 will be cast to the container type.
1336
1337
1337 minlen : Int [ default 0 ]
1338 minlen : Int [ default 0 ]
1338 The minimum length of the input list
1339 The minimum length of the input list
1339
1340
1340 maxlen : Int [ default sys.maxsize ]
1341 maxlen : Int [ default sys.maxsize ]
1341 The maximum length of the input list
1342 The maximum length of the input list
1342
1343
1343 allow_none : Bool [ default True ]
1344 allow_none : Bool [ default True ]
1344 Whether to allow the value to be None
1345 Whether to allow the value to be None
1345
1346
1346 **metadata : any
1347 **metadata : any
1347 further keys for extensions to the Trait (e.g. config)
1348 further keys for extensions to the Trait (e.g. config)
1348
1349
1349 """
1350 """
1350 self._minlen = minlen
1351 self._minlen = minlen
1351 self._maxlen = maxlen
1352 self._maxlen = maxlen
1352 super(List, self).__init__(trait=trait, default_value=default_value,
1353 super(List, self).__init__(trait=trait, default_value=default_value,
1353 allow_none=allow_none, **metadata)
1354 allow_none=allow_none, **metadata)
1354
1355
1355 def length_error(self, obj, value):
1356 def length_error(self, obj, value):
1356 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1357 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1357 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1358 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1358 raise TraitError(e)
1359 raise TraitError(e)
1359
1360
1360 def validate_elements(self, obj, value):
1361 def validate_elements(self, obj, value):
1361 length = len(value)
1362 length = len(value)
1362 if length < self._minlen or length > self._maxlen:
1363 if length < self._minlen or length > self._maxlen:
1363 self.length_error(obj, value)
1364 self.length_error(obj, value)
1364
1365
1365 return super(List, self).validate_elements(obj, value)
1366 return super(List, self).validate_elements(obj, value)
1366
1367
1367 def validate(self, obj, value):
1368 def validate(self, obj, value):
1368 value = super(List, self).validate(obj, value)
1369 value = super(List, self).validate(obj, value)
1369
1370
1370 value = self.validate_elements(obj, value)
1371 value = self.validate_elements(obj, value)
1371
1372
1372 return value
1373 return value
1373
1374
1374
1375
1375
1376
1376 class Set(List):
1377 class Set(List):
1377 """An instance of a Python set."""
1378 """An instance of a Python set."""
1378 klass = set
1379 klass = set
1379 _cast_types = (tuple, list)
1380 _cast_types = (tuple, list)
1380
1381
1381 class Tuple(Container):
1382 class Tuple(Container):
1382 """An instance of a Python tuple."""
1383 """An instance of a Python tuple."""
1383 klass = tuple
1384 klass = tuple
1384 _cast_types = (list,)
1385 _cast_types = (list,)
1385
1386
1386 def __init__(self, *traits, **metadata):
1387 def __init__(self, *traits, **metadata):
1387 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1388 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1388
1389
1389 Create a tuple from a list, set, or tuple.
1390 Create a tuple from a list, set, or tuple.
1390
1391
1391 Create a fixed-type tuple with Traits:
1392 Create a fixed-type tuple with Traits:
1392
1393
1393 ``t = Tuple(Int, Str, CStr)``
1394 ``t = Tuple(Int, Str, CStr)``
1394
1395
1395 would be length 3, with Int,Str,CStr for each element.
1396 would be length 3, with Int,Str,CStr for each element.
1396
1397
1397 If only one arg is given and it is not a Trait, it is taken as
1398 If only one arg is given and it is not a Trait, it is taken as
1398 default_value:
1399 default_value:
1399
1400
1400 ``t = Tuple((1,2,3))``
1401 ``t = Tuple((1,2,3))``
1401
1402
1402 Otherwise, ``default_value`` *must* be specified by keyword.
1403 Otherwise, ``default_value`` *must* be specified by keyword.
1403
1404
1404 Parameters
1405 Parameters
1405 ----------
1406 ----------
1406
1407
1407 *traits : TraitTypes [ optional ]
1408 *traits : TraitTypes [ optional ]
1408 the tsype for restricting the contents of the Tuple. If unspecified,
1409 the tsype for restricting the contents of the Tuple. If unspecified,
1409 types are not checked. If specified, then each positional argument
1410 types are not checked. If specified, then each positional argument
1410 corresponds to an element of the tuple. Tuples defined with traits
1411 corresponds to an element of the tuple. Tuples defined with traits
1411 are of fixed length.
1412 are of fixed length.
1412
1413
1413 default_value : SequenceType [ optional ]
1414 default_value : SequenceType [ optional ]
1414 The default value for the Tuple. Must be list/tuple/set, and
1415 The default value for the Tuple. Must be list/tuple/set, and
1415 will be cast to a tuple. If `traits` are specified, the
1416 will be cast to a tuple. If `traits` are specified, the
1416 `default_value` must conform to the shape and type they specify.
1417 `default_value` must conform to the shape and type they specify.
1417
1418
1418 allow_none : Bool [ default True ]
1419 allow_none : Bool [ default True ]
1419 Whether to allow the value to be None
1420 Whether to allow the value to be None
1420
1421
1421 **metadata : any
1422 **metadata : any
1422 further keys for extensions to the Trait (e.g. config)
1423 further keys for extensions to the Trait (e.g. config)
1423
1424
1424 """
1425 """
1425 default_value = metadata.pop('default_value', None)
1426 default_value = metadata.pop('default_value', None)
1426 allow_none = metadata.pop('allow_none', True)
1427 allow_none = metadata.pop('allow_none', True)
1427
1428
1428 # allow Tuple((values,)):
1429 # allow Tuple((values,)):
1429 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1430 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1430 default_value = traits[0]
1431 default_value = traits[0]
1431 traits = ()
1432 traits = ()
1432
1433
1433 if default_value is None:
1434 if default_value is None:
1434 args = ()
1435 args = ()
1435 elif isinstance(default_value, self._valid_defaults):
1436 elif isinstance(default_value, self._valid_defaults):
1436 args = (default_value,)
1437 args = (default_value,)
1437 else:
1438 else:
1438 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1439 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1439
1440
1440 self._traits = []
1441 self._traits = []
1441 for trait in traits:
1442 for trait in traits:
1442 t = trait() if isinstance(trait, type) else trait
1443 t = trait() if isinstance(trait, type) else trait
1443 t.name = 'element'
1444 t.name = 'element'
1444 self._traits.append(t)
1445 self._traits.append(t)
1445
1446
1446 if self._traits and default_value is None:
1447 if self._traits and default_value is None:
1447 # don't allow default to be an empty container if length is specified
1448 # don't allow default to be an empty container if length is specified
1448 args = None
1449 args = None
1449 super(Container,self).__init__(klass=self.klass, args=args,
1450 super(Container,self).__init__(klass=self.klass, args=args,
1450 allow_none=allow_none, **metadata)
1451 allow_none=allow_none, **metadata)
1451
1452
1452 def validate_elements(self, obj, value):
1453 def validate_elements(self, obj, value):
1453 if not self._traits:
1454 if not self._traits:
1454 # nothing to validate
1455 # nothing to validate
1455 return value
1456 return value
1456 if len(value) != len(self._traits):
1457 if len(value) != len(self._traits):
1457 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1458 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1458 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1459 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1459 raise TraitError(e)
1460 raise TraitError(e)
1460
1461
1461 validated = []
1462 validated = []
1462 for t,v in zip(self._traits, value):
1463 for t,v in zip(self._traits, value):
1463 try:
1464 try:
1464 v = t._validate(obj, v)
1465 v = t._validate(obj, v)
1465 except TraitError:
1466 except TraitError:
1466 self.element_error(obj, v, t)
1467 self.element_error(obj, v, t)
1467 else:
1468 else:
1468 validated.append(v)
1469 validated.append(v)
1469 return tuple(validated)
1470 return tuple(validated)
1470
1471
1471
1472
1472 class Dict(Instance):
1473 class Dict(Instance):
1473 """An instance of a Python dict."""
1474 """An instance of a Python dict."""
1474
1475
1475 def __init__(self, default_value=None, allow_none=True, **metadata):
1476 def __init__(self, default_value=None, allow_none=True, **metadata):
1476 """Create a dict trait type from a dict.
1477 """Create a dict trait type from a dict.
1477
1478
1478 The default value is created by doing ``dict(default_value)``,
1479 The default value is created by doing ``dict(default_value)``,
1479 which creates a copy of the ``default_value``.
1480 which creates a copy of the ``default_value``.
1480 """
1481 """
1481 if default_value is None:
1482 if default_value is None:
1482 args = ((),)
1483 args = ((),)
1483 elif isinstance(default_value, dict):
1484 elif isinstance(default_value, dict):
1484 args = (default_value,)
1485 args = (default_value,)
1485 elif isinstance(default_value, SequenceTypes):
1486 elif isinstance(default_value, SequenceTypes):
1486 args = (default_value,)
1487 args = (default_value,)
1487 else:
1488 else:
1488 raise TypeError('default value of Dict was %s' % default_value)
1489 raise TypeError('default value of Dict was %s' % default_value)
1489
1490
1490 super(Dict,self).__init__(klass=dict, args=args,
1491 super(Dict,self).__init__(klass=dict, args=args,
1491 allow_none=allow_none, **metadata)
1492 allow_none=allow_none, **metadata)
1492
1493
1494
1495 class EventfulDict(Instance):
1496 """An instance of an EventfulDict."""
1497
1498 def __init__(self, default_value=None, allow_none=True, **metadata):
1499 """Create a EventfulDict trait type from a dict.
1500
1501 The default value is created by doing
1502 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1503 ``default_value``.
1504 """
1505 if default_value is None:
1506 args = ((),)
1507 elif isinstance(default_value, dict):
1508 args = (default_value,)
1509 elif isinstance(default_value, SequenceTypes):
1510 args = (default_value,)
1511 else:
1512 raise TypeError('default value of EventfulDict was %s' % default_value)
1513
1514 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1515 allow_none=allow_none, **metadata)
1516
1517
1518 class EventfulList(Instance):
1519 """An instance of an EventfulList."""
1520
1521 def __init__(self, default_value=None, allow_none=True, **metadata):
1522 """Create a EventfulList trait type from a dict.
1523
1524 The default value is created by doing
1525 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1526 ``default_value``.
1527 """
1528 if default_value is None:
1529 args = ((),)
1530 else:
1531 args = (default_value,)
1532
1533 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1534 allow_none=allow_none, **metadata)
1535
1536
1493 class TCPAddress(TraitType):
1537 class TCPAddress(TraitType):
1494 """A trait for an (ip, port) tuple.
1538 """A trait for an (ip, port) tuple.
1495
1539
1496 This allows for both IPv4 IP addresses as well as hostnames.
1540 This allows for both IPv4 IP addresses as well as hostnames.
1497 """
1541 """
1498
1542
1499 default_value = ('127.0.0.1', 0)
1543 default_value = ('127.0.0.1', 0)
1500 info_text = 'an (ip, port) tuple'
1544 info_text = 'an (ip, port) tuple'
1501
1545
1502 def validate(self, obj, value):
1546 def validate(self, obj, value):
1503 if isinstance(value, tuple):
1547 if isinstance(value, tuple):
1504 if len(value) == 2:
1548 if len(value) == 2:
1505 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1549 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1506 port = value[1]
1550 port = value[1]
1507 if port >= 0 and port <= 65535:
1551 if port >= 0 and port <= 65535:
1508 return value
1552 return value
1509 self.error(obj, value)
1553 self.error(obj, value)
1510
1554
1511 class CRegExp(TraitType):
1555 class CRegExp(TraitType):
1512 """A casting compiled regular expression trait.
1556 """A casting compiled regular expression trait.
1513
1557
1514 Accepts both strings and compiled regular expressions. The resulting
1558 Accepts both strings and compiled regular expressions. The resulting
1515 attribute will be a compiled regular expression."""
1559 attribute will be a compiled regular expression."""
1516
1560
1517 info_text = 'a regular expression'
1561 info_text = 'a regular expression'
1518
1562
1519 def validate(self, obj, value):
1563 def validate(self, obj, value):
1520 try:
1564 try:
1521 return re.compile(value)
1565 return re.compile(value)
1522 except:
1566 except:
1523 self.error(obj, value)
1567 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now