##// END OF EJS Templates
Added on_events convinience method
Jonathan Frederic -
Show More
@@ -1,292 +1,322 b''
1 """Contains eventful dict and list implementations."""
1 """Contains eventful dict and list implementations."""
2
2
3 # void function used as a callback placeholder.
3 # void function used as a callback placeholder.
4 def _void(*p, **k): return None
4 def _void(*p, **k): return None
5
5
6 class EventfulDict(dict):
6 class EventfulDict(dict):
7 """Eventful dictionary.
7 """Eventful dictionary.
8
8
9 This class inherits from the Python intrinsic dictionary class, dict. It
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
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
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
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`
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
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
15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
16 to the same handlers if necessary.
16 to the same handlers if necessary.
17
17
18 See the on_add, on_set, and on_del methods for registering an event
18 See the on_events, on_add, on_set, and on_del methods for registering
19 handler."""
19 event handlers."""
20
20
21 def __init__(self, *args, **kwargs):
21 def __init__(self, *args, **kwargs):
22 """Public constructor"""
22 """Public constructor"""
23 self._add_callback = _void
23 self._add_callback = _void
24 self._del_callback = _void
24 self._del_callback = _void
25 self._set_callback = _void
25 self._set_callback = _void
26 dict.__init__(self, *args, **kwargs)
26 dict.__init__(self, *args, **kwargs)
27
27
28 def on_events(self, *callbacks):
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 or None
35 set_callback: callback or None
36 del_callback: callback or None"""
37 registers = ['on_add', 'on_set', 'on_del']
38 if len(callbacks) < len(registers):
39 raise ValueError('on_events takes {} callbacks'.format(len(registers)))
40 [getattr(self, n)(callbacks[i]) for i, n in enumerate(registers)]
41
28 def on_add(self, callback):
42 def on_add(self, callback):
29 """Register a callback for when an item is added to the dict.
43 """Register a callback for when an item is added to the dict.
30
44
31 Allows the listener to detect when items are added to the dictionary and
45 Allows the listener to detect when items are added to the dictionary and
32 optionally cancel the addition.
46 optionally cancel the addition.
33
47
34 callback: callable or None
48 callback: callable or None
35 If you want to ignore the addition event, pass None as the callback.
49 If you want to ignore the addition event, pass None as the callback.
36 The callback should have a signature of callback(key, value). The
50 The callback should have a signature of callback(key, value). The
37 callback should return a boolean True if the additon should be
51 callback should return a boolean True if the additon should be
38 canceled, False or None otherwise."""
52 canceled, False or None otherwise."""
39 if callable(callback):
53 if callable(callback):
40 self._add_callback = callback
54 self._add_callback = callback
41 else:
55 else:
42 self._add_callback = _void
56 self._add_callback = _void
43
57
44 def on_del(self, callback):
58 def on_del(self, callback):
45 """Register a callback for when an item is deleted from the dict.
59 """Register a callback for when an item is deleted from the dict.
46
60
47 Allows the listener to detect when items are deleted from the dictionary
61 Allows the listener to detect when items are deleted from the dictionary
48 and optionally cancel the deletion.
62 and optionally cancel the deletion.
49
63
50 callback: callable or None
64 callback: callable or None
51 If you want to ignore the deletion event, pass None as the callback.
65 If you want to ignore the deletion event, pass None as the callback.
52 The callback should have a signature of callback(key). The
66 The callback should have a signature of callback(key). The
53 callback should return a boolean True if the deletion should be
67 callback should return a boolean True if the deletion should be
54 canceled, False or None otherwise."""
68 canceled, False or None otherwise."""
55 if callable(callback):
69 if callable(callback):
56 self._del_callback = callback
70 self._del_callback = callback
57 else:
71 else:
58 self._del_callback = _void
72 self._del_callback = _void
59
73
60 def on_set(self, callback):
74 def on_set(self, callback):
61 """Register a callback for when an item is changed in the dict.
75 """Register a callback for when an item is changed in the dict.
62
76
63 Allows the listener to detect when items are changed in the dictionary
77 Allows the listener to detect when items are changed in the dictionary
64 and optionally cancel the change.
78 and optionally cancel the change.
65
79
66 callback: callable or None
80 callback: callable or None
67 If you want to ignore the change event, pass None as the callback.
81 If you want to ignore the change event, pass None as the callback.
68 The callback should have a signature of callback(key, value). The
82 The callback should have a signature of callback(key, value). The
69 callback should return a boolean True if the change should be
83 callback should return a boolean True if the change should be
70 canceled, False or None otherwise."""
84 canceled, False or None otherwise."""
71 if callable(callback):
85 if callable(callback):
72 self._set_callback = callback
86 self._set_callback = callback
73 else:
87 else:
74 self._set_callback = _void
88 self._set_callback = _void
75
89
76 def pop(self, key):
90 def pop(self, key):
77 """Returns the value of an item in the dictionary and then deletes the
91 """Returns the value of an item in the dictionary and then deletes the
78 item from the dictionary."""
92 item from the dictionary."""
79 if self._can_del(key):
93 if self._can_del(key):
80 return dict.pop(self, key)
94 return dict.pop(self, key)
81 else:
95 else:
82 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
96 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
83
97
84 def popitem(self):
98 def popitem(self):
85 """Pop the next key/value pair from the dictionary."""
99 """Pop the next key/value pair from the dictionary."""
86 key = next(iter(self))
100 key = next(iter(self))
87 return key, self.pop(key)
101 return key, self.pop(key)
88
102
89 def update(self, other_dict):
103 def update(self, other_dict):
90 """Copy the key/value pairs from another dictionary into this dictionary,
104 """Copy the key/value pairs from another dictionary into this dictionary,
91 overwriting any conflicting keys in this dictionary."""
105 overwriting any conflicting keys in this dictionary."""
92 for (key, value) in other_dict.items():
106 for (key, value) in other_dict.items():
93 self[key] = value
107 self[key] = value
94
108
95 def clear(self):
109 def clear(self):
96 """Clear the dictionary."""
110 """Clear the dictionary."""
97 for key in list(self.keys()):
111 for key in list(self.keys()):
98 del self[key]
112 del self[key]
99
113
100 def __setitem__(self, key, value):
114 def __setitem__(self, key, value):
101 if (key in self and self._can_set(key, value)) or \
115 if (key in self and self._can_set(key, value)) or \
102 (key not in self and self._can_add(key, value)):
116 (key not in self and self._can_add(key, value)):
103 return dict.__setitem__(self, key, value)
117 return dict.__setitem__(self, key, value)
104
118
105 def __delitem__(self, key):
119 def __delitem__(self, key):
106 if self._can_del(key):
120 if self._can_del(key):
107 return dict.__delitem__(self, key)
121 return dict.__delitem__(self, key)
108
122
109 def _can_add(self, key, value):
123 def _can_add(self, key, value):
110 """Check if the item can be added to the dict."""
124 """Check if the item can be added to the dict."""
111 return not bool(self._add_callback(key, value))
125 return not bool(self._add_callback(key, value))
112
126
113 def _can_del(self, key):
127 def _can_del(self, key):
114 """Check if the item can be deleted from the dict."""
128 """Check if the item can be deleted from the dict."""
115 return not bool(self._del_callback(key))
129 return not bool(self._del_callback(key))
116
130
117 def _can_set(self, key, value):
131 def _can_set(self, key, value):
118 """Check if the item can be changed in the dict."""
132 """Check if the item can be changed in the dict."""
119 return not bool(self._set_callback(key, value))
133 return not bool(self._set_callback(key, value))
120
134
121
135
122 class EventfulList(list):
136 class EventfulList(list):
123 """Eventful list.
137 """Eventful list.
124
138
125 This class inherits from the Python intrinsic `list` class. It adds events
139 This class inherits from the Python intrinsic `list` class. It adds events
126 that allow you to listen for actions that modify the list. You can
140 that allow you to listen for actions that modify the list. You can
127 optionally cancel the actions.
141 optionally cancel the actions.
128
142
129 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
143 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
130 registering an event handler.
144 registering an event handler.
131
145
132 Some of the method docstrings were taken from the Python documentation at
146 Some of the method docstrings were taken from the Python documentation at
133 https://docs.python.org/2/tutorial/datastructures.html"""
147 https://docs.python.org/2/tutorial/datastructures.html"""
134
148
135 def __init__(self, *pargs, **kwargs):
149 def __init__(self, *pargs, **kwargs):
136 """Public constructor"""
150 """Public constructor"""
137 self._insert_callback = _void
151 self._insert_callback = _void
138 self._set_callback = _void
152 self._set_callback = _void
139 self._del_callback = _void
153 self._del_callback = _void
140 self._sort_callback = _void
154 self._sort_callback = _void
141 self._reverse_callback = _void
155 self._reverse_callback = _void
142 list.__init__(self, *pargs, **kwargs)
156 list.__init__(self, *pargs, **kwargs)
143
157
158 def on_events(self, *callbacks):
159 """Register callbacks for add, set, and del actions.
160
161 See the doctstrings for on_(insert/set/del/reverse/sort) for details
162 about each callback.
163
164 insert_callback: callback or None
165 set_callback: callback or None
166 del_callback: callback or None
167 reverse_callback: callback or None
168 sort_callback: callback or None"""
169 registers = ['on_insert', 'on_set', 'on_del', 'on_reverse', 'on_sort']
170 if len(callbacks) < len(registers):
171 raise ValueError('on_events takes {} callbacks'.format(len(registers)))
172 [getattr(self, n)(callbacks[i]) for i, n in enumerate(registers)]
173
144 def on_insert(self, callback):
174 def on_insert(self, callback):
145 """Register a callback for when an item is inserted into the list.
175 """Register a callback for when an item is inserted into the list.
146
176
147 Allows the listener to detect when items are inserted into the list and
177 Allows the listener to detect when items are inserted into the list and
148 optionally cancel the insertion.
178 optionally cancel the insertion.
149
179
150 callback: callable or None
180 callback: callable or None
151 If you want to ignore the insertion event, pass None as the callback.
181 If you want to ignore the insertion event, pass None as the callback.
152 The callback should have a signature of callback(index, value). The
182 The callback should have a signature of callback(index, value). The
153 callback should return a boolean True if the insertion should be
183 callback should return a boolean True if the insertion should be
154 canceled, False or None otherwise."""
184 canceled, False or None otherwise."""
155 if callable(callback):
185 if callable(callback):
156 self._insert_callback = callback
186 self._insert_callback = callback
157 else:
187 else:
158 self._insert_callback = _void
188 self._insert_callback = _void
159
189
160 def on_del(self, callback):
190 def on_del(self, callback):
161 """Register a callback for item deletion.
191 """Register a callback for item deletion.
162
192
163 Allows the listener to detect when items are deleted from the list and
193 Allows the listener to detect when items are deleted from the list and
164 optionally cancel the deletion.
194 optionally cancel the deletion.
165
195
166 callback: callable or None
196 callback: callable or None
167 If you want to ignore the deletion event, pass None as the callback.
197 If you want to ignore the deletion event, pass None as the callback.
168 The callback should have a signature of callback(index). The
198 The callback should have a signature of callback(index). The
169 callback should return a boolean True if the deletion should be
199 callback should return a boolean True if the deletion should be
170 canceled, False or None otherwise."""
200 canceled, False or None otherwise."""
171 if callable(callback):
201 if callable(callback):
172 self._del_callback = callback
202 self._del_callback = callback
173 else:
203 else:
174 self._del_callback = _void
204 self._del_callback = _void
175
205
176 def on_set(self, callback):
206 def on_set(self, callback):
177 """Register a callback for items are set.
207 """Register a callback for items are set.
178
208
179 Allows the listener to detect when items are set and optionally cancel
209 Allows the listener to detect when items are set and optionally cancel
180 the setting. Note, `set` is also called when one or more items are
210 the setting. Note, `set` is also called when one or more items are
181 added to the end of the list.
211 added to the end of the list.
182
212
183 callback: callable or None
213 callback: callable or None
184 If you want to ignore the set event, pass None as the callback.
214 If you want to ignore the set event, pass None as the callback.
185 The callback should have a signature of callback(index, value). The
215 The callback should have a signature of callback(index, value). The
186 callback should return a boolean True if the set should be
216 callback should return a boolean True if the set should be
187 canceled, False or None otherwise."""
217 canceled, False or None otherwise."""
188 if callable(callback):
218 if callable(callback):
189 self._set_callback = callback
219 self._set_callback = callback
190 else:
220 else:
191 self._set_callback = _void
221 self._set_callback = _void
192
222
193 def on_reverse(self, callback):
223 def on_reverse(self, callback):
194 """Register a callback for list reversal.
224 """Register a callback for list reversal.
195
225
196 callback: callable or None
226 callback: callable or None
197 If you want to ignore the reverse event, pass None as the callback.
227 If you want to ignore the reverse event, pass None as the callback.
198 The callback should have a signature of callback(). The
228 The callback should have a signature of callback(). The
199 callback should return a boolean True if the reverse should be
229 callback should return a boolean True if the reverse should be
200 canceled, False or None otherwise."""
230 canceled, False or None otherwise."""
201 if callable(callback):
231 if callable(callback):
202 self._reverse_callback = callback
232 self._reverse_callback = callback
203 else:
233 else:
204 self._reverse_callback = _void
234 self._reverse_callback = _void
205
235
206 def on_sort(self, callback):
236 def on_sort(self, callback):
207 """Register a callback for sortting of the list.
237 """Register a callback for sortting of the list.
208
238
209 callback: callable or None
239 callback: callable or None
210 If you want to ignore the sort event, pass None as the callback.
240 If you want to ignore the sort event, pass None as the callback.
211 The callback signature should match that of Python list's `.sort`
241 The callback signature should match that of Python list's `.sort`
212 method or `callback(*pargs, **kwargs)` as a catch all. The callback
242 method or `callback(*pargs, **kwargs)` as a catch all. The callback
213 should return a boolean True if the reverse should be canceled,
243 should return a boolean True if the reverse should be canceled,
214 False or None otherwise."""
244 False or None otherwise."""
215 if callable(callback):
245 if callable(callback):
216 self._sort_callback = callback
246 self._sort_callback = callback
217 else:
247 else:
218 self._sort_callback = _void
248 self._sort_callback = _void
219
249
220 def append(self, x):
250 def append(self, x):
221 """Add an item to the end of the list."""
251 """Add an item to the end of the list."""
222 self[len(self):] = [x]
252 self[len(self):] = [x]
223
253
224 def extend(self, L):
254 def extend(self, L):
225 """Extend the list by appending all the items in the given list."""
255 """Extend the list by appending all the items in the given list."""
226 self[len(self):] = L
256 self[len(self):] = L
227
257
228 def remove(self, x):
258 def remove(self, x):
229 """Remove the first item from the list whose value is x. It is an error
259 """Remove the first item from the list whose value is x. It is an error
230 if there is no such item."""
260 if there is no such item."""
231 del self[self.index(x)]
261 del self[self.index(x)]
232
262
233 def pop(self, i=None):
263 def pop(self, i=None):
234 """Remove the item at the given position in the list, and return it. If
264 """Remove the item at the given position in the list, and return it. If
235 no index is specified, a.pop() removes and returns the last item in the
265 no index is specified, a.pop() removes and returns the last item in the
236 list."""
266 list."""
237 if i is None:
267 if i is None:
238 i = len(self) - 1
268 i = len(self) - 1
239 val = self[i]
269 val = self[i]
240 del self[i]
270 del self[i]
241 return val
271 return val
242
272
243 def reverse(self):
273 def reverse(self):
244 """Reverse the elements of the list, in place."""
274 """Reverse the elements of the list, in place."""
245 if self._can_reverse():
275 if self._can_reverse():
246 list.reverse(self)
276 list.reverse(self)
247
277
248 def insert(self, index, value):
278 def insert(self, index, value):
249 """Insert an item at a given position. The first argument is the index
279 """Insert an item at a given position. The first argument is the index
250 of the element before which to insert, so a.insert(0, x) inserts at the
280 of the element before which to insert, so a.insert(0, x) inserts at the
251 front of the list, and a.insert(len(a), x) is equivalent to
281 front of the list, and a.insert(len(a), x) is equivalent to
252 a.append(x)."""
282 a.append(x)."""
253 if self._can_insert(index, value):
283 if self._can_insert(index, value):
254 list.insert(self, index, value)
284 list.insert(self, index, value)
255
285
256 def sort(self, *pargs, **kwargs):
286 def sort(self, *pargs, **kwargs):
257 """Sort the items of the list in place (the arguments can be used for
287 """Sort the items of the list in place (the arguments can be used for
258 sort customization, see Python's sorted() for their explanation)."""
288 sort customization, see Python's sorted() for their explanation)."""
259 if self._can_sort(*pargs, **kwargs):
289 if self._can_sort(*pargs, **kwargs):
260 list.sort(self, *pargs, **kwargs)
290 list.sort(self, *pargs, **kwargs)
261
291
262 def __delitem__(self, index):
292 def __delitem__(self, index):
263 if self._can_del(index):
293 if self._can_del(index):
264 list.__delitem__(self, index)
294 list.__delitem__(self, index)
265
295
266 def __setitem__(self, index, value):
296 def __setitem__(self, index, value):
267 if self._can_set(index, value):
297 if self._can_set(index, value):
268 list.__setitem__(self, index, value)
298 list.__setitem__(self, index, value)
269
299
270 def __setslice__(self, start, end, value):
300 def __setslice__(self, start, end, value):
271 if self._can_set(slice(start, end), value):
301 if self._can_set(slice(start, end), value):
272 list.__setslice__(self, start, end, value)
302 list.__setslice__(self, start, end, value)
273
303
274 def _can_insert(self, index, value):
304 def _can_insert(self, index, value):
275 """Check if the item can be inserted."""
305 """Check if the item can be inserted."""
276 return not bool(self._insert_callback(index, value))
306 return not bool(self._insert_callback(index, value))
277
307
278 def _can_del(self, index):
308 def _can_del(self, index):
279 """Check if the item can be deleted."""
309 """Check if the item can be deleted."""
280 return not bool(self._del_callback(index))
310 return not bool(self._del_callback(index))
281
311
282 def _can_set(self, index, value):
312 def _can_set(self, index, value):
283 """Check if the item can be set."""
313 """Check if the item can be set."""
284 return not bool(self._set_callback(index, value))
314 return not bool(self._set_callback(index, value))
285
315
286 def _can_reverse(self):
316 def _can_reverse(self):
287 """Check if the list can be reversed."""
317 """Check if the list can be reversed."""
288 return not bool(self._reverse_callback())
318 return not bool(self._reverse_callback())
289
319
290 def _can_sort(self, *pargs, **kwargs):
320 def _can_sort(self, *pargs, **kwargs):
291 """Check if the list can be sorted."""
321 """Check if the list can be sorted."""
292 return not bool(self._sort_callback(*pargs, **kwargs))
322 return not bool(self._sort_callback(*pargs, **kwargs))
@@ -1,1253 +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, EventfulList, EventfulDict
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 = ['(', 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):
1175 class TestEventful(TestCase):
1176
1176
1177 def test_list(self):
1177 def test_list(self):
1178 """Does the EventfulList work?"""
1178 """Does the EventfulList work?"""
1179 event_cache = []
1179 event_cache = []
1180
1180
1181 class A(HasTraits):
1181 class A(HasTraits):
1182 x = EventfulList([c for c in 'abc'])
1182 x = EventfulList([c for c in 'abc'])
1183 a = A()
1183 a = A()
1184
1184 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1185 def handle_insert(index, value):
1185 lambda i, x: event_cache.append('set'), \
1186 event_cache.append('insert')
1186 lambda i: event_cache.append('del'), \
1187 def handle_del(index):
1187 lambda: event_cache.append('reverse'), \
1188 event_cache.append('del')
1188 lambda *p, **k: event_cache.append('sort'))
1189 def handle_set(index, value):
1190 event_cache.append('set')
1191 def handle_reverse():
1192 event_cache.append('reverse')
1193 def handle_sort(*pargs, **kwargs):
1194 event_cache.append('sort')
1195 a.x.on_insert(handle_insert)
1196 a.x.on_del(handle_del)
1197 a.x.on_set(handle_set)
1198 a.x.on_reverse(handle_reverse)
1199 a.x.on_sort(handle_sort)
1200
1189
1201 a.x.remove('c')
1190 a.x.remove('c')
1202 # ab
1191 # ab
1203 a.x.insert(0, 'z')
1192 a.x.insert(0, 'z')
1204 # zab
1193 # zab
1205 del a.x[1]
1194 del a.x[1]
1206 # zb
1195 # zb
1207 a.x.reverse()
1196 a.x.reverse()
1208 # bz
1197 # bz
1209 a.x[1] = 'o'
1198 a.x[1] = 'o'
1210 # bo
1199 # bo
1211 a.x.append('a')
1200 a.x.append('a')
1212 # boa
1201 # boa
1213 a.x.sort()
1202 a.x.sort()
1214 # abo
1203 # abo
1215
1204
1216 # Were the correct events captured?
1205 # Were the correct events captured?
1217 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1206 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1218
1207
1219 # Is the output correct?
1208 # Is the output correct?
1220 self.assertEqual(a.x, [c for c in 'abo'])
1209 self.assertEqual(a.x, [c for c in 'abo'])
1221
1210
1222 def test_dict(self):
1211 def test_dict(self):
1223 """Does the EventfulDict work?"""
1212 """Does the EventfulDict work?"""
1224 event_cache = []
1213 event_cache = []
1225
1214
1226 class A(HasTraits):
1215 class A(HasTraits):
1227 x = EventfulDict({c: c for c in 'abc'})
1216 x = EventfulDict({c: c for c in 'abc'})
1228 a = A()
1217 a = A()
1229
1218 a.x.on_events(lambda k, v: event_cache.append('add'), \
1230 def handle_add(key, value):
1219 lambda k, v: event_cache.append('set'), \
1231 event_cache.append('add')
1220 lambda k: event_cache.append('del'))
1232 def handle_del(key):
1233 event_cache.append('del')
1234 def handle_set(key, value):
1235 event_cache.append('set')
1236 a.x.on_add(handle_add)
1237 a.x.on_del(handle_del)
1238 a.x.on_set(handle_set)
1239
1221
1240 del a.x['c']
1222 del a.x['c']
1241 # ab
1223 # ab
1242 a.x['z'] = 1
1224 a.x['z'] = 1
1243 # abz
1225 # abz
1244 a.x['z'] = 'z'
1226 a.x['z'] = 'z'
1245 # abz
1227 # abz
1246 a.x.pop('a')
1228 a.x.pop('a')
1247 # bz
1229 # bz
1248
1230
1249 # Were the correct events captured?
1231 # Were the correct events captured?
1250 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1232 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1251
1233
1252 # Is the output correct?
1234 # Is the output correct?
1253 self.assertEqual(a.x, {c: c for c in 'bz'})
1235 self.assertEqual(a.x, {c: c for c in 'bz'})
General Comments 0
You need to be logged in to leave comments. Login now