##// END OF EJS Templates
Added on_events convinience method
Jonathan Frederic -
Show More
@@ -1,292 +1,322
1 1 """Contains eventful dict and list implementations."""
2 2
3 3 # void function used as a callback placeholder.
4 4 def _void(*p, **k): return None
5 5
6 6 class EventfulDict(dict):
7 7 """Eventful dictionary.
8 8
9 9 This class inherits from the Python intrinsic dictionary class, dict. It
10 10 adds events to the get, set, and del actions and optionally allows you to
11 11 intercept and cancel these actions. The eventfulness isn't recursive. In
12 12 other words, if you add a dict as a child, the events of that dict won't be
13 13 listened to. If you find you need something recursive, listen to the `add`
14 14 and `set` methods, and then cancel `dict` values from being set, and instead
15 15 set `EventfulDict`s that wrap those `dict`s. Then you can wire the events
16 16 to the same handlers if necessary.
17 17
18 See the on_add, on_set, and on_del methods for registering an event
19 handler."""
18 See the on_events, on_add, on_set, and on_del methods for registering
19 event handlers."""
20 20
21 21 def __init__(self, *args, **kwargs):
22 22 """Public constructor"""
23 23 self._add_callback = _void
24 24 self._del_callback = _void
25 25 self._set_callback = _void
26 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 42 def on_add(self, callback):
29 43 """Register a callback for when an item is added to the dict.
30 44
31 45 Allows the listener to detect when items are added to the dictionary and
32 46 optionally cancel the addition.
33 47
34 48 callback: callable or None
35 49 If you want to ignore the addition event, pass None as the callback.
36 50 The callback should have a signature of callback(key, value). The
37 51 callback should return a boolean True if the additon should be
38 52 canceled, False or None otherwise."""
39 53 if callable(callback):
40 54 self._add_callback = callback
41 55 else:
42 56 self._add_callback = _void
43 57
44 58 def on_del(self, callback):
45 59 """Register a callback for when an item is deleted from the dict.
46 60
47 61 Allows the listener to detect when items are deleted from the dictionary
48 62 and optionally cancel the deletion.
49 63
50 64 callback: callable or None
51 65 If you want to ignore the deletion event, pass None as the callback.
52 66 The callback should have a signature of callback(key). The
53 67 callback should return a boolean True if the deletion should be
54 68 canceled, False or None otherwise."""
55 69 if callable(callback):
56 70 self._del_callback = callback
57 71 else:
58 72 self._del_callback = _void
59 73
60 74 def on_set(self, callback):
61 75 """Register a callback for when an item is changed in the dict.
62 76
63 77 Allows the listener to detect when items are changed in the dictionary
64 78 and optionally cancel the change.
65 79
66 80 callback: callable or None
67 81 If you want to ignore the change event, pass None as the callback.
68 82 The callback should have a signature of callback(key, value). The
69 83 callback should return a boolean True if the change should be
70 84 canceled, False or None otherwise."""
71 85 if callable(callback):
72 86 self._set_callback = callback
73 87 else:
74 88 self._set_callback = _void
75 89
76 90 def pop(self, key):
77 91 """Returns the value of an item in the dictionary and then deletes the
78 92 item from the dictionary."""
79 93 if self._can_del(key):
80 94 return dict.pop(self, key)
81 95 else:
82 96 raise Exception('Cannot `pop`, deletion of key "{}" failed.'.format(key))
83 97
84 98 def popitem(self):
85 99 """Pop the next key/value pair from the dictionary."""
86 100 key = next(iter(self))
87 101 return key, self.pop(key)
88 102
89 103 def update(self, other_dict):
90 104 """Copy the key/value pairs from another dictionary into this dictionary,
91 105 overwriting any conflicting keys in this dictionary."""
92 106 for (key, value) in other_dict.items():
93 107 self[key] = value
94 108
95 109 def clear(self):
96 110 """Clear the dictionary."""
97 111 for key in list(self.keys()):
98 112 del self[key]
99 113
100 114 def __setitem__(self, key, value):
101 115 if (key in self and self._can_set(key, value)) or \
102 116 (key not in self and self._can_add(key, value)):
103 117 return dict.__setitem__(self, key, value)
104 118
105 119 def __delitem__(self, key):
106 120 if self._can_del(key):
107 121 return dict.__delitem__(self, key)
108 122
109 123 def _can_add(self, key, value):
110 124 """Check if the item can be added to the dict."""
111 125 return not bool(self._add_callback(key, value))
112 126
113 127 def _can_del(self, key):
114 128 """Check if the item can be deleted from the dict."""
115 129 return not bool(self._del_callback(key))
116 130
117 131 def _can_set(self, key, value):
118 132 """Check if the item can be changed in the dict."""
119 133 return not bool(self._set_callback(key, value))
120 134
121 135
122 136 class EventfulList(list):
123 137 """Eventful list.
124 138
125 139 This class inherits from the Python intrinsic `list` class. It adds events
126 140 that allow you to listen for actions that modify the list. You can
127 141 optionally cancel the actions.
128 142
129 143 See the on_del, on_set, on_insert, on_sort, and on_reverse methods for
130 144 registering an event handler.
131 145
132 146 Some of the method docstrings were taken from the Python documentation at
133 147 https://docs.python.org/2/tutorial/datastructures.html"""
134 148
135 149 def __init__(self, *pargs, **kwargs):
136 150 """Public constructor"""
137 151 self._insert_callback = _void
138 152 self._set_callback = _void
139 153 self._del_callback = _void
140 154 self._sort_callback = _void
141 155 self._reverse_callback = _void
142 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 174 def on_insert(self, callback):
145 175 """Register a callback for when an item is inserted into the list.
146 176
147 177 Allows the listener to detect when items are inserted into the list and
148 178 optionally cancel the insertion.
149 179
150 180 callback: callable or None
151 181 If you want to ignore the insertion event, pass None as the callback.
152 182 The callback should have a signature of callback(index, value). The
153 183 callback should return a boolean True if the insertion should be
154 184 canceled, False or None otherwise."""
155 185 if callable(callback):
156 186 self._insert_callback = callback
157 187 else:
158 188 self._insert_callback = _void
159 189
160 190 def on_del(self, callback):
161 191 """Register a callback for item deletion.
162 192
163 193 Allows the listener to detect when items are deleted from the list and
164 194 optionally cancel the deletion.
165 195
166 196 callback: callable or None
167 197 If you want to ignore the deletion event, pass None as the callback.
168 198 The callback should have a signature of callback(index). The
169 199 callback should return a boolean True if the deletion should be
170 200 canceled, False or None otherwise."""
171 201 if callable(callback):
172 202 self._del_callback = callback
173 203 else:
174 204 self._del_callback = _void
175 205
176 206 def on_set(self, callback):
177 207 """Register a callback for items are set.
178 208
179 209 Allows the listener to detect when items are set and optionally cancel
180 210 the setting. Note, `set` is also called when one or more items are
181 211 added to the end of the list.
182 212
183 213 callback: callable or None
184 214 If you want to ignore the set event, pass None as the callback.
185 215 The callback should have a signature of callback(index, value). The
186 216 callback should return a boolean True if the set should be
187 217 canceled, False or None otherwise."""
188 218 if callable(callback):
189 219 self._set_callback = callback
190 220 else:
191 221 self._set_callback = _void
192 222
193 223 def on_reverse(self, callback):
194 224 """Register a callback for list reversal.
195 225
196 226 callback: callable or None
197 227 If you want to ignore the reverse event, pass None as the callback.
198 228 The callback should have a signature of callback(). The
199 229 callback should return a boolean True if the reverse should be
200 230 canceled, False or None otherwise."""
201 231 if callable(callback):
202 232 self._reverse_callback = callback
203 233 else:
204 234 self._reverse_callback = _void
205 235
206 236 def on_sort(self, callback):
207 237 """Register a callback for sortting of the list.
208 238
209 239 callback: callable or None
210 240 If you want to ignore the sort event, pass None as the callback.
211 241 The callback signature should match that of Python list's `.sort`
212 242 method or `callback(*pargs, **kwargs)` as a catch all. The callback
213 243 should return a boolean True if the reverse should be canceled,
214 244 False or None otherwise."""
215 245 if callable(callback):
216 246 self._sort_callback = callback
217 247 else:
218 248 self._sort_callback = _void
219 249
220 250 def append(self, x):
221 251 """Add an item to the end of the list."""
222 252 self[len(self):] = [x]
223 253
224 254 def extend(self, L):
225 255 """Extend the list by appending all the items in the given list."""
226 256 self[len(self):] = L
227 257
228 258 def remove(self, x):
229 259 """Remove the first item from the list whose value is x. It is an error
230 260 if there is no such item."""
231 261 del self[self.index(x)]
232 262
233 263 def pop(self, i=None):
234 264 """Remove the item at the given position in the list, and return it. If
235 265 no index is specified, a.pop() removes and returns the last item in the
236 266 list."""
237 267 if i is None:
238 268 i = len(self) - 1
239 269 val = self[i]
240 270 del self[i]
241 271 return val
242 272
243 273 def reverse(self):
244 274 """Reverse the elements of the list, in place."""
245 275 if self._can_reverse():
246 276 list.reverse(self)
247 277
248 278 def insert(self, index, value):
249 279 """Insert an item at a given position. The first argument is the index
250 280 of the element before which to insert, so a.insert(0, x) inserts at the
251 281 front of the list, and a.insert(len(a), x) is equivalent to
252 282 a.append(x)."""
253 283 if self._can_insert(index, value):
254 284 list.insert(self, index, value)
255 285
256 286 def sort(self, *pargs, **kwargs):
257 287 """Sort the items of the list in place (the arguments can be used for
258 288 sort customization, see Python's sorted() for their explanation)."""
259 289 if self._can_sort(*pargs, **kwargs):
260 290 list.sort(self, *pargs, **kwargs)
261 291
262 292 def __delitem__(self, index):
263 293 if self._can_del(index):
264 294 list.__delitem__(self, index)
265 295
266 296 def __setitem__(self, index, value):
267 297 if self._can_set(index, value):
268 298 list.__setitem__(self, index, value)
269 299
270 300 def __setslice__(self, start, end, value):
271 301 if self._can_set(slice(start, end), value):
272 302 list.__setslice__(self, start, end, value)
273 303
274 304 def _can_insert(self, index, value):
275 305 """Check if the item can be inserted."""
276 306 return not bool(self._insert_callback(index, value))
277 307
278 308 def _can_del(self, index):
279 309 """Check if the item can be deleted."""
280 310 return not bool(self._del_callback(index))
281 311
282 312 def _can_set(self, index, value):
283 313 """Check if the item can be set."""
284 314 return not bool(self._set_callback(index, value))
285 315
286 316 def _can_reverse(self):
287 317 """Check if the list can be reversed."""
288 318 return not bool(self._reverse_callback())
289 319
290 320 def _can_sort(self, *pargs, **kwargs):
291 321 """Check if the list can be sorted."""
292 322 return not bool(self._sort_callback(*pargs, **kwargs))
@@ -1,1253 +1,1235
1 1 # encoding: utf-8
2 2 """Tests for IPython.utils.traitlets."""
3 3
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6 #
7 7 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
8 8 # also under the terms of the Modified BSD License.
9 9
10 10 import pickle
11 11 import re
12 12 import sys
13 13 from unittest import TestCase
14 14
15 15 import nose.tools as nt
16 16 from nose import SkipTest
17 17
18 18 from IPython.utils.traitlets import (
19 19 HasTraits, MetaHasTraits, TraitType, Any, CBytes, Dict,
20 20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 21 Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 22 ObjectName, DottedObjectName, CRegExp, link, EventfulList, EventfulDict
23 23 )
24 24 from IPython.utils import py3compat
25 25 from IPython.testing.decorators import skipif
26 26
27 27 #-----------------------------------------------------------------------------
28 28 # Helper classes for testing
29 29 #-----------------------------------------------------------------------------
30 30
31 31
32 32 class HasTraitsStub(HasTraits):
33 33
34 34 def _notify_trait(self, name, old, new):
35 35 self._notify_name = name
36 36 self._notify_old = old
37 37 self._notify_new = new
38 38
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Test classes
42 42 #-----------------------------------------------------------------------------
43 43
44 44
45 45 class TestTraitType(TestCase):
46 46
47 47 def test_get_undefined(self):
48 48 class A(HasTraits):
49 49 a = TraitType
50 50 a = A()
51 51 self.assertEqual(a.a, Undefined)
52 52
53 53 def test_set(self):
54 54 class A(HasTraitsStub):
55 55 a = TraitType
56 56
57 57 a = A()
58 58 a.a = 10
59 59 self.assertEqual(a.a, 10)
60 60 self.assertEqual(a._notify_name, 'a')
61 61 self.assertEqual(a._notify_old, Undefined)
62 62 self.assertEqual(a._notify_new, 10)
63 63
64 64 def test_validate(self):
65 65 class MyTT(TraitType):
66 66 def validate(self, inst, value):
67 67 return -1
68 68 class A(HasTraitsStub):
69 69 tt = MyTT
70 70
71 71 a = A()
72 72 a.tt = 10
73 73 self.assertEqual(a.tt, -1)
74 74
75 75 def test_default_validate(self):
76 76 class MyIntTT(TraitType):
77 77 def validate(self, obj, value):
78 78 if isinstance(value, int):
79 79 return value
80 80 self.error(obj, value)
81 81 class A(HasTraits):
82 82 tt = MyIntTT(10)
83 83 a = A()
84 84 self.assertEqual(a.tt, 10)
85 85
86 86 # Defaults are validated when the HasTraits is instantiated
87 87 class B(HasTraits):
88 88 tt = MyIntTT('bad default')
89 89 self.assertRaises(TraitError, B)
90 90
91 91 def test_is_valid_for(self):
92 92 class MyTT(TraitType):
93 93 def is_valid_for(self, value):
94 94 return True
95 95 class A(HasTraits):
96 96 tt = MyTT
97 97
98 98 a = A()
99 99 a.tt = 10
100 100 self.assertEqual(a.tt, 10)
101 101
102 102 def test_value_for(self):
103 103 class MyTT(TraitType):
104 104 def value_for(self, value):
105 105 return 20
106 106 class A(HasTraits):
107 107 tt = MyTT
108 108
109 109 a = A()
110 110 a.tt = 10
111 111 self.assertEqual(a.tt, 20)
112 112
113 113 def test_info(self):
114 114 class A(HasTraits):
115 115 tt = TraitType
116 116 a = A()
117 117 self.assertEqual(A.tt.info(), 'any value')
118 118
119 119 def test_error(self):
120 120 class A(HasTraits):
121 121 tt = TraitType
122 122 a = A()
123 123 self.assertRaises(TraitError, A.tt.error, a, 10)
124 124
125 125 def test_dynamic_initializer(self):
126 126 class A(HasTraits):
127 127 x = Int(10)
128 128 def _x_default(self):
129 129 return 11
130 130 class B(A):
131 131 x = Int(20)
132 132 class C(A):
133 133 def _x_default(self):
134 134 return 21
135 135
136 136 a = A()
137 137 self.assertEqual(a._trait_values, {})
138 138 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
139 139 self.assertEqual(a.x, 11)
140 140 self.assertEqual(a._trait_values, {'x': 11})
141 141 b = B()
142 142 self.assertEqual(b._trait_values, {'x': 20})
143 143 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
144 144 self.assertEqual(b.x, 20)
145 145 c = C()
146 146 self.assertEqual(c._trait_values, {})
147 147 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
148 148 self.assertEqual(c.x, 21)
149 149 self.assertEqual(c._trait_values, {'x': 21})
150 150 # Ensure that the base class remains unmolested when the _default
151 151 # initializer gets overridden in a subclass.
152 152 a = A()
153 153 c = C()
154 154 self.assertEqual(a._trait_values, {})
155 155 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
156 156 self.assertEqual(a.x, 11)
157 157 self.assertEqual(a._trait_values, {'x': 11})
158 158
159 159
160 160
161 161 class TestHasTraitsMeta(TestCase):
162 162
163 163 def test_metaclass(self):
164 164 self.assertEqual(type(HasTraits), MetaHasTraits)
165 165
166 166 class A(HasTraits):
167 167 a = Int
168 168
169 169 a = A()
170 170 self.assertEqual(type(a.__class__), MetaHasTraits)
171 171 self.assertEqual(a.a,0)
172 172 a.a = 10
173 173 self.assertEqual(a.a,10)
174 174
175 175 class B(HasTraits):
176 176 b = Int()
177 177
178 178 b = B()
179 179 self.assertEqual(b.b,0)
180 180 b.b = 10
181 181 self.assertEqual(b.b,10)
182 182
183 183 class C(HasTraits):
184 184 c = Int(30)
185 185
186 186 c = C()
187 187 self.assertEqual(c.c,30)
188 188 c.c = 10
189 189 self.assertEqual(c.c,10)
190 190
191 191 def test_this_class(self):
192 192 class A(HasTraits):
193 193 t = This()
194 194 tt = This()
195 195 class B(A):
196 196 tt = This()
197 197 ttt = This()
198 198 self.assertEqual(A.t.this_class, A)
199 199 self.assertEqual(B.t.this_class, A)
200 200 self.assertEqual(B.tt.this_class, B)
201 201 self.assertEqual(B.ttt.this_class, B)
202 202
203 203 class TestHasTraitsNotify(TestCase):
204 204
205 205 def setUp(self):
206 206 self._notify1 = []
207 207 self._notify2 = []
208 208
209 209 def notify1(self, name, old, new):
210 210 self._notify1.append((name, old, new))
211 211
212 212 def notify2(self, name, old, new):
213 213 self._notify2.append((name, old, new))
214 214
215 215 def test_notify_all(self):
216 216
217 217 class A(HasTraits):
218 218 a = Int
219 219 b = Float
220 220
221 221 a = A()
222 222 a.on_trait_change(self.notify1)
223 223 a.a = 0
224 224 self.assertEqual(len(self._notify1),0)
225 225 a.b = 0.0
226 226 self.assertEqual(len(self._notify1),0)
227 227 a.a = 10
228 228 self.assertTrue(('a',0,10) in self._notify1)
229 229 a.b = 10.0
230 230 self.assertTrue(('b',0.0,10.0) in self._notify1)
231 231 self.assertRaises(TraitError,setattr,a,'a','bad string')
232 232 self.assertRaises(TraitError,setattr,a,'b','bad string')
233 233 self._notify1 = []
234 234 a.on_trait_change(self.notify1,remove=True)
235 235 a.a = 20
236 236 a.b = 20.0
237 237 self.assertEqual(len(self._notify1),0)
238 238
239 239 def test_notify_one(self):
240 240
241 241 class A(HasTraits):
242 242 a = Int
243 243 b = Float
244 244
245 245 a = A()
246 246 a.on_trait_change(self.notify1, 'a')
247 247 a.a = 0
248 248 self.assertEqual(len(self._notify1),0)
249 249 a.a = 10
250 250 self.assertTrue(('a',0,10) in self._notify1)
251 251 self.assertRaises(TraitError,setattr,a,'a','bad string')
252 252
253 253 def test_subclass(self):
254 254
255 255 class A(HasTraits):
256 256 a = Int
257 257
258 258 class B(A):
259 259 b = Float
260 260
261 261 b = B()
262 262 self.assertEqual(b.a,0)
263 263 self.assertEqual(b.b,0.0)
264 264 b.a = 100
265 265 b.b = 100.0
266 266 self.assertEqual(b.a,100)
267 267 self.assertEqual(b.b,100.0)
268 268
269 269 def test_notify_subclass(self):
270 270
271 271 class A(HasTraits):
272 272 a = Int
273 273
274 274 class B(A):
275 275 b = Float
276 276
277 277 b = B()
278 278 b.on_trait_change(self.notify1, 'a')
279 279 b.on_trait_change(self.notify2, 'b')
280 280 b.a = 0
281 281 b.b = 0.0
282 282 self.assertEqual(len(self._notify1),0)
283 283 self.assertEqual(len(self._notify2),0)
284 284 b.a = 10
285 285 b.b = 10.0
286 286 self.assertTrue(('a',0,10) in self._notify1)
287 287 self.assertTrue(('b',0.0,10.0) in self._notify2)
288 288
289 289 def test_static_notify(self):
290 290
291 291 class A(HasTraits):
292 292 a = Int
293 293 _notify1 = []
294 294 def _a_changed(self, name, old, new):
295 295 self._notify1.append((name, old, new))
296 296
297 297 a = A()
298 298 a.a = 0
299 299 # This is broken!!!
300 300 self.assertEqual(len(a._notify1),0)
301 301 a.a = 10
302 302 self.assertTrue(('a',0,10) in a._notify1)
303 303
304 304 class B(A):
305 305 b = Float
306 306 _notify2 = []
307 307 def _b_changed(self, name, old, new):
308 308 self._notify2.append((name, old, new))
309 309
310 310 b = B()
311 311 b.a = 10
312 312 b.b = 10.0
313 313 self.assertTrue(('a',0,10) in b._notify1)
314 314 self.assertTrue(('b',0.0,10.0) in b._notify2)
315 315
316 316 def test_notify_args(self):
317 317
318 318 def callback0():
319 319 self.cb = ()
320 320 def callback1(name):
321 321 self.cb = (name,)
322 322 def callback2(name, new):
323 323 self.cb = (name, new)
324 324 def callback3(name, old, new):
325 325 self.cb = (name, old, new)
326 326
327 327 class A(HasTraits):
328 328 a = Int
329 329
330 330 a = A()
331 331 a.on_trait_change(callback0, 'a')
332 332 a.a = 10
333 333 self.assertEqual(self.cb,())
334 334 a.on_trait_change(callback0, 'a', remove=True)
335 335
336 336 a.on_trait_change(callback1, 'a')
337 337 a.a = 100
338 338 self.assertEqual(self.cb,('a',))
339 339 a.on_trait_change(callback1, 'a', remove=True)
340 340
341 341 a.on_trait_change(callback2, 'a')
342 342 a.a = 1000
343 343 self.assertEqual(self.cb,('a',1000))
344 344 a.on_trait_change(callback2, 'a', remove=True)
345 345
346 346 a.on_trait_change(callback3, 'a')
347 347 a.a = 10000
348 348 self.assertEqual(self.cb,('a',1000,10000))
349 349 a.on_trait_change(callback3, 'a', remove=True)
350 350
351 351 self.assertEqual(len(a._trait_notifiers['a']),0)
352 352
353 353 def test_notify_only_once(self):
354 354
355 355 class A(HasTraits):
356 356 listen_to = ['a']
357 357
358 358 a = Int(0)
359 359 b = 0
360 360
361 361 def __init__(self, **kwargs):
362 362 super(A, self).__init__(**kwargs)
363 363 self.on_trait_change(self.listener1, ['a'])
364 364
365 365 def listener1(self, name, old, new):
366 366 self.b += 1
367 367
368 368 class B(A):
369 369
370 370 c = 0
371 371 d = 0
372 372
373 373 def __init__(self, **kwargs):
374 374 super(B, self).__init__(**kwargs)
375 375 self.on_trait_change(self.listener2)
376 376
377 377 def listener2(self, name, old, new):
378 378 self.c += 1
379 379
380 380 def _a_changed(self, name, old, new):
381 381 self.d += 1
382 382
383 383 b = B()
384 384 b.a += 1
385 385 self.assertEqual(b.b, b.c)
386 386 self.assertEqual(b.b, b.d)
387 387 b.a += 1
388 388 self.assertEqual(b.b, b.c)
389 389 self.assertEqual(b.b, b.d)
390 390
391 391
392 392 class TestHasTraits(TestCase):
393 393
394 394 def test_trait_names(self):
395 395 class A(HasTraits):
396 396 i = Int
397 397 f = Float
398 398 a = A()
399 399 self.assertEqual(sorted(a.trait_names()),['f','i'])
400 400 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
401 401
402 402 def test_trait_metadata(self):
403 403 class A(HasTraits):
404 404 i = Int(config_key='MY_VALUE')
405 405 a = A()
406 406 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
407 407
408 408 def test_traits(self):
409 409 class A(HasTraits):
410 410 i = Int
411 411 f = Float
412 412 a = A()
413 413 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
414 414 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
415 415
416 416 def test_traits_metadata(self):
417 417 class A(HasTraits):
418 418 i = Int(config_key='VALUE1', other_thing='VALUE2')
419 419 f = Float(config_key='VALUE3', other_thing='VALUE2')
420 420 j = Int(0)
421 421 a = A()
422 422 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
423 423 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
424 424 self.assertEqual(traits, dict(i=A.i))
425 425
426 426 # This passes, but it shouldn't because I am replicating a bug in
427 427 # traits.
428 428 traits = a.traits(config_key=lambda v: True)
429 429 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
430 430
431 431 def test_init(self):
432 432 class A(HasTraits):
433 433 i = Int()
434 434 x = Float()
435 435 a = A(i=1, x=10.0)
436 436 self.assertEqual(a.i, 1)
437 437 self.assertEqual(a.x, 10.0)
438 438
439 439 def test_positional_args(self):
440 440 class A(HasTraits):
441 441 i = Int(0)
442 442 def __init__(self, i):
443 443 super(A, self).__init__()
444 444 self.i = i
445 445
446 446 a = A(5)
447 447 self.assertEqual(a.i, 5)
448 448 # should raise TypeError if no positional arg given
449 449 self.assertRaises(TypeError, A)
450 450
451 451 #-----------------------------------------------------------------------------
452 452 # Tests for specific trait types
453 453 #-----------------------------------------------------------------------------
454 454
455 455
456 456 class TestType(TestCase):
457 457
458 458 def test_default(self):
459 459
460 460 class B(object): pass
461 461 class A(HasTraits):
462 462 klass = Type
463 463
464 464 a = A()
465 465 self.assertEqual(a.klass, None)
466 466
467 467 a.klass = B
468 468 self.assertEqual(a.klass, B)
469 469 self.assertRaises(TraitError, setattr, a, 'klass', 10)
470 470
471 471 def test_value(self):
472 472
473 473 class B(object): pass
474 474 class C(object): pass
475 475 class A(HasTraits):
476 476 klass = Type(B)
477 477
478 478 a = A()
479 479 self.assertEqual(a.klass, B)
480 480 self.assertRaises(TraitError, setattr, a, 'klass', C)
481 481 self.assertRaises(TraitError, setattr, a, 'klass', object)
482 482 a.klass = B
483 483
484 484 def test_allow_none(self):
485 485
486 486 class B(object): pass
487 487 class C(B): pass
488 488 class A(HasTraits):
489 489 klass = Type(B, allow_none=False)
490 490
491 491 a = A()
492 492 self.assertEqual(a.klass, B)
493 493 self.assertRaises(TraitError, setattr, a, 'klass', None)
494 494 a.klass = C
495 495 self.assertEqual(a.klass, C)
496 496
497 497 def test_validate_klass(self):
498 498
499 499 class A(HasTraits):
500 500 klass = Type('no strings allowed')
501 501
502 502 self.assertRaises(ImportError, A)
503 503
504 504 class A(HasTraits):
505 505 klass = Type('rub.adub.Duck')
506 506
507 507 self.assertRaises(ImportError, A)
508 508
509 509 def test_validate_default(self):
510 510
511 511 class B(object): pass
512 512 class A(HasTraits):
513 513 klass = Type('bad default', B)
514 514
515 515 self.assertRaises(ImportError, A)
516 516
517 517 class C(HasTraits):
518 518 klass = Type(None, B, allow_none=False)
519 519
520 520 self.assertRaises(TraitError, C)
521 521
522 522 def test_str_klass(self):
523 523
524 524 class A(HasTraits):
525 525 klass = Type('IPython.utils.ipstruct.Struct')
526 526
527 527 from IPython.utils.ipstruct import Struct
528 528 a = A()
529 529 a.klass = Struct
530 530 self.assertEqual(a.klass, Struct)
531 531
532 532 self.assertRaises(TraitError, setattr, a, 'klass', 10)
533 533
534 534 def test_set_str_klass(self):
535 535
536 536 class A(HasTraits):
537 537 klass = Type()
538 538
539 539 a = A(klass='IPython.utils.ipstruct.Struct')
540 540 from IPython.utils.ipstruct import Struct
541 541 self.assertEqual(a.klass, Struct)
542 542
543 543 class TestInstance(TestCase):
544 544
545 545 def test_basic(self):
546 546 class Foo(object): pass
547 547 class Bar(Foo): pass
548 548 class Bah(object): pass
549 549
550 550 class A(HasTraits):
551 551 inst = Instance(Foo)
552 552
553 553 a = A()
554 554 self.assertTrue(a.inst is None)
555 555 a.inst = Foo()
556 556 self.assertTrue(isinstance(a.inst, Foo))
557 557 a.inst = Bar()
558 558 self.assertTrue(isinstance(a.inst, Foo))
559 559 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
560 560 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
561 561 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
562 562
563 563 def test_default_klass(self):
564 564 class Foo(object): pass
565 565 class Bar(Foo): pass
566 566 class Bah(object): pass
567 567
568 568 class FooInstance(Instance):
569 569 klass = Foo
570 570
571 571 class A(HasTraits):
572 572 inst = FooInstance()
573 573
574 574 a = A()
575 575 self.assertTrue(a.inst is None)
576 576 a.inst = Foo()
577 577 self.assertTrue(isinstance(a.inst, Foo))
578 578 a.inst = Bar()
579 579 self.assertTrue(isinstance(a.inst, Foo))
580 580 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
581 581 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
582 582 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
583 583
584 584 def test_unique_default_value(self):
585 585 class Foo(object): pass
586 586 class A(HasTraits):
587 587 inst = Instance(Foo,(),{})
588 588
589 589 a = A()
590 590 b = A()
591 591 self.assertTrue(a.inst is not b.inst)
592 592
593 593 def test_args_kw(self):
594 594 class Foo(object):
595 595 def __init__(self, c): self.c = c
596 596 class Bar(object): pass
597 597 class Bah(object):
598 598 def __init__(self, c, d):
599 599 self.c = c; self.d = d
600 600
601 601 class A(HasTraits):
602 602 inst = Instance(Foo, (10,))
603 603 a = A()
604 604 self.assertEqual(a.inst.c, 10)
605 605
606 606 class B(HasTraits):
607 607 inst = Instance(Bah, args=(10,), kw=dict(d=20))
608 608 b = B()
609 609 self.assertEqual(b.inst.c, 10)
610 610 self.assertEqual(b.inst.d, 20)
611 611
612 612 class C(HasTraits):
613 613 inst = Instance(Foo)
614 614 c = C()
615 615 self.assertTrue(c.inst is None)
616 616
617 617 def test_bad_default(self):
618 618 class Foo(object): pass
619 619
620 620 class A(HasTraits):
621 621 inst = Instance(Foo, allow_none=False)
622 622
623 623 self.assertRaises(TraitError, A)
624 624
625 625 def test_instance(self):
626 626 class Foo(object): pass
627 627
628 628 def inner():
629 629 class A(HasTraits):
630 630 inst = Instance(Foo())
631 631
632 632 self.assertRaises(TraitError, inner)
633 633
634 634
635 635 class TestThis(TestCase):
636 636
637 637 def test_this_class(self):
638 638 class Foo(HasTraits):
639 639 this = This
640 640
641 641 f = Foo()
642 642 self.assertEqual(f.this, None)
643 643 g = Foo()
644 644 f.this = g
645 645 self.assertEqual(f.this, g)
646 646 self.assertRaises(TraitError, setattr, f, 'this', 10)
647 647
648 648 def test_this_inst(self):
649 649 class Foo(HasTraits):
650 650 this = This()
651 651
652 652 f = Foo()
653 653 f.this = Foo()
654 654 self.assertTrue(isinstance(f.this, Foo))
655 655
656 656 def test_subclass(self):
657 657 class Foo(HasTraits):
658 658 t = This()
659 659 class Bar(Foo):
660 660 pass
661 661 f = Foo()
662 662 b = Bar()
663 663 f.t = b
664 664 b.t = f
665 665 self.assertEqual(f.t, b)
666 666 self.assertEqual(b.t, f)
667 667
668 668 def test_subclass_override(self):
669 669 class Foo(HasTraits):
670 670 t = This()
671 671 class Bar(Foo):
672 672 t = This()
673 673 f = Foo()
674 674 b = Bar()
675 675 f.t = b
676 676 self.assertEqual(f.t, b)
677 677 self.assertRaises(TraitError, setattr, b, 't', f)
678 678
679 679 class TraitTestBase(TestCase):
680 680 """A best testing class for basic trait types."""
681 681
682 682 def assign(self, value):
683 683 self.obj.value = value
684 684
685 685 def coerce(self, value):
686 686 return value
687 687
688 688 def test_good_values(self):
689 689 if hasattr(self, '_good_values'):
690 690 for value in self._good_values:
691 691 self.assign(value)
692 692 self.assertEqual(self.obj.value, self.coerce(value))
693 693
694 694 def test_bad_values(self):
695 695 if hasattr(self, '_bad_values'):
696 696 for value in self._bad_values:
697 697 try:
698 698 self.assertRaises(TraitError, self.assign, value)
699 699 except AssertionError:
700 700 assert False, value
701 701
702 702 def test_default_value(self):
703 703 if hasattr(self, '_default_value'):
704 704 self.assertEqual(self._default_value, self.obj.value)
705 705
706 706 def test_allow_none(self):
707 707 if (hasattr(self, '_bad_values') and hasattr(self, '_good_values') and
708 708 None in self._bad_values):
709 709 trait=self.obj.traits()['value']
710 710 try:
711 711 trait.allow_none = True
712 712 self._bad_values.remove(None)
713 713 #skip coerce. Allow None casts None to None.
714 714 self.assign(None)
715 715 self.assertEqual(self.obj.value,None)
716 716 self.test_good_values()
717 717 self.test_bad_values()
718 718 finally:
719 719 #tear down
720 720 trait.allow_none = False
721 721 self._bad_values.append(None)
722 722
723 723 def tearDown(self):
724 724 # restore default value after tests, if set
725 725 if hasattr(self, '_default_value'):
726 726 self.obj.value = self._default_value
727 727
728 728
729 729 class AnyTrait(HasTraits):
730 730
731 731 value = Any
732 732
733 733 class AnyTraitTest(TraitTestBase):
734 734
735 735 obj = AnyTrait()
736 736
737 737 _default_value = None
738 738 _good_values = [10.0, 'ten', u'ten', [10], {'ten': 10},(10,), None, 1j]
739 739 _bad_values = []
740 740
741 741
742 742 class IntTrait(HasTraits):
743 743
744 744 value = Int(99)
745 745
746 746 class TestInt(TraitTestBase):
747 747
748 748 obj = IntTrait()
749 749 _default_value = 99
750 750 _good_values = [10, -10]
751 751 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
752 752 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
753 753 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
754 754 if not py3compat.PY3:
755 755 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
756 756
757 757
758 758 class LongTrait(HasTraits):
759 759
760 760 value = Long(99 if py3compat.PY3 else long(99))
761 761
762 762 class TestLong(TraitTestBase):
763 763
764 764 obj = LongTrait()
765 765
766 766 _default_value = 99 if py3compat.PY3 else long(99)
767 767 _good_values = [10, -10]
768 768 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
769 769 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
770 770 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
771 771 u'-10.1']
772 772 if not py3compat.PY3:
773 773 # maxint undefined on py3, because int == long
774 774 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775 775 _bad_values.extend([[long(10)], (long(10),)])
776 776
777 777 @skipif(py3compat.PY3, "not relevant on py3")
778 778 def test_cast_small(self):
779 779 """Long casts ints to long"""
780 780 self.obj.value = 10
781 781 self.assertEqual(type(self.obj.value), long)
782 782
783 783
784 784 class IntegerTrait(HasTraits):
785 785 value = Integer(1)
786 786
787 787 class TestInteger(TestLong):
788 788 obj = IntegerTrait()
789 789 _default_value = 1
790 790
791 791 def coerce(self, n):
792 792 return int(n)
793 793
794 794 @skipif(py3compat.PY3, "not relevant on py3")
795 795 def test_cast_small(self):
796 796 """Integer casts small longs to int"""
797 797 if py3compat.PY3:
798 798 raise SkipTest("not relevant on py3")
799 799
800 800 self.obj.value = long(100)
801 801 self.assertEqual(type(self.obj.value), int)
802 802
803 803
804 804 class FloatTrait(HasTraits):
805 805
806 806 value = Float(99.0)
807 807
808 808 class TestFloat(TraitTestBase):
809 809
810 810 obj = FloatTrait()
811 811
812 812 _default_value = 99.0
813 813 _good_values = [10, -10, 10.1, -10.1]
814 814 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
815 815 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
816 816 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
817 817 if not py3compat.PY3:
818 818 _bad_values.extend([long(10), long(-10)])
819 819
820 820
821 821 class ComplexTrait(HasTraits):
822 822
823 823 value = Complex(99.0-99.0j)
824 824
825 825 class TestComplex(TraitTestBase):
826 826
827 827 obj = ComplexTrait()
828 828
829 829 _default_value = 99.0-99.0j
830 830 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
831 831 10.1j, 10.1+10.1j, 10.1-10.1j]
832 832 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
833 833 if not py3compat.PY3:
834 834 _bad_values.extend([long(10), long(-10)])
835 835
836 836
837 837 class BytesTrait(HasTraits):
838 838
839 839 value = Bytes(b'string')
840 840
841 841 class TestBytes(TraitTestBase):
842 842
843 843 obj = BytesTrait()
844 844
845 845 _default_value = b'string'
846 846 _good_values = [b'10', b'-10', b'10L',
847 847 b'-10L', b'10.1', b'-10.1', b'string']
848 848 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
849 849 ['ten'],{'ten': 10},(10,), None, u'string']
850 850 if not py3compat.PY3:
851 851 _bad_values.extend([long(10), long(-10)])
852 852
853 853
854 854 class UnicodeTrait(HasTraits):
855 855
856 856 value = Unicode(u'unicode')
857 857
858 858 class TestUnicode(TraitTestBase):
859 859
860 860 obj = UnicodeTrait()
861 861
862 862 _default_value = u'unicode'
863 863 _good_values = ['10', '-10', '10L', '-10L', '10.1',
864 864 '-10.1', '', u'', 'string', u'string', u"€"]
865 865 _bad_values = [10, -10, 10.1, -10.1, 1j,
866 866 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
867 867 if not py3compat.PY3:
868 868 _bad_values.extend([long(10), long(-10)])
869 869
870 870
871 871 class ObjectNameTrait(HasTraits):
872 872 value = ObjectName("abc")
873 873
874 874 class TestObjectName(TraitTestBase):
875 875 obj = ObjectNameTrait()
876 876
877 877 _default_value = "abc"
878 878 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
879 879 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
880 880 None, object(), object]
881 881 if sys.version_info[0] < 3:
882 882 _bad_values.append(u"ΓΎ")
883 883 else:
884 884 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
885 885
886 886
887 887 class DottedObjectNameTrait(HasTraits):
888 888 value = DottedObjectName("a.b")
889 889
890 890 class TestDottedObjectName(TraitTestBase):
891 891 obj = DottedObjectNameTrait()
892 892
893 893 _default_value = "a.b"
894 894 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
895 895 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
896 896 if sys.version_info[0] < 3:
897 897 _bad_values.append(u"t.ΓΎ")
898 898 else:
899 899 _good_values.append(u"t.ΓΎ")
900 900
901 901
902 902 class TCPAddressTrait(HasTraits):
903 903
904 904 value = TCPAddress()
905 905
906 906 class TestTCPAddress(TraitTestBase):
907 907
908 908 obj = TCPAddressTrait()
909 909
910 910 _default_value = ('127.0.0.1',0)
911 911 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
912 912 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
913 913
914 914 class ListTrait(HasTraits):
915 915
916 916 value = List(Int)
917 917
918 918 class TestList(TraitTestBase):
919 919
920 920 obj = ListTrait()
921 921
922 922 _default_value = []
923 923 _good_values = [[], [1], list(range(10)), (1,2)]
924 924 _bad_values = [10, [1,'a'], 'a']
925 925
926 926 def coerce(self, value):
927 927 if value is not None:
928 928 value = list(value)
929 929 return value
930 930
931 931 class Foo(object):
932 932 pass
933 933
934 934 class InstanceListTrait(HasTraits):
935 935
936 936 value = List(Instance(__name__+'.Foo'))
937 937
938 938 class TestInstanceList(TraitTestBase):
939 939
940 940 obj = InstanceListTrait()
941 941
942 942 def test_klass(self):
943 943 """Test that the instance klass is properly assigned."""
944 944 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
945 945
946 946 _default_value = []
947 947 _good_values = [[Foo(), Foo(), None], None]
948 948 _bad_values = [['1', 2,], '1', [Foo]]
949 949
950 950 class LenListTrait(HasTraits):
951 951
952 952 value = List(Int, [0], minlen=1, maxlen=2)
953 953
954 954 class TestLenList(TraitTestBase):
955 955
956 956 obj = LenListTrait()
957 957
958 958 _default_value = [0]
959 959 _good_values = [[1], [1,2], (1,2)]
960 960 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
961 961
962 962 def coerce(self, value):
963 963 if value is not None:
964 964 value = list(value)
965 965 return value
966 966
967 967 class TupleTrait(HasTraits):
968 968
969 969 value = Tuple(Int(allow_none=True))
970 970
971 971 class TestTupleTrait(TraitTestBase):
972 972
973 973 obj = TupleTrait()
974 974
975 975 _default_value = None
976 976 _good_values = [(1,), None, (0,), [1], (None,)]
977 977 _bad_values = [10, (1,2), ('a'), ()]
978 978
979 979 def coerce(self, value):
980 980 if value is not None:
981 981 value = tuple(value)
982 982 return value
983 983
984 984 def test_invalid_args(self):
985 985 self.assertRaises(TypeError, Tuple, 5)
986 986 self.assertRaises(TypeError, Tuple, default_value='hello')
987 987 t = Tuple(Int, CBytes, default_value=(1,5))
988 988
989 989 class LooseTupleTrait(HasTraits):
990 990
991 991 value = Tuple((1,2,3))
992 992
993 993 class TestLooseTupleTrait(TraitTestBase):
994 994
995 995 obj = LooseTupleTrait()
996 996
997 997 _default_value = (1,2,3)
998 998 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
999 999 _bad_values = [10, 'hello', {}]
1000 1000
1001 1001 def coerce(self, value):
1002 1002 if value is not None:
1003 1003 value = tuple(value)
1004 1004 return value
1005 1005
1006 1006 def test_invalid_args(self):
1007 1007 self.assertRaises(TypeError, Tuple, 5)
1008 1008 self.assertRaises(TypeError, Tuple, default_value='hello')
1009 1009 t = Tuple(Int, CBytes, default_value=(1,5))
1010 1010
1011 1011
1012 1012 class MultiTupleTrait(HasTraits):
1013 1013
1014 1014 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1015 1015
1016 1016 class TestMultiTuple(TraitTestBase):
1017 1017
1018 1018 obj = MultiTupleTrait()
1019 1019
1020 1020 _default_value = (99,b'bottles')
1021 1021 _good_values = [(1,b'a'), (2,b'b')]
1022 1022 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1023 1023
1024 1024 class CRegExpTrait(HasTraits):
1025 1025
1026 1026 value = CRegExp(r'')
1027 1027
1028 1028 class TestCRegExp(TraitTestBase):
1029 1029
1030 1030 def coerce(self, value):
1031 1031 return re.compile(value)
1032 1032
1033 1033 obj = CRegExpTrait()
1034 1034
1035 1035 _default_value = re.compile(r'')
1036 1036 _good_values = [r'\d+', re.compile(r'\d+')]
1037 1037 _bad_values = ['(', None, ()]
1038 1038
1039 1039 class DictTrait(HasTraits):
1040 1040 value = Dict()
1041 1041
1042 1042 def test_dict_assignment():
1043 1043 d = dict()
1044 1044 c = DictTrait()
1045 1045 c.value = d
1046 1046 d['a'] = 5
1047 1047 nt.assert_equal(d, c.value)
1048 1048 nt.assert_true(c.value is d)
1049 1049
1050 1050 class TestLink(TestCase):
1051 1051 def test_connect_same(self):
1052 1052 """Verify two traitlets of the same type can be linked together using link."""
1053 1053
1054 1054 # Create two simple classes with Int traitlets.
1055 1055 class A(HasTraits):
1056 1056 value = Int()
1057 1057 a = A(value=9)
1058 1058 b = A(value=8)
1059 1059
1060 1060 # Conenct the two classes.
1061 1061 c = link((a, 'value'), (b, 'value'))
1062 1062
1063 1063 # Make sure the values are the same at the point of linking.
1064 1064 self.assertEqual(a.value, b.value)
1065 1065
1066 1066 # Change one of the values to make sure they stay in sync.
1067 1067 a.value = 5
1068 1068 self.assertEqual(a.value, b.value)
1069 1069 b.value = 6
1070 1070 self.assertEqual(a.value, b.value)
1071 1071
1072 1072 def test_link_different(self):
1073 1073 """Verify two traitlets of different types can be linked together using link."""
1074 1074
1075 1075 # Create two simple classes with Int traitlets.
1076 1076 class A(HasTraits):
1077 1077 value = Int()
1078 1078 class B(HasTraits):
1079 1079 count = Int()
1080 1080 a = A(value=9)
1081 1081 b = B(count=8)
1082 1082
1083 1083 # Conenct the two classes.
1084 1084 c = link((a, 'value'), (b, 'count'))
1085 1085
1086 1086 # Make sure the values are the same at the point of linking.
1087 1087 self.assertEqual(a.value, b.count)
1088 1088
1089 1089 # Change one of the values to make sure they stay in sync.
1090 1090 a.value = 5
1091 1091 self.assertEqual(a.value, b.count)
1092 1092 b.count = 4
1093 1093 self.assertEqual(a.value, b.count)
1094 1094
1095 1095 def test_unlink(self):
1096 1096 """Verify two linked traitlets can be unlinked."""
1097 1097
1098 1098 # Create two simple classes with Int traitlets.
1099 1099 class A(HasTraits):
1100 1100 value = Int()
1101 1101 a = A(value=9)
1102 1102 b = A(value=8)
1103 1103
1104 1104 # Connect the two classes.
1105 1105 c = link((a, 'value'), (b, 'value'))
1106 1106 a.value = 4
1107 1107 c.unlink()
1108 1108
1109 1109 # Change one of the values to make sure they don't stay in sync.
1110 1110 a.value = 5
1111 1111 self.assertNotEqual(a.value, b.value)
1112 1112
1113 1113 def test_callbacks(self):
1114 1114 """Verify two linked traitlets have their callbacks called once."""
1115 1115
1116 1116 # Create two simple classes with Int traitlets.
1117 1117 class A(HasTraits):
1118 1118 value = Int()
1119 1119 class B(HasTraits):
1120 1120 count = Int()
1121 1121 a = A(value=9)
1122 1122 b = B(count=8)
1123 1123
1124 1124 # Register callbacks that count.
1125 1125 callback_count = []
1126 1126 def a_callback(name, old, new):
1127 1127 callback_count.append('a')
1128 1128 a.on_trait_change(a_callback, 'value')
1129 1129 def b_callback(name, old, new):
1130 1130 callback_count.append('b')
1131 1131 b.on_trait_change(b_callback, 'count')
1132 1132
1133 1133 # Connect the two classes.
1134 1134 c = link((a, 'value'), (b, 'count'))
1135 1135
1136 1136 # Make sure b's count was set to a's value once.
1137 1137 self.assertEqual(''.join(callback_count), 'b')
1138 1138 del callback_count[:]
1139 1139
1140 1140 # Make sure a's value was set to b's count once.
1141 1141 b.count = 5
1142 1142 self.assertEqual(''.join(callback_count), 'ba')
1143 1143 del callback_count[:]
1144 1144
1145 1145 # Make sure b's count was set to a's value once.
1146 1146 a.value = 4
1147 1147 self.assertEqual(''.join(callback_count), 'ab')
1148 1148 del callback_count[:]
1149 1149
1150 1150 class Pickleable(HasTraits):
1151 1151 i = Int()
1152 1152 j = Int()
1153 1153
1154 1154 def _i_default(self):
1155 1155 return 1
1156 1156
1157 1157 def _i_changed(self, name, old, new):
1158 1158 self.j = new
1159 1159
1160 1160 def test_pickle_hastraits():
1161 1161 c = Pickleable()
1162 1162 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1163 1163 p = pickle.dumps(c, protocol)
1164 1164 c2 = pickle.loads(p)
1165 1165 nt.assert_equal(c2.i, c.i)
1166 1166 nt.assert_equal(c2.j, c.j)
1167 1167
1168 1168 c.i = 5
1169 1169 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1170 1170 p = pickle.dumps(c, protocol)
1171 1171 c2 = pickle.loads(p)
1172 1172 nt.assert_equal(c2.i, c.i)
1173 1173 nt.assert_equal(c2.j, c.j)
1174 1174
1175 1175 class TestEventful(TestCase):
1176 1176
1177 1177 def test_list(self):
1178 1178 """Does the EventfulList work?"""
1179 1179 event_cache = []
1180 1180
1181 1181 class A(HasTraits):
1182 1182 x = EventfulList([c for c in 'abc'])
1183 1183 a = A()
1184
1185 def handle_insert(index, value):
1186 event_cache.append('insert')
1187 def handle_del(index):
1188 event_cache.append('del')
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)
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'))
1200 1189
1201 1190 a.x.remove('c')
1202 1191 # ab
1203 1192 a.x.insert(0, 'z')
1204 1193 # zab
1205 1194 del a.x[1]
1206 1195 # zb
1207 1196 a.x.reverse()
1208 1197 # bz
1209 1198 a.x[1] = 'o'
1210 1199 # bo
1211 1200 a.x.append('a')
1212 1201 # boa
1213 1202 a.x.sort()
1214 1203 # abo
1215 1204
1216 1205 # Were the correct events captured?
1217 1206 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1218 1207
1219 1208 # Is the output correct?
1220 1209 self.assertEqual(a.x, [c for c in 'abo'])
1221 1210
1222 1211 def test_dict(self):
1223 1212 """Does the EventfulDict work?"""
1224 1213 event_cache = []
1225 1214
1226 1215 class A(HasTraits):
1227 1216 x = EventfulDict({c: c for c in 'abc'})
1228 1217 a = A()
1229
1230 def handle_add(key, value):
1231 event_cache.append('add')
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)
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'))
1239 1221
1240 1222 del a.x['c']
1241 1223 # ab
1242 1224 a.x['z'] = 1
1243 1225 # abz
1244 1226 a.x['z'] = 'z'
1245 1227 # abz
1246 1228 a.x.pop('a')
1247 1229 # bz
1248 1230
1249 1231 # Were the correct events captured?
1250 1232 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1251 1233
1252 1234 # Is the output correct?
1253 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