##// END OF EJS Templates
Merge pull request #8124 from jdfreder/dynamic...
Min RK -
r20932:a748bc5d merge
parent child Browse files
Show More
@@ -1,490 +1,497 b''
1 """Base Widget class. Allows user to create widgets in the back-end that render
1 """Base Widget class. Allows user to create widgets in the back-end that render
2 in the IPython notebook front-end.
2 in the IPython notebook front-end.
3 """
3 """
4 #-----------------------------------------------------------------------------
4 #-----------------------------------------------------------------------------
5 # Copyright (c) 2013, the IPython Development Team.
5 # Copyright (c) 2013, the IPython Development Team.
6 #
6 #
7 # Distributed under the terms of the Modified BSD License.
7 # Distributed under the terms of the Modified BSD License.
8 #
8 #
9 # The full license is in the file COPYING.txt, distributed with this software.
9 # The full license is in the file COPYING.txt, distributed with this software.
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Imports
13 # Imports
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 from contextlib import contextmanager
15 from contextlib import contextmanager
16 import collections
16 import collections
17
17
18 from IPython.core.getipython import get_ipython
18 from IPython.core.getipython import get_ipython
19 from IPython.kernel.comm import Comm
19 from IPython.kernel.comm import Comm
20 from IPython.config import LoggingConfigurable
20 from IPython.config import LoggingConfigurable
21 from IPython.utils.importstring import import_item
21 from IPython.utils.importstring import import_item
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
22 from IPython.utils.traitlets import Unicode, Dict, Instance, Bool, List, \
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
23 CaselessStrEnum, Tuple, CUnicode, Int, Set
24 from IPython.utils.py3compat import string_types
24 from IPython.utils.py3compat import string_types
25 from .trait_types import Color
25 from .trait_types import Color
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Classes
28 # Classes
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 class CallbackDispatcher(LoggingConfigurable):
30 class CallbackDispatcher(LoggingConfigurable):
31 """A structure for registering and running callbacks"""
31 """A structure for registering and running callbacks"""
32 callbacks = List()
32 callbacks = List()
33
33
34 def __call__(self, *args, **kwargs):
34 def __call__(self, *args, **kwargs):
35 """Call all of the registered callbacks."""
35 """Call all of the registered callbacks."""
36 value = None
36 value = None
37 for callback in self.callbacks:
37 for callback in self.callbacks:
38 try:
38 try:
39 local_value = callback(*args, **kwargs)
39 local_value = callback(*args, **kwargs)
40 except Exception as e:
40 except Exception as e:
41 ip = get_ipython()
41 ip = get_ipython()
42 if ip is None:
42 if ip is None:
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
43 self.log.warn("Exception in callback %s: %s", callback, e, exc_info=True)
44 else:
44 else:
45 ip.showtraceback()
45 ip.showtraceback()
46 else:
46 else:
47 value = local_value if local_value is not None else value
47 value = local_value if local_value is not None else value
48 return value
48 return value
49
49
50 def register_callback(self, callback, remove=False):
50 def register_callback(self, callback, remove=False):
51 """(Un)Register a callback
51 """(Un)Register a callback
52
52
53 Parameters
53 Parameters
54 ----------
54 ----------
55 callback: method handle
55 callback: method handle
56 Method to be registered or unregistered.
56 Method to be registered or unregistered.
57 remove=False: bool
57 remove=False: bool
58 Whether to unregister the callback."""
58 Whether to unregister the callback."""
59
59
60 # (Un)Register the callback.
60 # (Un)Register the callback.
61 if remove and callback in self.callbacks:
61 if remove and callback in self.callbacks:
62 self.callbacks.remove(callback)
62 self.callbacks.remove(callback)
63 elif not remove and callback not in self.callbacks:
63 elif not remove and callback not in self.callbacks:
64 self.callbacks.append(callback)
64 self.callbacks.append(callback)
65
65
66 def _show_traceback(method):
66 def _show_traceback(method):
67 """decorator for showing tracebacks in IPython"""
67 """decorator for showing tracebacks in IPython"""
68 def m(self, *args, **kwargs):
68 def m(self, *args, **kwargs):
69 try:
69 try:
70 return(method(self, *args, **kwargs))
70 return(method(self, *args, **kwargs))
71 except Exception as e:
71 except Exception as e:
72 ip = get_ipython()
72 ip = get_ipython()
73 if ip is None:
73 if ip is None:
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
74 self.log.warn("Exception in widget method %s: %s", method, e, exc_info=True)
75 else:
75 else:
76 ip.showtraceback()
76 ip.showtraceback()
77 return m
77 return m
78
78
79
79
80 def register(key=None):
80 def register(key=None):
81 """Returns a decorator registering a widget class in the widget registry.
81 """Returns a decorator registering a widget class in the widget registry.
82 If no key is provided, the class name is used as a key. A key is
82 If no key is provided, the class name is used as a key. A key is
83 provided for each core IPython widget so that the frontend can use
83 provided for each core IPython widget so that the frontend can use
84 this key regardless of the language of the kernel"""
84 this key regardless of the language of the kernel"""
85 def wrap(widget):
85 def wrap(widget):
86 l = key if key is not None else widget.__module__ + widget.__name__
86 l = key if key is not None else widget.__module__ + widget.__name__
87 Widget.widget_types[l] = widget
87 Widget.widget_types[l] = widget
88 return widget
88 return widget
89 return wrap
89 return wrap
90
90
91
91
92 class Widget(LoggingConfigurable):
92 class Widget(LoggingConfigurable):
93 #-------------------------------------------------------------------------
93 #-------------------------------------------------------------------------
94 # Class attributes
94 # Class attributes
95 #-------------------------------------------------------------------------
95 #-------------------------------------------------------------------------
96 _widget_construction_callback = None
96 _widget_construction_callback = None
97 widgets = {}
97 widgets = {}
98 widget_types = {}
98 widget_types = {}
99
99
100 @staticmethod
100 @staticmethod
101 def on_widget_constructed(callback):
101 def on_widget_constructed(callback):
102 """Registers a callback to be called when a widget is constructed.
102 """Registers a callback to be called when a widget is constructed.
103
103
104 The callback must have the following signature:
104 The callback must have the following signature:
105 callback(widget)"""
105 callback(widget)"""
106 Widget._widget_construction_callback = callback
106 Widget._widget_construction_callback = callback
107
107
108 @staticmethod
108 @staticmethod
109 def _call_widget_constructed(widget):
109 def _call_widget_constructed(widget):
110 """Static method, called when a widget is constructed."""
110 """Static method, called when a widget is constructed."""
111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
111 if Widget._widget_construction_callback is not None and callable(Widget._widget_construction_callback):
112 Widget._widget_construction_callback(widget)
112 Widget._widget_construction_callback(widget)
113
113
114 @staticmethod
114 @staticmethod
115 def handle_comm_opened(comm, msg):
115 def handle_comm_opened(comm, msg):
116 """Static method, called when a widget is constructed."""
116 """Static method, called when a widget is constructed."""
117 widget_class = import_item(msg['content']['data']['widget_class'])
117 widget_class = import_item(msg['content']['data']['widget_class'])
118 widget = widget_class(comm=comm)
118 widget = widget_class(comm=comm)
119
119
120
120
121 #-------------------------------------------------------------------------
121 #-------------------------------------------------------------------------
122 # Traits
122 # Traits
123 #-------------------------------------------------------------------------
123 #-------------------------------------------------------------------------
124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
124 _model_module = Unicode(None, allow_none=True, help="""A requirejs module name
125 in which to find _model_name. If empty, look in the global registry.""")
125 in which to find _model_name. If empty, look in the global registry.""")
126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
126 _model_name = Unicode('WidgetModel', help="""Name of the backbone model
127 registered in the front-end to create and sync this widget with.""")
127 registered in the front-end to create and sync this widget with.""")
128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
128 _view_module = Unicode(help="""A requirejs module in which to find _view_name.
129 If empty, look in the global registry.""", sync=True)
129 If empty, look in the global registry.""", sync=True)
130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
130 _view_name = Unicode(None, allow_none=True, help="""Default view registered in the front-end
131 to use to represent the widget.""", sync=True)
131 to use to represent the widget.""", sync=True)
132 comm = Instance('IPython.kernel.comm.Comm')
132 comm = Instance('IPython.kernel.comm.Comm')
133
133
134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
134 msg_throttle = Int(3, sync=True, help="""Maximum number of msgs the
135 front-end can send before receiving an idle msg from the back-end.""")
135 front-end can send before receiving an idle msg from the back-end.""")
136
136
137 version = Int(0, sync=True, help="""Widget's version""")
137 version = Int(0, sync=True, help="""Widget's version""")
138 keys = List()
138 keys = List()
139 def _keys_default(self):
139 def _keys_default(self):
140 return [name for name in self.traits(sync=True)]
140 return [name for name in self.traits(sync=True)]
141
141
142 _property_lock = Tuple((None, None))
142 _property_lock = Tuple((None, None))
143 _send_state_lock = Int(0)
143 _send_state_lock = Int(0)
144 _states_to_send = Set()
144 _states_to_send = Set()
145 _display_callbacks = Instance(CallbackDispatcher, ())
145 _display_callbacks = Instance(CallbackDispatcher, ())
146 _msg_callbacks = Instance(CallbackDispatcher, ())
146 _msg_callbacks = Instance(CallbackDispatcher, ())
147
147
148 #-------------------------------------------------------------------------
148 #-------------------------------------------------------------------------
149 # (Con/de)structor
149 # (Con/de)structor
150 #-------------------------------------------------------------------------
150 #-------------------------------------------------------------------------
151 def __init__(self, **kwargs):
151 def __init__(self, **kwargs):
152 """Public constructor"""
152 """Public constructor"""
153 self._model_id = kwargs.pop('model_id', None)
153 self._model_id = kwargs.pop('model_id', None)
154 super(Widget, self).__init__(**kwargs)
154 super(Widget, self).__init__(**kwargs)
155
155
156 Widget._call_widget_constructed(self)
156 Widget._call_widget_constructed(self)
157 self.open()
157 self.open()
158
158
159 def __del__(self):
159 def __del__(self):
160 """Object disposal"""
160 """Object disposal"""
161 self.close()
161 self.close()
162
162
163 #-------------------------------------------------------------------------
163 #-------------------------------------------------------------------------
164 # Properties
164 # Properties
165 #-------------------------------------------------------------------------
165 #-------------------------------------------------------------------------
166
166
167 def open(self):
167 def open(self):
168 """Open a comm to the frontend if one isn't already open."""
168 """Open a comm to the frontend if one isn't already open."""
169 if self.comm is None:
169 if self.comm is None:
170 args = dict(target_name='ipython.widget',
170 args = dict(target_name='ipython.widget',
171 data={'model_name': self._model_name,
171 data={'model_name': self._model_name,
172 'model_module': self._model_module})
172 'model_module': self._model_module})
173 if self._model_id is not None:
173 if self._model_id is not None:
174 args['comm_id'] = self._model_id
174 args['comm_id'] = self._model_id
175 self.comm = Comm(**args)
175 self.comm = Comm(**args)
176
176
177 def _comm_changed(self, name, new):
177 def _comm_changed(self, name, new):
178 """Called when the comm is changed."""
178 """Called when the comm is changed."""
179 if new is None:
179 if new is None:
180 return
180 return
181 self._model_id = self.model_id
181 self._model_id = self.model_id
182
182
183 self.comm.on_msg(self._handle_msg)
183 self.comm.on_msg(self._handle_msg)
184 Widget.widgets[self.model_id] = self
184 Widget.widgets[self.model_id] = self
185
185
186 # first update
186 # first update
187 self.send_state()
187 self.send_state()
188
188
189 @property
189 @property
190 def model_id(self):
190 def model_id(self):
191 """Gets the model id of this widget.
191 """Gets the model id of this widget.
192
192
193 If a Comm doesn't exist yet, a Comm will be created automagically."""
193 If a Comm doesn't exist yet, a Comm will be created automagically."""
194 return self.comm.comm_id
194 return self.comm.comm_id
195
195
196 #-------------------------------------------------------------------------
196 #-------------------------------------------------------------------------
197 # Methods
197 # Methods
198 #-------------------------------------------------------------------------
198 #-------------------------------------------------------------------------
199
199
200 def close(self):
200 def close(self):
201 """Close method.
201 """Close method.
202
202
203 Closes the underlying comm.
203 Closes the underlying comm.
204 When the comm is closed, all of the widget views are automatically
204 When the comm is closed, all of the widget views are automatically
205 removed from the front-end."""
205 removed from the front-end."""
206 if self.comm is not None:
206 if self.comm is not None:
207 Widget.widgets.pop(self.model_id, None)
207 Widget.widgets.pop(self.model_id, None)
208 self.comm.close()
208 self.comm.close()
209 self.comm = None
209 self.comm = None
210
210
211 def send_state(self, key=None):
211 def send_state(self, key=None):
212 """Sends the widget state, or a piece of it, to the front-end.
212 """Sends the widget state, or a piece of it, to the front-end.
213
213
214 Parameters
214 Parameters
215 ----------
215 ----------
216 key : unicode, or iterable (optional)
216 key : unicode, or iterable (optional)
217 A single property's name or iterable of property names to sync with the front-end.
217 A single property's name or iterable of property names to sync with the front-end.
218 """
218 """
219 self._send({
219 self._send({
220 "method" : "update",
220 "method" : "update",
221 "state" : self.get_state(key=key)
221 "state" : self.get_state(key=key)
222 })
222 })
223
223
224 def get_state(self, key=None):
224 def get_state(self, key=None):
225 """Gets the widget state, or a piece of it.
225 """Gets the widget state, or a piece of it.
226
226
227 Parameters
227 Parameters
228 ----------
228 ----------
229 key : unicode or iterable (optional)
229 key : unicode or iterable (optional)
230 A single property's name or iterable of property names to get.
230 A single property's name or iterable of property names to get.
231 """
231 """
232 if key is None:
232 if key is None:
233 keys = self.keys
233 keys = self.keys
234 elif isinstance(key, string_types):
234 elif isinstance(key, string_types):
235 keys = [key]
235 keys = [key]
236 elif isinstance(key, collections.Iterable):
236 elif isinstance(key, collections.Iterable):
237 keys = key
237 keys = key
238 else:
238 else:
239 raise ValueError("key must be a string, an iterable of keys, or None")
239 raise ValueError("key must be a string, an iterable of keys, or None")
240 state = {}
240 state = {}
241 for k in keys:
241 for k in keys:
242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
242 f = self.trait_metadata(k, 'to_json', self._trait_to_json)
243 value = getattr(self, k)
243 value = getattr(self, k)
244 state[k] = f(value)
244 state[k] = f(value)
245 return state
245 return state
246
246
247 def set_state(self, sync_data):
247 def set_state(self, sync_data):
248 """Called when a state is received from the front-end."""
248 """Called when a state is received from the front-end."""
249 for name in self.keys:
249 for name in self.keys:
250 if name in sync_data:
250 if name in sync_data:
251 json_value = sync_data[name]
251 json_value = sync_data[name]
252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
252 from_json = self.trait_metadata(name, 'from_json', self._trait_from_json)
253 with self._lock_property(name, json_value):
253 with self._lock_property(name, json_value):
254 setattr(self, name, from_json(json_value))
254 setattr(self, name, from_json(json_value))
255
255
256 def send(self, content):
256 def send(self, content):
257 """Sends a custom msg to the widget model in the front-end.
257 """Sends a custom msg to the widget model in the front-end.
258
258
259 Parameters
259 Parameters
260 ----------
260 ----------
261 content : dict
261 content : dict
262 Content of the message to send.
262 Content of the message to send.
263 """
263 """
264 self._send({"method": "custom", "content": content})
264 self._send({"method": "custom", "content": content})
265
265
266 def on_msg(self, callback, remove=False):
266 def on_msg(self, callback, remove=False):
267 """(Un)Register a custom msg receive callback.
267 """(Un)Register a custom msg receive callback.
268
268
269 Parameters
269 Parameters
270 ----------
270 ----------
271 callback: callable
271 callback: callable
272 callback will be passed two arguments when a message arrives::
272 callback will be passed two arguments when a message arrives::
273
273
274 callback(widget, content)
274 callback(widget, content)
275
275
276 remove: bool
276 remove: bool
277 True if the callback should be unregistered."""
277 True if the callback should be unregistered."""
278 self._msg_callbacks.register_callback(callback, remove=remove)
278 self._msg_callbacks.register_callback(callback, remove=remove)
279
279
280 def on_displayed(self, callback, remove=False):
280 def on_displayed(self, callback, remove=False):
281 """(Un)Register a widget displayed callback.
281 """(Un)Register a widget displayed callback.
282
282
283 Parameters
283 Parameters
284 ----------
284 ----------
285 callback: method handler
285 callback: method handler
286 Must have a signature of::
286 Must have a signature of::
287
287
288 callback(widget, **kwargs)
288 callback(widget, **kwargs)
289
289
290 kwargs from display are passed through without modification.
290 kwargs from display are passed through without modification.
291 remove: bool
291 remove: bool
292 True if the callback should be unregistered."""
292 True if the callback should be unregistered."""
293 self._display_callbacks.register_callback(callback, remove=remove)
293 self._display_callbacks.register_callback(callback, remove=remove)
294
294
295 def add_trait(self, traitname, trait):
296 """Dynamically add a trait attribute to the Widget."""
297 super(Widget, self).add_trait(traitname, trait)
298 if trait.get_metadata('sync'):
299 self.keys.append(traitname)
300 self.send_state(traitname)
301
295 #-------------------------------------------------------------------------
302 #-------------------------------------------------------------------------
296 # Support methods
303 # Support methods
297 #-------------------------------------------------------------------------
304 #-------------------------------------------------------------------------
298 @contextmanager
305 @contextmanager
299 def _lock_property(self, key, value):
306 def _lock_property(self, key, value):
300 """Lock a property-value pair.
307 """Lock a property-value pair.
301
308
302 The value should be the JSON state of the property.
309 The value should be the JSON state of the property.
303
310
304 NOTE: This, in addition to the single lock for all state changes, is
311 NOTE: This, in addition to the single lock for all state changes, is
305 flawed. In the future we may want to look into buffering state changes
312 flawed. In the future we may want to look into buffering state changes
306 back to the front-end."""
313 back to the front-end."""
307 self._property_lock = (key, value)
314 self._property_lock = (key, value)
308 try:
315 try:
309 yield
316 yield
310 finally:
317 finally:
311 self._property_lock = (None, None)
318 self._property_lock = (None, None)
312
319
313 @contextmanager
320 @contextmanager
314 def hold_sync(self):
321 def hold_sync(self):
315 """Hold syncing any state until the context manager is released"""
322 """Hold syncing any state until the context manager is released"""
316 # We increment a value so that this can be nested. Syncing will happen when
323 # We increment a value so that this can be nested. Syncing will happen when
317 # all levels have been released.
324 # all levels have been released.
318 self._send_state_lock += 1
325 self._send_state_lock += 1
319 try:
326 try:
320 yield
327 yield
321 finally:
328 finally:
322 self._send_state_lock -=1
329 self._send_state_lock -=1
323 if self._send_state_lock == 0:
330 if self._send_state_lock == 0:
324 self.send_state(self._states_to_send)
331 self.send_state(self._states_to_send)
325 self._states_to_send.clear()
332 self._states_to_send.clear()
326
333
327 def _should_send_property(self, key, value):
334 def _should_send_property(self, key, value):
328 """Check the property lock (property_lock)"""
335 """Check the property lock (property_lock)"""
329 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
336 to_json = self.trait_metadata(key, 'to_json', self._trait_to_json)
330 if (key == self._property_lock[0]
337 if (key == self._property_lock[0]
331 and to_json(value) == self._property_lock[1]):
338 and to_json(value) == self._property_lock[1]):
332 return False
339 return False
333 elif self._send_state_lock > 0:
340 elif self._send_state_lock > 0:
334 self._states_to_send.add(key)
341 self._states_to_send.add(key)
335 return False
342 return False
336 else:
343 else:
337 return True
344 return True
338
345
339 # Event handlers
346 # Event handlers
340 @_show_traceback
347 @_show_traceback
341 def _handle_msg(self, msg):
348 def _handle_msg(self, msg):
342 """Called when a msg is received from the front-end"""
349 """Called when a msg is received from the front-end"""
343 data = msg['content']['data']
350 data = msg['content']['data']
344 method = data['method']
351 method = data['method']
345
352
346 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
353 # Handle backbone sync methods CREATE, PATCH, and UPDATE all in one.
347 if method == 'backbone':
354 if method == 'backbone':
348 if 'sync_data' in data:
355 if 'sync_data' in data:
349 sync_data = data['sync_data']
356 sync_data = data['sync_data']
350 self.set_state(sync_data) # handles all methods
357 self.set_state(sync_data) # handles all methods
351
358
352 # Handle a state request.
359 # Handle a state request.
353 elif method == 'request_state':
360 elif method == 'request_state':
354 self.send_state()
361 self.send_state()
355
362
356 # Handle a custom msg from the front-end.
363 # Handle a custom msg from the front-end.
357 elif method == 'custom':
364 elif method == 'custom':
358 if 'content' in data:
365 if 'content' in data:
359 self._handle_custom_msg(data['content'])
366 self._handle_custom_msg(data['content'])
360
367
361 # Catch remainder.
368 # Catch remainder.
362 else:
369 else:
363 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
370 self.log.error('Unknown front-end to back-end widget msg with method "%s"' % method)
364
371
365 def _handle_custom_msg(self, content):
372 def _handle_custom_msg(self, content):
366 """Called when a custom msg is received."""
373 """Called when a custom msg is received."""
367 self._msg_callbacks(self, content)
374 self._msg_callbacks(self, content)
368
375
369 def _notify_trait(self, name, old_value, new_value):
376 def _notify_trait(self, name, old_value, new_value):
370 """Called when a property has been changed."""
377 """Called when a property has been changed."""
371 # Trigger default traitlet callback machinery. This allows any user
378 # Trigger default traitlet callback machinery. This allows any user
372 # registered validation to be processed prior to allowing the widget
379 # registered validation to be processed prior to allowing the widget
373 # machinery to handle the state.
380 # machinery to handle the state.
374 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
381 LoggingConfigurable._notify_trait(self, name, old_value, new_value)
375
382
376 # Send the state after the user registered callbacks for trait changes
383 # Send the state after the user registered callbacks for trait changes
377 # have all fired (allows for user to validate values).
384 # have all fired (allows for user to validate values).
378 if self.comm is not None and name in self.keys:
385 if self.comm is not None and name in self.keys:
379 # Make sure this isn't information that the front-end just sent us.
386 # Make sure this isn't information that the front-end just sent us.
380 if self._should_send_property(name, new_value):
387 if self._should_send_property(name, new_value):
381 # Send new state to front-end
388 # Send new state to front-end
382 self.send_state(key=name)
389 self.send_state(key=name)
383
390
384 def _handle_displayed(self, **kwargs):
391 def _handle_displayed(self, **kwargs):
385 """Called when a view has been displayed for this widget instance"""
392 """Called when a view has been displayed for this widget instance"""
386 self._display_callbacks(self, **kwargs)
393 self._display_callbacks(self, **kwargs)
387
394
388 def _trait_to_json(self, x):
395 def _trait_to_json(self, x):
389 """Convert a trait value to json
396 """Convert a trait value to json
390
397
391 Traverse lists/tuples and dicts and serialize their values as well.
398 Traverse lists/tuples and dicts and serialize their values as well.
392 Replace any widgets with their model_id
399 Replace any widgets with their model_id
393 """
400 """
394 if isinstance(x, dict):
401 if isinstance(x, dict):
395 return {k: self._trait_to_json(v) for k, v in x.items()}
402 return {k: self._trait_to_json(v) for k, v in x.items()}
396 elif isinstance(x, (list, tuple)):
403 elif isinstance(x, (list, tuple)):
397 return [self._trait_to_json(v) for v in x]
404 return [self._trait_to_json(v) for v in x]
398 elif isinstance(x, Widget):
405 elif isinstance(x, Widget):
399 return "IPY_MODEL_" + x.model_id
406 return "IPY_MODEL_" + x.model_id
400 else:
407 else:
401 return x # Value must be JSON-able
408 return x # Value must be JSON-able
402
409
403 def _trait_from_json(self, x):
410 def _trait_from_json(self, x):
404 """Convert json values to objects
411 """Convert json values to objects
405
412
406 Replace any strings representing valid model id values to Widget references.
413 Replace any strings representing valid model id values to Widget references.
407 """
414 """
408 if isinstance(x, dict):
415 if isinstance(x, dict):
409 return {k: self._trait_from_json(v) for k, v in x.items()}
416 return {k: self._trait_from_json(v) for k, v in x.items()}
410 elif isinstance(x, (list, tuple)):
417 elif isinstance(x, (list, tuple)):
411 return [self._trait_from_json(v) for v in x]
418 return [self._trait_from_json(v) for v in x]
412 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
419 elif isinstance(x, string_types) and x.startswith('IPY_MODEL_') and x[10:] in Widget.widgets:
413 # we want to support having child widgets at any level in a hierarchy
420 # we want to support having child widgets at any level in a hierarchy
414 # trusting that a widget UUID will not appear out in the wild
421 # trusting that a widget UUID will not appear out in the wild
415 return Widget.widgets[x[10:]]
422 return Widget.widgets[x[10:]]
416 else:
423 else:
417 return x
424 return x
418
425
419 def _ipython_display_(self, **kwargs):
426 def _ipython_display_(self, **kwargs):
420 """Called when `IPython.display.display` is called on the widget."""
427 """Called when `IPython.display.display` is called on the widget."""
421 # Show view.
428 # Show view.
422 if self._view_name is not None:
429 if self._view_name is not None:
423 self._send({"method": "display"})
430 self._send({"method": "display"})
424 self._handle_displayed(**kwargs)
431 self._handle_displayed(**kwargs)
425
432
426 def _send(self, msg):
433 def _send(self, msg):
427 """Sends a message to the model in the front-end."""
434 """Sends a message to the model in the front-end."""
428 self.comm.send(msg)
435 self.comm.send(msg)
429
436
430
437
431 class DOMWidget(Widget):
438 class DOMWidget(Widget):
432 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
439 visible = Bool(True, allow_none=True, help="Whether the widget is visible. False collapses the empty space, while None preserves the empty space.", sync=True)
433 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
440 _css = Tuple(sync=True, help="CSS property list: (selector, key, value)")
434 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
441 _dom_classes = Tuple(sync=True, help="DOM classes applied to widget.$el.")
435
442
436 width = CUnicode(sync=True)
443 width = CUnicode(sync=True)
437 height = CUnicode(sync=True)
444 height = CUnicode(sync=True)
438 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
445 # A default padding of 2.5 px makes the widgets look nice when displayed inline.
439 padding = CUnicode(sync=True)
446 padding = CUnicode(sync=True)
440 margin = CUnicode(sync=True)
447 margin = CUnicode(sync=True)
441
448
442 color = Color(None, allow_none=True, sync=True)
449 color = Color(None, allow_none=True, sync=True)
443 background_color = Color(None, allow_none=True, sync=True)
450 background_color = Color(None, allow_none=True, sync=True)
444 border_color = Color(None, allow_none=True, sync=True)
451 border_color = Color(None, allow_none=True, sync=True)
445
452
446 border_width = CUnicode(sync=True)
453 border_width = CUnicode(sync=True)
447 border_radius = CUnicode(sync=True)
454 border_radius = CUnicode(sync=True)
448 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
455 border_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_border-style.asp
449 'none',
456 'none',
450 'hidden',
457 'hidden',
451 'dotted',
458 'dotted',
452 'dashed',
459 'dashed',
453 'solid',
460 'solid',
454 'double',
461 'double',
455 'groove',
462 'groove',
456 'ridge',
463 'ridge',
457 'inset',
464 'inset',
458 'outset',
465 'outset',
459 'initial',
466 'initial',
460 'inherit', ''],
467 'inherit', ''],
461 default_value='', sync=True)
468 default_value='', sync=True)
462
469
463 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
470 font_style = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_font-style.asp
464 'normal',
471 'normal',
465 'italic',
472 'italic',
466 'oblique',
473 'oblique',
467 'initial',
474 'initial',
468 'inherit', ''],
475 'inherit', ''],
469 default_value='', sync=True)
476 default_value='', sync=True)
470 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
477 font_weight = CaselessStrEnum(values=[ # http://www.w3schools.com/cssref/pr_font_weight.asp
471 'normal',
478 'normal',
472 'bold',
479 'bold',
473 'bolder',
480 'bolder',
474 'lighter',
481 'lighter',
475 'initial',
482 'initial',
476 'inherit', ''] + list(map(str, range(100,1000,100))),
483 'inherit', ''] + list(map(str, range(100,1000,100))),
477 default_value='', sync=True)
484 default_value='', sync=True)
478 font_size = CUnicode(sync=True)
485 font_size = CUnicode(sync=True)
479 font_family = Unicode(sync=True)
486 font_family = Unicode(sync=True)
480
487
481 def __init__(self, *pargs, **kwargs):
488 def __init__(self, *pargs, **kwargs):
482 super(DOMWidget, self).__init__(*pargs, **kwargs)
489 super(DOMWidget, self).__init__(*pargs, **kwargs)
483
490
484 def _validate_border(name, old, new):
491 def _validate_border(name, old, new):
485 if new is not None and new != '':
492 if new is not None and new != '':
486 if name != 'border_width' and not self.border_width:
493 if name != 'border_width' and not self.border_width:
487 self.border_width = 1
494 self.border_width = 1
488 if name != 'border_style' and self.border_style == '':
495 if name != 'border_style' and self.border_style == '':
489 self.border_style = 'solid'
496 self.border_style = 'solid'
490 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
497 self.on_trait_change(_validate_border, ['border_width', 'border_style', 'border_color'])
@@ -1,1565 +1,1617 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, Bool, CBytes, Dict, Enum,
19 HasTraits, MetaHasTraits, TraitType, Any, Bool, CBytes, Dict, Enum,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
20 Int, Long, Integer, Float, Complex, Bytes, Unicode, TraitError,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
21 Union, Undefined, Type, This, Instance, TCPAddress, List, Tuple,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
22 ObjectName, DottedObjectName, CRegExp, link, directional_link,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
23 EventfulList, EventfulDict, ForwardDeclaredType, ForwardDeclaredInstance,
24 )
24 )
25 from IPython.utils import py3compat
25 from IPython.utils import py3compat
26 from IPython.testing.decorators import skipif
26 from IPython.testing.decorators import skipif
27
27
28 #-----------------------------------------------------------------------------
28 #-----------------------------------------------------------------------------
29 # Helper classes for testing
29 # Helper classes for testing
30 #-----------------------------------------------------------------------------
30 #-----------------------------------------------------------------------------
31
31
32
32
33 class HasTraitsStub(HasTraits):
33 class HasTraitsStub(HasTraits):
34
34
35 def _notify_trait(self, name, old, new):
35 def _notify_trait(self, name, old, new):
36 self._notify_name = name
36 self._notify_name = name
37 self._notify_old = old
37 self._notify_old = old
38 self._notify_new = new
38 self._notify_new = new
39
39
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Test classes
42 # Test classes
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45
45
46 class TestTraitType(TestCase):
46 class TestTraitType(TestCase):
47
47
48 def test_get_undefined(self):
48 def test_get_undefined(self):
49 class A(HasTraits):
49 class A(HasTraits):
50 a = TraitType
50 a = TraitType
51 a = A()
51 a = A()
52 self.assertEqual(a.a, Undefined)
52 self.assertEqual(a.a, Undefined)
53
53
54 def test_set(self):
54 def test_set(self):
55 class A(HasTraitsStub):
55 class A(HasTraitsStub):
56 a = TraitType
56 a = TraitType
57
57
58 a = A()
58 a = A()
59 a.a = 10
59 a.a = 10
60 self.assertEqual(a.a, 10)
60 self.assertEqual(a.a, 10)
61 self.assertEqual(a._notify_name, 'a')
61 self.assertEqual(a._notify_name, 'a')
62 self.assertEqual(a._notify_old, Undefined)
62 self.assertEqual(a._notify_old, Undefined)
63 self.assertEqual(a._notify_new, 10)
63 self.assertEqual(a._notify_new, 10)
64
64
65 def test_validate(self):
65 def test_validate(self):
66 class MyTT(TraitType):
66 class MyTT(TraitType):
67 def validate(self, inst, value):
67 def validate(self, inst, value):
68 return -1
68 return -1
69 class A(HasTraitsStub):
69 class A(HasTraitsStub):
70 tt = MyTT
70 tt = MyTT
71
71
72 a = A()
72 a = A()
73 a.tt = 10
73 a.tt = 10
74 self.assertEqual(a.tt, -1)
74 self.assertEqual(a.tt, -1)
75
75
76 def test_default_validate(self):
76 def test_default_validate(self):
77 class MyIntTT(TraitType):
77 class MyIntTT(TraitType):
78 def validate(self, obj, value):
78 def validate(self, obj, value):
79 if isinstance(value, int):
79 if isinstance(value, int):
80 return value
80 return value
81 self.error(obj, value)
81 self.error(obj, value)
82 class A(HasTraits):
82 class A(HasTraits):
83 tt = MyIntTT(10)
83 tt = MyIntTT(10)
84 a = A()
84 a = A()
85 self.assertEqual(a.tt, 10)
85 self.assertEqual(a.tt, 10)
86
86
87 # Defaults are validated when the HasTraits is instantiated
87 # Defaults are validated when the HasTraits is instantiated
88 class B(HasTraits):
88 class B(HasTraits):
89 tt = MyIntTT('bad default')
89 tt = MyIntTT('bad default')
90 self.assertRaises(TraitError, B)
90 self.assertRaises(TraitError, B)
91
91
92 def test_info(self):
92 def test_info(self):
93 class A(HasTraits):
93 class A(HasTraits):
94 tt = TraitType
94 tt = TraitType
95 a = A()
95 a = A()
96 self.assertEqual(A.tt.info(), 'any value')
96 self.assertEqual(A.tt.info(), 'any value')
97
97
98 def test_error(self):
98 def test_error(self):
99 class A(HasTraits):
99 class A(HasTraits):
100 tt = TraitType
100 tt = TraitType
101 a = A()
101 a = A()
102 self.assertRaises(TraitError, A.tt.error, a, 10)
102 self.assertRaises(TraitError, A.tt.error, a, 10)
103
103
104 def test_dynamic_initializer(self):
104 def test_dynamic_initializer(self):
105 class A(HasTraits):
105 class A(HasTraits):
106 x = Int(10)
106 x = Int(10)
107 def _x_default(self):
107 def _x_default(self):
108 return 11
108 return 11
109 class B(A):
109 class B(A):
110 x = Int(20)
110 x = Int(20)
111 class C(A):
111 class C(A):
112 def _x_default(self):
112 def _x_default(self):
113 return 21
113 return 21
114
114
115 a = A()
115 a = A()
116 self.assertEqual(a._trait_values, {})
116 self.assertEqual(a._trait_values, {})
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
117 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
118 self.assertEqual(a.x, 11)
118 self.assertEqual(a.x, 11)
119 self.assertEqual(a._trait_values, {'x': 11})
119 self.assertEqual(a._trait_values, {'x': 11})
120 b = B()
120 b = B()
121 self.assertEqual(b._trait_values, {'x': 20})
121 self.assertEqual(b._trait_values, {'x': 20})
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
122 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
123 self.assertEqual(b.x, 20)
123 self.assertEqual(b.x, 20)
124 c = C()
124 c = C()
125 self.assertEqual(c._trait_values, {})
125 self.assertEqual(c._trait_values, {})
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
126 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
127 self.assertEqual(c.x, 21)
127 self.assertEqual(c.x, 21)
128 self.assertEqual(c._trait_values, {'x': 21})
128 self.assertEqual(c._trait_values, {'x': 21})
129 # Ensure that the base class remains unmolested when the _default
129 # Ensure that the base class remains unmolested when the _default
130 # initializer gets overridden in a subclass.
130 # initializer gets overridden in a subclass.
131 a = A()
131 a = A()
132 c = C()
132 c = C()
133 self.assertEqual(a._trait_values, {})
133 self.assertEqual(a._trait_values, {})
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
134 self.assertEqual(list(a._trait_dyn_inits.keys()), ['x'])
135 self.assertEqual(a.x, 11)
135 self.assertEqual(a.x, 11)
136 self.assertEqual(a._trait_values, {'x': 11})
136 self.assertEqual(a._trait_values, {'x': 11})
137
137
138
138
139
139
140 class TestHasTraitsMeta(TestCase):
140 class TestHasTraitsMeta(TestCase):
141
141
142 def test_metaclass(self):
142 def test_metaclass(self):
143 self.assertEqual(type(HasTraits), MetaHasTraits)
143 self.assertEqual(type(HasTraits), MetaHasTraits)
144
144
145 class A(HasTraits):
145 class A(HasTraits):
146 a = Int
146 a = Int
147
147
148 a = A()
148 a = A()
149 self.assertEqual(type(a.__class__), MetaHasTraits)
149 self.assertEqual(type(a.__class__), MetaHasTraits)
150 self.assertEqual(a.a,0)
150 self.assertEqual(a.a,0)
151 a.a = 10
151 a.a = 10
152 self.assertEqual(a.a,10)
152 self.assertEqual(a.a,10)
153
153
154 class B(HasTraits):
154 class B(HasTraits):
155 b = Int()
155 b = Int()
156
156
157 b = B()
157 b = B()
158 self.assertEqual(b.b,0)
158 self.assertEqual(b.b,0)
159 b.b = 10
159 b.b = 10
160 self.assertEqual(b.b,10)
160 self.assertEqual(b.b,10)
161
161
162 class C(HasTraits):
162 class C(HasTraits):
163 c = Int(30)
163 c = Int(30)
164
164
165 c = C()
165 c = C()
166 self.assertEqual(c.c,30)
166 self.assertEqual(c.c,30)
167 c.c = 10
167 c.c = 10
168 self.assertEqual(c.c,10)
168 self.assertEqual(c.c,10)
169
169
170 def test_this_class(self):
170 def test_this_class(self):
171 class A(HasTraits):
171 class A(HasTraits):
172 t = This()
172 t = This()
173 tt = This()
173 tt = This()
174 class B(A):
174 class B(A):
175 tt = This()
175 tt = This()
176 ttt = This()
176 ttt = This()
177 self.assertEqual(A.t.this_class, A)
177 self.assertEqual(A.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
178 self.assertEqual(B.t.this_class, A)
179 self.assertEqual(B.tt.this_class, B)
179 self.assertEqual(B.tt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
180 self.assertEqual(B.ttt.this_class, B)
181
181
182 class TestHasTraitsNotify(TestCase):
182 class TestHasTraitsNotify(TestCase):
183
183
184 def setUp(self):
184 def setUp(self):
185 self._notify1 = []
185 self._notify1 = []
186 self._notify2 = []
186 self._notify2 = []
187
187
188 def notify1(self, name, old, new):
188 def notify1(self, name, old, new):
189 self._notify1.append((name, old, new))
189 self._notify1.append((name, old, new))
190
190
191 def notify2(self, name, old, new):
191 def notify2(self, name, old, new):
192 self._notify2.append((name, old, new))
192 self._notify2.append((name, old, new))
193
193
194 def test_notify_all(self):
194 def test_notify_all(self):
195
195
196 class A(HasTraits):
196 class A(HasTraits):
197 a = Int
197 a = Int
198 b = Float
198 b = Float
199
199
200 a = A()
200 a = A()
201 a.on_trait_change(self.notify1)
201 a.on_trait_change(self.notify1)
202 a.a = 0
202 a.a = 0
203 self.assertEqual(len(self._notify1),0)
203 self.assertEqual(len(self._notify1),0)
204 a.b = 0.0
204 a.b = 0.0
205 self.assertEqual(len(self._notify1),0)
205 self.assertEqual(len(self._notify1),0)
206 a.a = 10
206 a.a = 10
207 self.assertTrue(('a',0,10) in self._notify1)
207 self.assertTrue(('a',0,10) in self._notify1)
208 a.b = 10.0
208 a.b = 10.0
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
209 self.assertTrue(('b',0.0,10.0) in self._notify1)
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
210 self.assertRaises(TraitError,setattr,a,'a','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
211 self.assertRaises(TraitError,setattr,a,'b','bad string')
212 self._notify1 = []
212 self._notify1 = []
213 a.on_trait_change(self.notify1,remove=True)
213 a.on_trait_change(self.notify1,remove=True)
214 a.a = 20
214 a.a = 20
215 a.b = 20.0
215 a.b = 20.0
216 self.assertEqual(len(self._notify1),0)
216 self.assertEqual(len(self._notify1),0)
217
217
218 def test_notify_one(self):
218 def test_notify_one(self):
219
219
220 class A(HasTraits):
220 class A(HasTraits):
221 a = Int
221 a = Int
222 b = Float
222 b = Float
223
223
224 a = A()
224 a = A()
225 a.on_trait_change(self.notify1, 'a')
225 a.on_trait_change(self.notify1, 'a')
226 a.a = 0
226 a.a = 0
227 self.assertEqual(len(self._notify1),0)
227 self.assertEqual(len(self._notify1),0)
228 a.a = 10
228 a.a = 10
229 self.assertTrue(('a',0,10) in self._notify1)
229 self.assertTrue(('a',0,10) in self._notify1)
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
230 self.assertRaises(TraitError,setattr,a,'a','bad string')
231
231
232 def test_subclass(self):
232 def test_subclass(self):
233
233
234 class A(HasTraits):
234 class A(HasTraits):
235 a = Int
235 a = Int
236
236
237 class B(A):
237 class B(A):
238 b = Float
238 b = Float
239
239
240 b = B()
240 b = B()
241 self.assertEqual(b.a,0)
241 self.assertEqual(b.a,0)
242 self.assertEqual(b.b,0.0)
242 self.assertEqual(b.b,0.0)
243 b.a = 100
243 b.a = 100
244 b.b = 100.0
244 b.b = 100.0
245 self.assertEqual(b.a,100)
245 self.assertEqual(b.a,100)
246 self.assertEqual(b.b,100.0)
246 self.assertEqual(b.b,100.0)
247
247
248 def test_notify_subclass(self):
248 def test_notify_subclass(self):
249
249
250 class A(HasTraits):
250 class A(HasTraits):
251 a = Int
251 a = Int
252
252
253 class B(A):
253 class B(A):
254 b = Float
254 b = Float
255
255
256 b = B()
256 b = B()
257 b.on_trait_change(self.notify1, 'a')
257 b.on_trait_change(self.notify1, 'a')
258 b.on_trait_change(self.notify2, 'b')
258 b.on_trait_change(self.notify2, 'b')
259 b.a = 0
259 b.a = 0
260 b.b = 0.0
260 b.b = 0.0
261 self.assertEqual(len(self._notify1),0)
261 self.assertEqual(len(self._notify1),0)
262 self.assertEqual(len(self._notify2),0)
262 self.assertEqual(len(self._notify2),0)
263 b.a = 10
263 b.a = 10
264 b.b = 10.0
264 b.b = 10.0
265 self.assertTrue(('a',0,10) in self._notify1)
265 self.assertTrue(('a',0,10) in self._notify1)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
266 self.assertTrue(('b',0.0,10.0) in self._notify2)
267
267
268 def test_static_notify(self):
268 def test_static_notify(self):
269
269
270 class A(HasTraits):
270 class A(HasTraits):
271 a = Int
271 a = Int
272 _notify1 = []
272 _notify1 = []
273 def _a_changed(self, name, old, new):
273 def _a_changed(self, name, old, new):
274 self._notify1.append((name, old, new))
274 self._notify1.append((name, old, new))
275
275
276 a = A()
276 a = A()
277 a.a = 0
277 a.a = 0
278 # This is broken!!!
278 # This is broken!!!
279 self.assertEqual(len(a._notify1),0)
279 self.assertEqual(len(a._notify1),0)
280 a.a = 10
280 a.a = 10
281 self.assertTrue(('a',0,10) in a._notify1)
281 self.assertTrue(('a',0,10) in a._notify1)
282
282
283 class B(A):
283 class B(A):
284 b = Float
284 b = Float
285 _notify2 = []
285 _notify2 = []
286 def _b_changed(self, name, old, new):
286 def _b_changed(self, name, old, new):
287 self._notify2.append((name, old, new))
287 self._notify2.append((name, old, new))
288
288
289 b = B()
289 b = B()
290 b.a = 10
290 b.a = 10
291 b.b = 10.0
291 b.b = 10.0
292 self.assertTrue(('a',0,10) in b._notify1)
292 self.assertTrue(('a',0,10) in b._notify1)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
293 self.assertTrue(('b',0.0,10.0) in b._notify2)
294
294
295 def test_notify_args(self):
295 def test_notify_args(self):
296
296
297 def callback0():
297 def callback0():
298 self.cb = ()
298 self.cb = ()
299 def callback1(name):
299 def callback1(name):
300 self.cb = (name,)
300 self.cb = (name,)
301 def callback2(name, new):
301 def callback2(name, new):
302 self.cb = (name, new)
302 self.cb = (name, new)
303 def callback3(name, old, new):
303 def callback3(name, old, new):
304 self.cb = (name, old, new)
304 self.cb = (name, old, new)
305
305
306 class A(HasTraits):
306 class A(HasTraits):
307 a = Int
307 a = Int
308
308
309 a = A()
309 a = A()
310 a.on_trait_change(callback0, 'a')
310 a.on_trait_change(callback0, 'a')
311 a.a = 10
311 a.a = 10
312 self.assertEqual(self.cb,())
312 self.assertEqual(self.cb,())
313 a.on_trait_change(callback0, 'a', remove=True)
313 a.on_trait_change(callback0, 'a', remove=True)
314
314
315 a.on_trait_change(callback1, 'a')
315 a.on_trait_change(callback1, 'a')
316 a.a = 100
316 a.a = 100
317 self.assertEqual(self.cb,('a',))
317 self.assertEqual(self.cb,('a',))
318 a.on_trait_change(callback1, 'a', remove=True)
318 a.on_trait_change(callback1, 'a', remove=True)
319
319
320 a.on_trait_change(callback2, 'a')
320 a.on_trait_change(callback2, 'a')
321 a.a = 1000
321 a.a = 1000
322 self.assertEqual(self.cb,('a',1000))
322 self.assertEqual(self.cb,('a',1000))
323 a.on_trait_change(callback2, 'a', remove=True)
323 a.on_trait_change(callback2, 'a', remove=True)
324
324
325 a.on_trait_change(callback3, 'a')
325 a.on_trait_change(callback3, 'a')
326 a.a = 10000
326 a.a = 10000
327 self.assertEqual(self.cb,('a',1000,10000))
327 self.assertEqual(self.cb,('a',1000,10000))
328 a.on_trait_change(callback3, 'a', remove=True)
328 a.on_trait_change(callback3, 'a', remove=True)
329
329
330 self.assertEqual(len(a._trait_notifiers['a']),0)
330 self.assertEqual(len(a._trait_notifiers['a']),0)
331
331
332 def test_notify_only_once(self):
332 def test_notify_only_once(self):
333
333
334 class A(HasTraits):
334 class A(HasTraits):
335 listen_to = ['a']
335 listen_to = ['a']
336
336
337 a = Int(0)
337 a = Int(0)
338 b = 0
338 b = 0
339
339
340 def __init__(self, **kwargs):
340 def __init__(self, **kwargs):
341 super(A, self).__init__(**kwargs)
341 super(A, self).__init__(**kwargs)
342 self.on_trait_change(self.listener1, ['a'])
342 self.on_trait_change(self.listener1, ['a'])
343
343
344 def listener1(self, name, old, new):
344 def listener1(self, name, old, new):
345 self.b += 1
345 self.b += 1
346
346
347 class B(A):
347 class B(A):
348
348
349 c = 0
349 c = 0
350 d = 0
350 d = 0
351
351
352 def __init__(self, **kwargs):
352 def __init__(self, **kwargs):
353 super(B, self).__init__(**kwargs)
353 super(B, self).__init__(**kwargs)
354 self.on_trait_change(self.listener2)
354 self.on_trait_change(self.listener2)
355
355
356 def listener2(self, name, old, new):
356 def listener2(self, name, old, new):
357 self.c += 1
357 self.c += 1
358
358
359 def _a_changed(self, name, old, new):
359 def _a_changed(self, name, old, new):
360 self.d += 1
360 self.d += 1
361
361
362 b = B()
362 b = B()
363 b.a += 1
363 b.a += 1
364 self.assertEqual(b.b, b.c)
364 self.assertEqual(b.b, b.c)
365 self.assertEqual(b.b, b.d)
365 self.assertEqual(b.b, b.d)
366 b.a += 1
366 b.a += 1
367 self.assertEqual(b.b, b.c)
367 self.assertEqual(b.b, b.c)
368 self.assertEqual(b.b, b.d)
368 self.assertEqual(b.b, b.d)
369
369
370
370
371 class TestHasTraits(TestCase):
371 class TestHasTraits(TestCase):
372
372
373 def test_trait_names(self):
373 def test_trait_names(self):
374 class A(HasTraits):
374 class A(HasTraits):
375 i = Int
375 i = Int
376 f = Float
376 f = Float
377 a = A()
377 a = A()
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
378 self.assertEqual(sorted(a.trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
379 self.assertEqual(sorted(A.class_trait_names()),['f','i'])
380
380
381 def test_trait_metadata(self):
381 def test_trait_metadata(self):
382 class A(HasTraits):
382 class A(HasTraits):
383 i = Int(config_key='MY_VALUE')
383 i = Int(config_key='MY_VALUE')
384 a = A()
384 a = A()
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
385 self.assertEqual(a.trait_metadata('i','config_key'), 'MY_VALUE')
386
386
387 def test_trait_metadata_default(self):
387 def test_trait_metadata_default(self):
388 class A(HasTraits):
388 class A(HasTraits):
389 i = Int()
389 i = Int()
390 a = A()
390 a = A()
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
391 self.assertEqual(a.trait_metadata('i', 'config_key'), None)
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
392 self.assertEqual(a.trait_metadata('i', 'config_key', 'default'), 'default')
393
393
394 def test_traits(self):
394 def test_traits(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(a.traits(), dict(i=A.i, f=A.f))
399 self.assertEqual(a.traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
400 self.assertEqual(A.class_traits(), dict(i=A.i, f=A.f))
401
401
402 def test_traits_metadata(self):
402 def test_traits_metadata(self):
403 class A(HasTraits):
403 class A(HasTraits):
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
404 i = Int(config_key='VALUE1', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
405 f = Float(config_key='VALUE3', other_thing='VALUE2')
406 j = Int(0)
406 j = Int(0)
407 a = A()
407 a = A()
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
408 self.assertEqual(a.traits(), dict(i=A.i, f=A.f, j=A.j))
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
409 traits = a.traits(config_key='VALUE1', other_thing='VALUE2')
410 self.assertEqual(traits, dict(i=A.i))
410 self.assertEqual(traits, dict(i=A.i))
411
411
412 # This passes, but it shouldn't because I am replicating a bug in
412 # This passes, but it shouldn't because I am replicating a bug in
413 # traits.
413 # traits.
414 traits = a.traits(config_key=lambda v: True)
414 traits = a.traits(config_key=lambda v: True)
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
415 self.assertEqual(traits, dict(i=A.i, f=A.f, j=A.j))
416
416
417 def test_init(self):
417 def test_init(self):
418 class A(HasTraits):
418 class A(HasTraits):
419 i = Int()
419 i = Int()
420 x = Float()
420 x = Float()
421 a = A(i=1, x=10.0)
421 a = A(i=1, x=10.0)
422 self.assertEqual(a.i, 1)
422 self.assertEqual(a.i, 1)
423 self.assertEqual(a.x, 10.0)
423 self.assertEqual(a.x, 10.0)
424
424
425 def test_positional_args(self):
425 def test_positional_args(self):
426 class A(HasTraits):
426 class A(HasTraits):
427 i = Int(0)
427 i = Int(0)
428 def __init__(self, i):
428 def __init__(self, i):
429 super(A, self).__init__()
429 super(A, self).__init__()
430 self.i = i
430 self.i = i
431
431
432 a = A(5)
432 a = A(5)
433 self.assertEqual(a.i, 5)
433 self.assertEqual(a.i, 5)
434 # should raise TypeError if no positional arg given
434 # should raise TypeError if no positional arg given
435 self.assertRaises(TypeError, A)
435 self.assertRaises(TypeError, A)
436
436
437 #-----------------------------------------------------------------------------
437 #-----------------------------------------------------------------------------
438 # Tests for specific trait types
438 # Tests for specific trait types
439 #-----------------------------------------------------------------------------
439 #-----------------------------------------------------------------------------
440
440
441
441
442 class TestType(TestCase):
442 class TestType(TestCase):
443
443
444 def test_default(self):
444 def test_default(self):
445
445
446 class B(object): pass
446 class B(object): pass
447 class A(HasTraits):
447 class A(HasTraits):
448 klass = Type
448 klass = Type
449
449
450 a = A()
450 a = A()
451 self.assertEqual(a.klass, None)
451 self.assertEqual(a.klass, None)
452
452
453 a.klass = B
453 a.klass = B
454 self.assertEqual(a.klass, B)
454 self.assertEqual(a.klass, B)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
455 self.assertRaises(TraitError, setattr, a, 'klass', 10)
456
456
457 def test_value(self):
457 def test_value(self):
458
458
459 class B(object): pass
459 class B(object): pass
460 class C(object): pass
460 class C(object): pass
461 class A(HasTraits):
461 class A(HasTraits):
462 klass = Type(B)
462 klass = Type(B)
463
463
464 a = A()
464 a = A()
465 self.assertEqual(a.klass, B)
465 self.assertEqual(a.klass, B)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
466 self.assertRaises(TraitError, setattr, a, 'klass', C)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
467 self.assertRaises(TraitError, setattr, a, 'klass', object)
468 a.klass = B
468 a.klass = B
469
469
470 def test_allow_none(self):
470 def test_allow_none(self):
471
471
472 class B(object): pass
472 class B(object): pass
473 class C(B): pass
473 class C(B): pass
474 class A(HasTraits):
474 class A(HasTraits):
475 klass = Type(B, allow_none=False)
475 klass = Type(B, allow_none=False)
476
476
477 a = A()
477 a = A()
478 self.assertEqual(a.klass, B)
478 self.assertEqual(a.klass, B)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
479 self.assertRaises(TraitError, setattr, a, 'klass', None)
480 a.klass = C
480 a.klass = C
481 self.assertEqual(a.klass, C)
481 self.assertEqual(a.klass, C)
482
482
483 def test_validate_klass(self):
483 def test_validate_klass(self):
484
484
485 class A(HasTraits):
485 class A(HasTraits):
486 klass = Type('no strings allowed')
486 klass = Type('no strings allowed')
487
487
488 self.assertRaises(ImportError, A)
488 self.assertRaises(ImportError, A)
489
489
490 class A(HasTraits):
490 class A(HasTraits):
491 klass = Type('rub.adub.Duck')
491 klass = Type('rub.adub.Duck')
492
492
493 self.assertRaises(ImportError, A)
493 self.assertRaises(ImportError, A)
494
494
495 def test_validate_default(self):
495 def test_validate_default(self):
496
496
497 class B(object): pass
497 class B(object): pass
498 class A(HasTraits):
498 class A(HasTraits):
499 klass = Type('bad default', B)
499 klass = Type('bad default', B)
500
500
501 self.assertRaises(ImportError, A)
501 self.assertRaises(ImportError, A)
502
502
503 class C(HasTraits):
503 class C(HasTraits):
504 klass = Type(None, B, allow_none=False)
504 klass = Type(None, B, allow_none=False)
505
505
506 self.assertRaises(TraitError, C)
506 self.assertRaises(TraitError, C)
507
507
508 def test_str_klass(self):
508 def test_str_klass(self):
509
509
510 class A(HasTraits):
510 class A(HasTraits):
511 klass = Type('IPython.utils.ipstruct.Struct')
511 klass = Type('IPython.utils.ipstruct.Struct')
512
512
513 from IPython.utils.ipstruct import Struct
513 from IPython.utils.ipstruct import Struct
514 a = A()
514 a = A()
515 a.klass = Struct
515 a.klass = Struct
516 self.assertEqual(a.klass, Struct)
516 self.assertEqual(a.klass, Struct)
517
517
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
518 self.assertRaises(TraitError, setattr, a, 'klass', 10)
519
519
520 def test_set_str_klass(self):
520 def test_set_str_klass(self):
521
521
522 class A(HasTraits):
522 class A(HasTraits):
523 klass = Type()
523 klass = Type()
524
524
525 a = A(klass='IPython.utils.ipstruct.Struct')
525 a = A(klass='IPython.utils.ipstruct.Struct')
526 from IPython.utils.ipstruct import Struct
526 from IPython.utils.ipstruct import Struct
527 self.assertEqual(a.klass, Struct)
527 self.assertEqual(a.klass, Struct)
528
528
529 class TestInstance(TestCase):
529 class TestInstance(TestCase):
530
530
531 def test_basic(self):
531 def test_basic(self):
532 class Foo(object): pass
532 class Foo(object): pass
533 class Bar(Foo): pass
533 class Bar(Foo): pass
534 class Bah(object): pass
534 class Bah(object): pass
535
535
536 class A(HasTraits):
536 class A(HasTraits):
537 inst = Instance(Foo)
537 inst = Instance(Foo)
538
538
539 a = A()
539 a = A()
540 self.assertTrue(a.inst is None)
540 self.assertTrue(a.inst is None)
541 a.inst = Foo()
541 a.inst = Foo()
542 self.assertTrue(isinstance(a.inst, Foo))
542 self.assertTrue(isinstance(a.inst, Foo))
543 a.inst = Bar()
543 a.inst = Bar()
544 self.assertTrue(isinstance(a.inst, Foo))
544 self.assertTrue(isinstance(a.inst, Foo))
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
545 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
546 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
547 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
548
548
549 def test_default_klass(self):
549 def test_default_klass(self):
550 class Foo(object): pass
550 class Foo(object): pass
551 class Bar(Foo): pass
551 class Bar(Foo): pass
552 class Bah(object): pass
552 class Bah(object): pass
553
553
554 class FooInstance(Instance):
554 class FooInstance(Instance):
555 klass = Foo
555 klass = Foo
556
556
557 class A(HasTraits):
557 class A(HasTraits):
558 inst = FooInstance()
558 inst = FooInstance()
559
559
560 a = A()
560 a = A()
561 self.assertTrue(a.inst is None)
561 self.assertTrue(a.inst is None)
562 a.inst = Foo()
562 a.inst = Foo()
563 self.assertTrue(isinstance(a.inst, Foo))
563 self.assertTrue(isinstance(a.inst, Foo))
564 a.inst = Bar()
564 a.inst = Bar()
565 self.assertTrue(isinstance(a.inst, Foo))
565 self.assertTrue(isinstance(a.inst, Foo))
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
566 self.assertRaises(TraitError, setattr, a, 'inst', Foo)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
567 self.assertRaises(TraitError, setattr, a, 'inst', Bar)
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
568 self.assertRaises(TraitError, setattr, a, 'inst', Bah())
569
569
570 def test_unique_default_value(self):
570 def test_unique_default_value(self):
571 class Foo(object): pass
571 class Foo(object): pass
572 class A(HasTraits):
572 class A(HasTraits):
573 inst = Instance(Foo,(),{})
573 inst = Instance(Foo,(),{})
574
574
575 a = A()
575 a = A()
576 b = A()
576 b = A()
577 self.assertTrue(a.inst is not b.inst)
577 self.assertTrue(a.inst is not b.inst)
578
578
579 def test_args_kw(self):
579 def test_args_kw(self):
580 class Foo(object):
580 class Foo(object):
581 def __init__(self, c): self.c = c
581 def __init__(self, c): self.c = c
582 class Bar(object): pass
582 class Bar(object): pass
583 class Bah(object):
583 class Bah(object):
584 def __init__(self, c, d):
584 def __init__(self, c, d):
585 self.c = c; self.d = d
585 self.c = c; self.d = d
586
586
587 class A(HasTraits):
587 class A(HasTraits):
588 inst = Instance(Foo, (10,))
588 inst = Instance(Foo, (10,))
589 a = A()
589 a = A()
590 self.assertEqual(a.inst.c, 10)
590 self.assertEqual(a.inst.c, 10)
591
591
592 class B(HasTraits):
592 class B(HasTraits):
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
593 inst = Instance(Bah, args=(10,), kw=dict(d=20))
594 b = B()
594 b = B()
595 self.assertEqual(b.inst.c, 10)
595 self.assertEqual(b.inst.c, 10)
596 self.assertEqual(b.inst.d, 20)
596 self.assertEqual(b.inst.d, 20)
597
597
598 class C(HasTraits):
598 class C(HasTraits):
599 inst = Instance(Foo)
599 inst = Instance(Foo)
600 c = C()
600 c = C()
601 self.assertTrue(c.inst is None)
601 self.assertTrue(c.inst is None)
602
602
603 def test_bad_default(self):
603 def test_bad_default(self):
604 class Foo(object): pass
604 class Foo(object): pass
605
605
606 class A(HasTraits):
606 class A(HasTraits):
607 inst = Instance(Foo, allow_none=False)
607 inst = Instance(Foo, allow_none=False)
608
608
609 self.assertRaises(TraitError, A)
609 self.assertRaises(TraitError, A)
610
610
611 def test_instance(self):
611 def test_instance(self):
612 class Foo(object): pass
612 class Foo(object): pass
613
613
614 def inner():
614 def inner():
615 class A(HasTraits):
615 class A(HasTraits):
616 inst = Instance(Foo())
616 inst = Instance(Foo())
617
617
618 self.assertRaises(TraitError, inner)
618 self.assertRaises(TraitError, inner)
619
619
620
620
621 class TestThis(TestCase):
621 class TestThis(TestCase):
622
622
623 def test_this_class(self):
623 def test_this_class(self):
624 class Foo(HasTraits):
624 class Foo(HasTraits):
625 this = This
625 this = This
626
626
627 f = Foo()
627 f = Foo()
628 self.assertEqual(f.this, None)
628 self.assertEqual(f.this, None)
629 g = Foo()
629 g = Foo()
630 f.this = g
630 f.this = g
631 self.assertEqual(f.this, g)
631 self.assertEqual(f.this, g)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
632 self.assertRaises(TraitError, setattr, f, 'this', 10)
633
633
634 def test_this_inst(self):
634 def test_this_inst(self):
635 class Foo(HasTraits):
635 class Foo(HasTraits):
636 this = This()
636 this = This()
637
637
638 f = Foo()
638 f = Foo()
639 f.this = Foo()
639 f.this = Foo()
640 self.assertTrue(isinstance(f.this, Foo))
640 self.assertTrue(isinstance(f.this, Foo))
641
641
642 def test_subclass(self):
642 def test_subclass(self):
643 class Foo(HasTraits):
643 class Foo(HasTraits):
644 t = This()
644 t = This()
645 class Bar(Foo):
645 class Bar(Foo):
646 pass
646 pass
647 f = Foo()
647 f = Foo()
648 b = Bar()
648 b = Bar()
649 f.t = b
649 f.t = b
650 b.t = f
650 b.t = f
651 self.assertEqual(f.t, b)
651 self.assertEqual(f.t, b)
652 self.assertEqual(b.t, f)
652 self.assertEqual(b.t, f)
653
653
654 def test_subclass_override(self):
654 def test_subclass_override(self):
655 class Foo(HasTraits):
655 class Foo(HasTraits):
656 t = This()
656 t = This()
657 class Bar(Foo):
657 class Bar(Foo):
658 t = This()
658 t = This()
659 f = Foo()
659 f = Foo()
660 b = Bar()
660 b = Bar()
661 f.t = b
661 f.t = b
662 self.assertEqual(f.t, b)
662 self.assertEqual(f.t, b)
663 self.assertRaises(TraitError, setattr, b, 't', f)
663 self.assertRaises(TraitError, setattr, b, 't', f)
664
664
665 def test_this_in_container(self):
665 def test_this_in_container(self):
666
666
667 class Tree(HasTraits):
667 class Tree(HasTraits):
668 value = Unicode()
668 value = Unicode()
669 leaves = List(This())
669 leaves = List(This())
670
670
671 tree = Tree(
671 tree = Tree(
672 value='foo',
672 value='foo',
673 leaves=[Tree('bar'), Tree('buzz')]
673 leaves=[Tree('bar'), Tree('buzz')]
674 )
674 )
675
675
676 with self.assertRaises(TraitError):
676 with self.assertRaises(TraitError):
677 tree.leaves = [1, 2]
677 tree.leaves = [1, 2]
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 class UnionTrait(HasTraits):
741 class UnionTrait(HasTraits):
742
742
743 value = Union([Type(), Bool()])
743 value = Union([Type(), Bool()])
744
744
745 class UnionTraitTest(TraitTestBase):
745 class UnionTraitTest(TraitTestBase):
746
746
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
747 obj = UnionTrait(value='IPython.utils.ipstruct.Struct')
748 _good_values = [int, float, True]
748 _good_values = [int, float, True]
749 _bad_values = [[], (0,), 1j]
749 _bad_values = [[], (0,), 1j]
750
750
751 class OrTrait(HasTraits):
751 class OrTrait(HasTraits):
752
752
753 value = Bool() | Unicode()
753 value = Bool() | Unicode()
754
754
755 class OrTraitTest(TraitTestBase):
755 class OrTraitTest(TraitTestBase):
756
756
757 obj = OrTrait()
757 obj = OrTrait()
758 _good_values = [True, False, 'ten']
758 _good_values = [True, False, 'ten']
759 _bad_values = [[], (0,), 1j]
759 _bad_values = [[], (0,), 1j]
760
760
761 class IntTrait(HasTraits):
761 class IntTrait(HasTraits):
762
762
763 value = Int(99)
763 value = Int(99)
764
764
765 class TestInt(TraitTestBase):
765 class TestInt(TraitTestBase):
766
766
767 obj = IntTrait()
767 obj = IntTrait()
768 _default_value = 99
768 _default_value = 99
769 _good_values = [10, -10]
769 _good_values = [10, -10]
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
770 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None, 1j,
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
771 10.1, -10.1, '10L', '-10L', '10.1', '-10.1', u'10L',
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
772 u'-10L', u'10.1', u'-10.1', '10', '-10', u'10', u'-10']
773 if not py3compat.PY3:
773 if not py3compat.PY3:
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
774 _bad_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
775
775
776
776
777 class LongTrait(HasTraits):
777 class LongTrait(HasTraits):
778
778
779 value = Long(99 if py3compat.PY3 else long(99))
779 value = Long(99 if py3compat.PY3 else long(99))
780
780
781 class TestLong(TraitTestBase):
781 class TestLong(TraitTestBase):
782
782
783 obj = LongTrait()
783 obj = LongTrait()
784
784
785 _default_value = 99 if py3compat.PY3 else long(99)
785 _default_value = 99 if py3compat.PY3 else long(99)
786 _good_values = [10, -10]
786 _good_values = [10, -10]
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
787 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,),
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
788 None, 1j, 10.1, -10.1, '10', '-10', '10L', '-10L', '10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
789 '-10.1', u'10', u'-10', u'10L', u'-10L', u'10.1',
790 u'-10.1']
790 u'-10.1']
791 if not py3compat.PY3:
791 if not py3compat.PY3:
792 # maxint undefined on py3, because int == long
792 # maxint undefined on py3, because int == long
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
793 _good_values.extend([long(10), long(-10), 10*sys.maxint, -10*sys.maxint])
794 _bad_values.extend([[long(10)], (long(10),)])
794 _bad_values.extend([[long(10)], (long(10),)])
795
795
796 @skipif(py3compat.PY3, "not relevant on py3")
796 @skipif(py3compat.PY3, "not relevant on py3")
797 def test_cast_small(self):
797 def test_cast_small(self):
798 """Long casts ints to long"""
798 """Long casts ints to long"""
799 self.obj.value = 10
799 self.obj.value = 10
800 self.assertEqual(type(self.obj.value), long)
800 self.assertEqual(type(self.obj.value), long)
801
801
802
802
803 class IntegerTrait(HasTraits):
803 class IntegerTrait(HasTraits):
804 value = Integer(1)
804 value = Integer(1)
805
805
806 class TestInteger(TestLong):
806 class TestInteger(TestLong):
807 obj = IntegerTrait()
807 obj = IntegerTrait()
808 _default_value = 1
808 _default_value = 1
809
809
810 def coerce(self, n):
810 def coerce(self, n):
811 return int(n)
811 return int(n)
812
812
813 @skipif(py3compat.PY3, "not relevant on py3")
813 @skipif(py3compat.PY3, "not relevant on py3")
814 def test_cast_small(self):
814 def test_cast_small(self):
815 """Integer casts small longs to int"""
815 """Integer casts small longs to int"""
816 if py3compat.PY3:
816 if py3compat.PY3:
817 raise SkipTest("not relevant on py3")
817 raise SkipTest("not relevant on py3")
818
818
819 self.obj.value = long(100)
819 self.obj.value = long(100)
820 self.assertEqual(type(self.obj.value), int)
820 self.assertEqual(type(self.obj.value), int)
821
821
822
822
823 class FloatTrait(HasTraits):
823 class FloatTrait(HasTraits):
824
824
825 value = Float(99.0)
825 value = Float(99.0)
826
826
827 class TestFloat(TraitTestBase):
827 class TestFloat(TraitTestBase):
828
828
829 obj = FloatTrait()
829 obj = FloatTrait()
830
830
831 _default_value = 99.0
831 _default_value = 99.0
832 _good_values = [10, -10, 10.1, -10.1]
832 _good_values = [10, -10, 10.1, -10.1]
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
833 _bad_values = ['ten', u'ten', [10], {'ten': 10},(10,), None,
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
834 1j, '10', '-10', '10L', '-10L', '10.1', '-10.1', u'10',
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
835 u'-10', u'10L', u'-10L', u'10.1', u'-10.1']
836 if not py3compat.PY3:
836 if not py3compat.PY3:
837 _bad_values.extend([long(10), long(-10)])
837 _bad_values.extend([long(10), long(-10)])
838
838
839
839
840 class ComplexTrait(HasTraits):
840 class ComplexTrait(HasTraits):
841
841
842 value = Complex(99.0-99.0j)
842 value = Complex(99.0-99.0j)
843
843
844 class TestComplex(TraitTestBase):
844 class TestComplex(TraitTestBase):
845
845
846 obj = ComplexTrait()
846 obj = ComplexTrait()
847
847
848 _default_value = 99.0-99.0j
848 _default_value = 99.0-99.0j
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
849 _good_values = [10, -10, 10.1, -10.1, 10j, 10+10j, 10-10j,
850 10.1j, 10.1+10.1j, 10.1-10.1j]
850 10.1j, 10.1+10.1j, 10.1-10.1j]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
851 _bad_values = [u'10L', u'-10L', 'ten', [10], {'ten': 10},(10,), None]
852 if not py3compat.PY3:
852 if not py3compat.PY3:
853 _bad_values.extend([long(10), long(-10)])
853 _bad_values.extend([long(10), long(-10)])
854
854
855
855
856 class BytesTrait(HasTraits):
856 class BytesTrait(HasTraits):
857
857
858 value = Bytes(b'string')
858 value = Bytes(b'string')
859
859
860 class TestBytes(TraitTestBase):
860 class TestBytes(TraitTestBase):
861
861
862 obj = BytesTrait()
862 obj = BytesTrait()
863
863
864 _default_value = b'string'
864 _default_value = b'string'
865 _good_values = [b'10', b'-10', b'10L',
865 _good_values = [b'10', b'-10', b'10L',
866 b'-10L', b'10.1', b'-10.1', b'string']
866 b'-10L', b'10.1', b'-10.1', b'string']
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
867 _bad_values = [10, -10, 10.1, -10.1, 1j, [10],
868 ['ten'],{'ten': 10},(10,), None, u'string']
868 ['ten'],{'ten': 10},(10,), None, u'string']
869 if not py3compat.PY3:
869 if not py3compat.PY3:
870 _bad_values.extend([long(10), long(-10)])
870 _bad_values.extend([long(10), long(-10)])
871
871
872
872
873 class UnicodeTrait(HasTraits):
873 class UnicodeTrait(HasTraits):
874
874
875 value = Unicode(u'unicode')
875 value = Unicode(u'unicode')
876
876
877 class TestUnicode(TraitTestBase):
877 class TestUnicode(TraitTestBase):
878
878
879 obj = UnicodeTrait()
879 obj = UnicodeTrait()
880
880
881 _default_value = u'unicode'
881 _default_value = u'unicode'
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
882 _good_values = ['10', '-10', '10L', '-10L', '10.1',
883 '-10.1', '', u'', 'string', u'string', u"€"]
883 '-10.1', '', u'', 'string', u'string', u"€"]
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
884 _bad_values = [10, -10, 10.1, -10.1, 1j,
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
885 [10], ['ten'], [u'ten'], {'ten': 10},(10,), None]
886 if not py3compat.PY3:
886 if not py3compat.PY3:
887 _bad_values.extend([long(10), long(-10)])
887 _bad_values.extend([long(10), long(-10)])
888
888
889
889
890 class ObjectNameTrait(HasTraits):
890 class ObjectNameTrait(HasTraits):
891 value = ObjectName("abc")
891 value = ObjectName("abc")
892
892
893 class TestObjectName(TraitTestBase):
893 class TestObjectName(TraitTestBase):
894 obj = ObjectNameTrait()
894 obj = ObjectNameTrait()
895
895
896 _default_value = "abc"
896 _default_value = "abc"
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
897 _good_values = ["a", "gh", "g9", "g_", "_G", u"a345_"]
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
898 _bad_values = [1, "", u"€", "9g", "!", "#abc", "aj@", "a.b", "a()", "a[0]",
899 None, object(), object]
899 None, object(), object]
900 if sys.version_info[0] < 3:
900 if sys.version_info[0] < 3:
901 _bad_values.append(u"ΓΎ")
901 _bad_values.append(u"ΓΎ")
902 else:
902 else:
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
903 _good_values.append(u"ΓΎ") # ΓΎ=1 is valid in Python 3 (PEP 3131).
904
904
905
905
906 class DottedObjectNameTrait(HasTraits):
906 class DottedObjectNameTrait(HasTraits):
907 value = DottedObjectName("a.b")
907 value = DottedObjectName("a.b")
908
908
909 class TestDottedObjectName(TraitTestBase):
909 class TestDottedObjectName(TraitTestBase):
910 obj = DottedObjectNameTrait()
910 obj = DottedObjectNameTrait()
911
911
912 _default_value = "a.b"
912 _default_value = "a.b"
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
913 _good_values = ["A", "y.t", "y765.__repr__", "os.path.join", u"os.path.join"]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
914 _bad_values = [1, u"abc.€", "_.@", ".", ".abc", "abc.", ".abc.", None]
915 if sys.version_info[0] < 3:
915 if sys.version_info[0] < 3:
916 _bad_values.append(u"t.ΓΎ")
916 _bad_values.append(u"t.ΓΎ")
917 else:
917 else:
918 _good_values.append(u"t.ΓΎ")
918 _good_values.append(u"t.ΓΎ")
919
919
920
920
921 class TCPAddressTrait(HasTraits):
921 class TCPAddressTrait(HasTraits):
922 value = TCPAddress()
922 value = TCPAddress()
923
923
924 class TestTCPAddress(TraitTestBase):
924 class TestTCPAddress(TraitTestBase):
925
925
926 obj = TCPAddressTrait()
926 obj = TCPAddressTrait()
927
927
928 _default_value = ('127.0.0.1',0)
928 _default_value = ('127.0.0.1',0)
929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
929 _good_values = [('localhost',0),('192.168.0.1',1000),('www.google.com',80)]
930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
930 _bad_values = [(0,0),('localhost',10.0),('localhost',-1), None]
931
931
932 class ListTrait(HasTraits):
932 class ListTrait(HasTraits):
933
933
934 value = List(Int)
934 value = List(Int)
935
935
936 class TestList(TraitTestBase):
936 class TestList(TraitTestBase):
937
937
938 obj = ListTrait()
938 obj = ListTrait()
939
939
940 _default_value = []
940 _default_value = []
941 _good_values = [[], [1], list(range(10)), (1,2)]
941 _good_values = [[], [1], list(range(10)), (1,2)]
942 _bad_values = [10, [1,'a'], 'a']
942 _bad_values = [10, [1,'a'], 'a']
943
943
944 def coerce(self, value):
944 def coerce(self, value):
945 if value is not None:
945 if value is not None:
946 value = list(value)
946 value = list(value)
947 return value
947 return value
948
948
949 class Foo(object):
949 class Foo(object):
950 pass
950 pass
951
951
952 class NoneInstanceListTrait(HasTraits):
952 class NoneInstanceListTrait(HasTraits):
953
953
954 value = List(Instance(Foo, allow_none=False))
954 value = List(Instance(Foo, allow_none=False))
955
955
956 class TestNoneInstanceList(TraitTestBase):
956 class TestNoneInstanceList(TraitTestBase):
957
957
958 obj = NoneInstanceListTrait()
958 obj = NoneInstanceListTrait()
959
959
960 _default_value = []
960 _default_value = []
961 _good_values = [[Foo(), Foo()], []]
961 _good_values = [[Foo(), Foo()], []]
962 _bad_values = [[None], [Foo(), None]]
962 _bad_values = [[None], [Foo(), None]]
963
963
964
964
965 class InstanceListTrait(HasTraits):
965 class InstanceListTrait(HasTraits):
966
966
967 value = List(Instance(__name__+'.Foo'))
967 value = List(Instance(__name__+'.Foo'))
968
968
969 class TestInstanceList(TraitTestBase):
969 class TestInstanceList(TraitTestBase):
970
970
971 obj = InstanceListTrait()
971 obj = InstanceListTrait()
972
972
973 def test_klass(self):
973 def test_klass(self):
974 """Test that the instance klass is properly assigned."""
974 """Test that the instance klass is properly assigned."""
975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
975 self.assertIs(self.obj.traits()['value']._trait.klass, Foo)
976
976
977 _default_value = []
977 _default_value = []
978 _good_values = [[Foo(), Foo(), None], []]
978 _good_values = [[Foo(), Foo(), None], []]
979 _bad_values = [['1', 2,], '1', [Foo], None]
979 _bad_values = [['1', 2,], '1', [Foo], None]
980
980
981 class UnionListTrait(HasTraits):
981 class UnionListTrait(HasTraits):
982
982
983 value = List(Int() | Bool())
983 value = List(Int() | Bool())
984
984
985 class TestUnionListTrait(HasTraits):
985 class TestUnionListTrait(HasTraits):
986
986
987 obj = UnionListTrait()
987 obj = UnionListTrait()
988
988
989 _default_value = []
989 _default_value = []
990 _good_values = [[True, 1], [False, True]]
990 _good_values = [[True, 1], [False, True]]
991 _bad_values = [[1, 'True'], False]
991 _bad_values = [[1, 'True'], False]
992
992
993
993
994 class LenListTrait(HasTraits):
994 class LenListTrait(HasTraits):
995
995
996 value = List(Int, [0], minlen=1, maxlen=2)
996 value = List(Int, [0], minlen=1, maxlen=2)
997
997
998 class TestLenList(TraitTestBase):
998 class TestLenList(TraitTestBase):
999
999
1000 obj = LenListTrait()
1000 obj = LenListTrait()
1001
1001
1002 _default_value = [0]
1002 _default_value = [0]
1003 _good_values = [[1], [1,2], (1,2)]
1003 _good_values = [[1], [1,2], (1,2)]
1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1004 _bad_values = [10, [1,'a'], 'a', [], list(range(3))]
1005
1005
1006 def coerce(self, value):
1006 def coerce(self, value):
1007 if value is not None:
1007 if value is not None:
1008 value = list(value)
1008 value = list(value)
1009 return value
1009 return value
1010
1010
1011 class TupleTrait(HasTraits):
1011 class TupleTrait(HasTraits):
1012
1012
1013 value = Tuple(Int(allow_none=True))
1013 value = Tuple(Int(allow_none=True))
1014
1014
1015 class TestTupleTrait(TraitTestBase):
1015 class TestTupleTrait(TraitTestBase):
1016
1016
1017 obj = TupleTrait()
1017 obj = TupleTrait()
1018
1018
1019 _default_value = None
1019 _default_value = None
1020 _good_values = [(1,), None, (0,), [1], (None,)]
1020 _good_values = [(1,), None, (0,), [1], (None,)]
1021 _bad_values = [10, (1,2), ('a'), ()]
1021 _bad_values = [10, (1,2), ('a'), ()]
1022
1022
1023 def coerce(self, value):
1023 def coerce(self, value):
1024 if value is not None:
1024 if value is not None:
1025 value = tuple(value)
1025 value = tuple(value)
1026 return value
1026 return value
1027
1027
1028 def test_invalid_args(self):
1028 def test_invalid_args(self):
1029 self.assertRaises(TypeError, Tuple, 5)
1029 self.assertRaises(TypeError, Tuple, 5)
1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1030 self.assertRaises(TypeError, Tuple, default_value='hello')
1031 t = Tuple(Int, CBytes, default_value=(1,5))
1031 t = Tuple(Int, CBytes, default_value=(1,5))
1032
1032
1033 class LooseTupleTrait(HasTraits):
1033 class LooseTupleTrait(HasTraits):
1034
1034
1035 value = Tuple((1,2,3))
1035 value = Tuple((1,2,3))
1036
1036
1037 class TestLooseTupleTrait(TraitTestBase):
1037 class TestLooseTupleTrait(TraitTestBase):
1038
1038
1039 obj = LooseTupleTrait()
1039 obj = LooseTupleTrait()
1040
1040
1041 _default_value = (1,2,3)
1041 _default_value = (1,2,3)
1042 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1042 _good_values = [(1,), None, [1], (0,), tuple(range(5)), tuple('hello'), ('a',5), ()]
1043 _bad_values = [10, 'hello', {}]
1043 _bad_values = [10, 'hello', {}]
1044
1044
1045 def coerce(self, value):
1045 def coerce(self, value):
1046 if value is not None:
1046 if value is not None:
1047 value = tuple(value)
1047 value = tuple(value)
1048 return value
1048 return value
1049
1049
1050 def test_invalid_args(self):
1050 def test_invalid_args(self):
1051 self.assertRaises(TypeError, Tuple, 5)
1051 self.assertRaises(TypeError, Tuple, 5)
1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1052 self.assertRaises(TypeError, Tuple, default_value='hello')
1053 t = Tuple(Int, CBytes, default_value=(1,5))
1053 t = Tuple(Int, CBytes, default_value=(1,5))
1054
1054
1055
1055
1056 class MultiTupleTrait(HasTraits):
1056 class MultiTupleTrait(HasTraits):
1057
1057
1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1058 value = Tuple(Int, Bytes, default_value=[99,b'bottles'])
1059
1059
1060 class TestMultiTuple(TraitTestBase):
1060 class TestMultiTuple(TraitTestBase):
1061
1061
1062 obj = MultiTupleTrait()
1062 obj = MultiTupleTrait()
1063
1063
1064 _default_value = (99,b'bottles')
1064 _default_value = (99,b'bottles')
1065 _good_values = [(1,b'a'), (2,b'b')]
1065 _good_values = [(1,b'a'), (2,b'b')]
1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1066 _bad_values = ((),10, b'a', (1,b'a',3), (b'a',1), (1, u'a'))
1067
1067
1068 class CRegExpTrait(HasTraits):
1068 class CRegExpTrait(HasTraits):
1069
1069
1070 value = CRegExp(r'')
1070 value = CRegExp(r'')
1071
1071
1072 class TestCRegExp(TraitTestBase):
1072 class TestCRegExp(TraitTestBase):
1073
1073
1074 def coerce(self, value):
1074 def coerce(self, value):
1075 return re.compile(value)
1075 return re.compile(value)
1076
1076
1077 obj = CRegExpTrait()
1077 obj = CRegExpTrait()
1078
1078
1079 _default_value = re.compile(r'')
1079 _default_value = re.compile(r'')
1080 _good_values = [r'\d+', re.compile(r'\d+')]
1080 _good_values = [r'\d+', re.compile(r'\d+')]
1081 _bad_values = ['(', None, ()]
1081 _bad_values = ['(', None, ()]
1082
1082
1083 class DictTrait(HasTraits):
1083 class DictTrait(HasTraits):
1084 value = Dict()
1084 value = Dict()
1085
1085
1086 def test_dict_assignment():
1086 def test_dict_assignment():
1087 d = dict()
1087 d = dict()
1088 c = DictTrait()
1088 c = DictTrait()
1089 c.value = d
1089 c.value = d
1090 d['a'] = 5
1090 d['a'] = 5
1091 nt.assert_equal(d, c.value)
1091 nt.assert_equal(d, c.value)
1092 nt.assert_true(c.value is d)
1092 nt.assert_true(c.value is d)
1093
1093
1094 class ValidatedDictTrait(HasTraits):
1094 class ValidatedDictTrait(HasTraits):
1095
1095
1096 value = Dict(Unicode())
1096 value = Dict(Unicode())
1097
1097
1098 class TestInstanceDict(TraitTestBase):
1098 class TestInstanceDict(TraitTestBase):
1099
1099
1100 obj = ValidatedDictTrait()
1100 obj = ValidatedDictTrait()
1101
1101
1102 _default_value = {}
1102 _default_value = {}
1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1103 _good_values = [{'0': 'foo'}, {'1': 'bar'}]
1104 _bad_values = [{'0': 0}, {'1': 1}]
1104 _bad_values = [{'0': 0}, {'1': 1}]
1105
1105
1106
1106
1107 def test_dict_default_value():
1107 def test_dict_default_value():
1108 """Check that the `{}` default value of the Dict traitlet constructor is
1108 """Check that the `{}` default value of the Dict traitlet constructor is
1109 actually copied."""
1109 actually copied."""
1110
1110
1111 d1, d2 = Dict(), Dict()
1111 d1, d2 = Dict(), Dict()
1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1112 nt.assert_false(d1.get_default_value() is d2.get_default_value())
1113
1113
1114
1114
1115 class TestValidationHook(TestCase):
1115 class TestValidationHook(TestCase):
1116
1116
1117 def test_parity_trait(self):
1117 def test_parity_trait(self):
1118 """Verify that the early validation hook is effective"""
1118 """Verify that the early validation hook is effective"""
1119
1119
1120 class Parity(HasTraits):
1120 class Parity(HasTraits):
1121
1121
1122 value = Int(0)
1122 value = Int(0)
1123 parity = Enum(['odd', 'even'], default_value='even', allow_none=False)
1123 parity = Enum(['odd', 'even'], default_value='even', allow_none=False)
1124
1124
1125 def _value_validate(self, value, trait):
1125 def _value_validate(self, value, trait):
1126 if self.parity == 'even' and value % 2:
1126 if self.parity == 'even' and value % 2:
1127 raise TraitError('Expected an even number')
1127 raise TraitError('Expected an even number')
1128 if self.parity == 'odd' and (value % 2 == 0):
1128 if self.parity == 'odd' and (value % 2 == 0):
1129 raise TraitError('Expected an odd number')
1129 raise TraitError('Expected an odd number')
1130 return value
1130 return value
1131
1131
1132 u = Parity()
1132 u = Parity()
1133 u.parity = 'odd'
1133 u.parity = 'odd'
1134 u.value = 1 # OK
1134 u.value = 1 # OK
1135 with self.assertRaises(TraitError):
1135 with self.assertRaises(TraitError):
1136 u.value = 2 # Trait Error
1136 u.value = 2 # Trait Error
1137
1137
1138 u.parity = 'even'
1138 u.parity = 'even'
1139 u.value = 2 # OK
1139 u.value = 2 # OK
1140
1140
1141
1141
1142 class TestLink(TestCase):
1142 class TestLink(TestCase):
1143
1143
1144 def test_connect_same(self):
1144 def test_connect_same(self):
1145 """Verify two traitlets of the same type can be linked together using link."""
1145 """Verify two traitlets of the same type can be linked together using link."""
1146
1146
1147 # Create two simple classes with Int traitlets.
1147 # Create two simple classes with Int traitlets.
1148 class A(HasTraits):
1148 class A(HasTraits):
1149 value = Int()
1149 value = Int()
1150 a = A(value=9)
1150 a = A(value=9)
1151 b = A(value=8)
1151 b = A(value=8)
1152
1152
1153 # Conenct the two classes.
1153 # Conenct the two classes.
1154 c = link((a, 'value'), (b, 'value'))
1154 c = link((a, 'value'), (b, 'value'))
1155
1155
1156 # Make sure the values are the same at the point of linking.
1156 # Make sure the values are the same at the point of linking.
1157 self.assertEqual(a.value, b.value)
1157 self.assertEqual(a.value, b.value)
1158
1158
1159 # Change one of the values to make sure they stay in sync.
1159 # Change one of the values to make sure they stay in sync.
1160 a.value = 5
1160 a.value = 5
1161 self.assertEqual(a.value, b.value)
1161 self.assertEqual(a.value, b.value)
1162 b.value = 6
1162 b.value = 6
1163 self.assertEqual(a.value, b.value)
1163 self.assertEqual(a.value, b.value)
1164
1164
1165 def test_link_different(self):
1165 def test_link_different(self):
1166 """Verify two traitlets of different types can be linked together using link."""
1166 """Verify two traitlets of different types can be linked together using link."""
1167
1167
1168 # Create two simple classes with Int traitlets.
1168 # Create two simple classes with Int traitlets.
1169 class A(HasTraits):
1169 class A(HasTraits):
1170 value = Int()
1170 value = Int()
1171 class B(HasTraits):
1171 class B(HasTraits):
1172 count = Int()
1172 count = Int()
1173 a = A(value=9)
1173 a = A(value=9)
1174 b = B(count=8)
1174 b = B(count=8)
1175
1175
1176 # Conenct the two classes.
1176 # Conenct the two classes.
1177 c = link((a, 'value'), (b, 'count'))
1177 c = link((a, 'value'), (b, 'count'))
1178
1178
1179 # Make sure the values are the same at the point of linking.
1179 # Make sure the values are the same at the point of linking.
1180 self.assertEqual(a.value, b.count)
1180 self.assertEqual(a.value, b.count)
1181
1181
1182 # Change one of the values to make sure they stay in sync.
1182 # Change one of the values to make sure they stay in sync.
1183 a.value = 5
1183 a.value = 5
1184 self.assertEqual(a.value, b.count)
1184 self.assertEqual(a.value, b.count)
1185 b.count = 4
1185 b.count = 4
1186 self.assertEqual(a.value, b.count)
1186 self.assertEqual(a.value, b.count)
1187
1187
1188 def test_unlink(self):
1188 def test_unlink(self):
1189 """Verify two linked traitlets can be unlinked."""
1189 """Verify two linked traitlets can be unlinked."""
1190
1190
1191 # Create two simple classes with Int traitlets.
1191 # Create two simple classes with Int traitlets.
1192 class A(HasTraits):
1192 class A(HasTraits):
1193 value = Int()
1193 value = Int()
1194 a = A(value=9)
1194 a = A(value=9)
1195 b = A(value=8)
1195 b = A(value=8)
1196
1196
1197 # Connect the two classes.
1197 # Connect the two classes.
1198 c = link((a, 'value'), (b, 'value'))
1198 c = link((a, 'value'), (b, 'value'))
1199 a.value = 4
1199 a.value = 4
1200 c.unlink()
1200 c.unlink()
1201
1201
1202 # Change one of the values to make sure they don't stay in sync.
1202 # Change one of the values to make sure they don't stay in sync.
1203 a.value = 5
1203 a.value = 5
1204 self.assertNotEqual(a.value, b.value)
1204 self.assertNotEqual(a.value, b.value)
1205
1205
1206 def test_callbacks(self):
1206 def test_callbacks(self):
1207 """Verify two linked traitlets have their callbacks called once."""
1207 """Verify two linked traitlets have their callbacks called once."""
1208
1208
1209 # Create two simple classes with Int traitlets.
1209 # Create two simple classes with Int traitlets.
1210 class A(HasTraits):
1210 class A(HasTraits):
1211 value = Int()
1211 value = Int()
1212 class B(HasTraits):
1212 class B(HasTraits):
1213 count = Int()
1213 count = Int()
1214 a = A(value=9)
1214 a = A(value=9)
1215 b = B(count=8)
1215 b = B(count=8)
1216
1216
1217 # Register callbacks that count.
1217 # Register callbacks that count.
1218 callback_count = []
1218 callback_count = []
1219 def a_callback(name, old, new):
1219 def a_callback(name, old, new):
1220 callback_count.append('a')
1220 callback_count.append('a')
1221 a.on_trait_change(a_callback, 'value')
1221 a.on_trait_change(a_callback, 'value')
1222 def b_callback(name, old, new):
1222 def b_callback(name, old, new):
1223 callback_count.append('b')
1223 callback_count.append('b')
1224 b.on_trait_change(b_callback, 'count')
1224 b.on_trait_change(b_callback, 'count')
1225
1225
1226 # Connect the two classes.
1226 # Connect the two classes.
1227 c = link((a, 'value'), (b, 'count'))
1227 c = link((a, 'value'), (b, 'count'))
1228
1228
1229 # Make sure b's count was set to a's value once.
1229 # Make sure b's count was set to a's value once.
1230 self.assertEqual(''.join(callback_count), 'b')
1230 self.assertEqual(''.join(callback_count), 'b')
1231 del callback_count[:]
1231 del callback_count[:]
1232
1232
1233 # Make sure a's value was set to b's count once.
1233 # Make sure a's value was set to b's count once.
1234 b.count = 5
1234 b.count = 5
1235 self.assertEqual(''.join(callback_count), 'ba')
1235 self.assertEqual(''.join(callback_count), 'ba')
1236 del callback_count[:]
1236 del callback_count[:]
1237
1237
1238 # Make sure b's count was set to a's value once.
1238 # Make sure b's count was set to a's value once.
1239 a.value = 4
1239 a.value = 4
1240 self.assertEqual(''.join(callback_count), 'ab')
1240 self.assertEqual(''.join(callback_count), 'ab')
1241 del callback_count[:]
1241 del callback_count[:]
1242
1242
1243 class TestDirectionalLink(TestCase):
1243 class TestDirectionalLink(TestCase):
1244 def test_connect_same(self):
1244 def test_connect_same(self):
1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1245 """Verify two traitlets of the same type can be linked together using directional_link."""
1246
1246
1247 # Create two simple classes with Int traitlets.
1247 # Create two simple classes with Int traitlets.
1248 class A(HasTraits):
1248 class A(HasTraits):
1249 value = Int()
1249 value = Int()
1250 a = A(value=9)
1250 a = A(value=9)
1251 b = A(value=8)
1251 b = A(value=8)
1252
1252
1253 # Conenct the two classes.
1253 # Conenct the two classes.
1254 c = directional_link((a, 'value'), (b, 'value'))
1254 c = directional_link((a, 'value'), (b, 'value'))
1255
1255
1256 # Make sure the values are the same at the point of linking.
1256 # Make sure the values are the same at the point of linking.
1257 self.assertEqual(a.value, b.value)
1257 self.assertEqual(a.value, b.value)
1258
1258
1259 # Change one the value of the source and check that it synchronizes the target.
1259 # Change one the value of the source and check that it synchronizes the target.
1260 a.value = 5
1260 a.value = 5
1261 self.assertEqual(b.value, 5)
1261 self.assertEqual(b.value, 5)
1262 # Change one the value of the target and check that it has no impact on the source
1262 # Change one the value of the target and check that it has no impact on the source
1263 b.value = 6
1263 b.value = 6
1264 self.assertEqual(a.value, 5)
1264 self.assertEqual(a.value, 5)
1265
1265
1266 def test_link_different(self):
1266 def test_link_different(self):
1267 """Verify two traitlets of different types can be linked together using link."""
1267 """Verify two traitlets of different types can be linked together using link."""
1268
1268
1269 # Create two simple classes with Int traitlets.
1269 # Create two simple classes with Int traitlets.
1270 class A(HasTraits):
1270 class A(HasTraits):
1271 value = Int()
1271 value = Int()
1272 class B(HasTraits):
1272 class B(HasTraits):
1273 count = Int()
1273 count = Int()
1274 a = A(value=9)
1274 a = A(value=9)
1275 b = B(count=8)
1275 b = B(count=8)
1276
1276
1277 # Conenct the two classes.
1277 # Conenct the two classes.
1278 c = directional_link((a, 'value'), (b, 'count'))
1278 c = directional_link((a, 'value'), (b, 'count'))
1279
1279
1280 # Make sure the values are the same at the point of linking.
1280 # Make sure the values are the same at the point of linking.
1281 self.assertEqual(a.value, b.count)
1281 self.assertEqual(a.value, b.count)
1282
1282
1283 # Change one the value of the source and check that it synchronizes the target.
1283 # Change one the value of the source and check that it synchronizes the target.
1284 a.value = 5
1284 a.value = 5
1285 self.assertEqual(b.count, 5)
1285 self.assertEqual(b.count, 5)
1286 # Change one the value of the target and check that it has no impact on the source
1286 # Change one the value of the target and check that it has no impact on the source
1287 b.value = 6
1287 b.value = 6
1288 self.assertEqual(a.value, 5)
1288 self.assertEqual(a.value, 5)
1289
1289
1290 def test_unlink(self):
1290 def test_unlink(self):
1291 """Verify two linked traitlets can be unlinked."""
1291 """Verify two linked traitlets can be unlinked."""
1292
1292
1293 # Create two simple classes with Int traitlets.
1293 # Create two simple classes with Int traitlets.
1294 class A(HasTraits):
1294 class A(HasTraits):
1295 value = Int()
1295 value = Int()
1296 a = A(value=9)
1296 a = A(value=9)
1297 b = A(value=8)
1297 b = A(value=8)
1298
1298
1299 # Connect the two classes.
1299 # Connect the two classes.
1300 c = directional_link((a, 'value'), (b, 'value'))
1300 c = directional_link((a, 'value'), (b, 'value'))
1301 a.value = 4
1301 a.value = 4
1302 c.unlink()
1302 c.unlink()
1303
1303
1304 # Change one of the values to make sure they don't stay in sync.
1304 # Change one of the values to make sure they don't stay in sync.
1305 a.value = 5
1305 a.value = 5
1306 self.assertNotEqual(a.value, b.value)
1306 self.assertNotEqual(a.value, b.value)
1307
1307
1308 class Pickleable(HasTraits):
1308 class Pickleable(HasTraits):
1309 i = Int()
1309 i = Int()
1310 j = Int()
1310 j = Int()
1311
1311
1312 def _i_default(self):
1312 def _i_default(self):
1313 return 1
1313 return 1
1314
1314
1315 def _i_changed(self, name, old, new):
1315 def _i_changed(self, name, old, new):
1316 self.j = new
1316 self.j = new
1317
1317
1318 def test_pickle_hastraits():
1318 def test_pickle_hastraits():
1319 c = Pickleable()
1319 c = Pickleable()
1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1320 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1321 p = pickle.dumps(c, protocol)
1321 p = pickle.dumps(c, protocol)
1322 c2 = pickle.loads(p)
1322 c2 = pickle.loads(p)
1323 nt.assert_equal(c2.i, c.i)
1323 nt.assert_equal(c2.i, c.i)
1324 nt.assert_equal(c2.j, c.j)
1324 nt.assert_equal(c2.j, c.j)
1325
1325
1326 c.i = 5
1326 c.i = 5
1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1327 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
1328 p = pickle.dumps(c, protocol)
1328 p = pickle.dumps(c, protocol)
1329 c2 = pickle.loads(p)
1329 c2 = pickle.loads(p)
1330 nt.assert_equal(c2.i, c.i)
1330 nt.assert_equal(c2.i, c.i)
1331 nt.assert_equal(c2.j, c.j)
1331 nt.assert_equal(c2.j, c.j)
1332
1332
1333
1333
1334 def test_hold_trait_notifications():
1334 def test_hold_trait_notifications():
1335 changes = []
1335 changes = []
1336 class Test(HasTraits):
1336 class Test(HasTraits):
1337 a = Integer(0)
1337 a = Integer(0)
1338 def _a_changed(self, name, old, new):
1338 def _a_changed(self, name, old, new):
1339 changes.append((old, new))
1339 changes.append((old, new))
1340
1340
1341 t = Test()
1341 t = Test()
1342 with t.hold_trait_notifications():
1342 with t.hold_trait_notifications():
1343 with t.hold_trait_notifications():
1343 with t.hold_trait_notifications():
1344 t.a = 1
1344 t.a = 1
1345 nt.assert_equal(t.a, 1)
1345 nt.assert_equal(t.a, 1)
1346 nt.assert_equal(changes, [])
1346 nt.assert_equal(changes, [])
1347 t.a = 2
1347 t.a = 2
1348 nt.assert_equal(t.a, 2)
1348 nt.assert_equal(t.a, 2)
1349 with t.hold_trait_notifications():
1349 with t.hold_trait_notifications():
1350 t.a = 3
1350 t.a = 3
1351 nt.assert_equal(t.a, 3)
1351 nt.assert_equal(t.a, 3)
1352 nt.assert_equal(changes, [])
1352 nt.assert_equal(changes, [])
1353 t.a = 4
1353 t.a = 4
1354 nt.assert_equal(t.a, 4)
1354 nt.assert_equal(t.a, 4)
1355 nt.assert_equal(changes, [])
1355 nt.assert_equal(changes, [])
1356 t.a = 4
1356 t.a = 4
1357 nt.assert_equal(t.a, 4)
1357 nt.assert_equal(t.a, 4)
1358 nt.assert_equal(changes, [])
1358 nt.assert_equal(changes, [])
1359 nt.assert_equal(changes, [(0,1), (1,2), (2,3), (3,4)])
1359 nt.assert_equal(changes, [(0,1), (1,2), (2,3), (3,4)])
1360
1360
1361
1361
1362 class OrderTraits(HasTraits):
1362 class OrderTraits(HasTraits):
1363 notified = Dict()
1363 notified = Dict()
1364
1364
1365 a = Unicode()
1365 a = Unicode()
1366 b = Unicode()
1366 b = Unicode()
1367 c = Unicode()
1367 c = Unicode()
1368 d = Unicode()
1368 d = Unicode()
1369 e = Unicode()
1369 e = Unicode()
1370 f = Unicode()
1370 f = Unicode()
1371 g = Unicode()
1371 g = Unicode()
1372 h = Unicode()
1372 h = Unicode()
1373 i = Unicode()
1373 i = Unicode()
1374 j = Unicode()
1374 j = Unicode()
1375 k = Unicode()
1375 k = Unicode()
1376 l = Unicode()
1376 l = Unicode()
1377
1377
1378 def _notify(self, name, old, new):
1378 def _notify(self, name, old, new):
1379 """check the value of all traits when each trait change is triggered
1379 """check the value of all traits when each trait change is triggered
1380
1380
1381 This verifies that the values are not sensitive
1381 This verifies that the values are not sensitive
1382 to dict ordering when loaded from kwargs
1382 to dict ordering when loaded from kwargs
1383 """
1383 """
1384 # check the value of the other traits
1384 # check the value of the other traits
1385 # when a given trait change notification fires
1385 # when a given trait change notification fires
1386 self.notified[name] = {
1386 self.notified[name] = {
1387 c: getattr(self, c) for c in 'abcdefghijkl'
1387 c: getattr(self, c) for c in 'abcdefghijkl'
1388 }
1388 }
1389
1389
1390 def __init__(self, **kwargs):
1390 def __init__(self, **kwargs):
1391 self.on_trait_change(self._notify)
1391 self.on_trait_change(self._notify)
1392 super(OrderTraits, self).__init__(**kwargs)
1392 super(OrderTraits, self).__init__(**kwargs)
1393
1393
1394 def test_notification_order():
1394 def test_notification_order():
1395 d = {c:c for c in 'abcdefghijkl'}
1395 d = {c:c for c in 'abcdefghijkl'}
1396 obj = OrderTraits()
1396 obj = OrderTraits()
1397 nt.assert_equal(obj.notified, {})
1397 nt.assert_equal(obj.notified, {})
1398 obj = OrderTraits(**d)
1398 obj = OrderTraits(**d)
1399 notifications = {
1399 notifications = {
1400 c: d for c in 'abcdefghijkl'
1400 c: d for c in 'abcdefghijkl'
1401 }
1401 }
1402 nt.assert_equal(obj.notified, notifications)
1402 nt.assert_equal(obj.notified, notifications)
1403
1403
1404
1404
1405 class TestEventful(TestCase):
1405 class TestEventful(TestCase):
1406
1406
1407 def test_list(self):
1407 def test_list(self):
1408 """Does the EventfulList work?"""
1408 """Does the EventfulList work?"""
1409 event_cache = []
1409 event_cache = []
1410
1410
1411 class A(HasTraits):
1411 class A(HasTraits):
1412 x = EventfulList([c for c in 'abc'])
1412 x = EventfulList([c for c in 'abc'])
1413 a = A()
1413 a = A()
1414 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1414 a.x.on_events(lambda i, x: event_cache.append('insert'), \
1415 lambda i, x: event_cache.append('set'), \
1415 lambda i, x: event_cache.append('set'), \
1416 lambda i: event_cache.append('del'), \
1416 lambda i: event_cache.append('del'), \
1417 lambda: event_cache.append('reverse'), \
1417 lambda: event_cache.append('reverse'), \
1418 lambda *p, **k: event_cache.append('sort'))
1418 lambda *p, **k: event_cache.append('sort'))
1419
1419
1420 a.x.remove('c')
1420 a.x.remove('c')
1421 # ab
1421 # ab
1422 a.x.insert(0, 'z')
1422 a.x.insert(0, 'z')
1423 # zab
1423 # zab
1424 del a.x[1]
1424 del a.x[1]
1425 # zb
1425 # zb
1426 a.x.reverse()
1426 a.x.reverse()
1427 # bz
1427 # bz
1428 a.x[1] = 'o'
1428 a.x[1] = 'o'
1429 # bo
1429 # bo
1430 a.x.append('a')
1430 a.x.append('a')
1431 # boa
1431 # boa
1432 a.x.sort()
1432 a.x.sort()
1433 # abo
1433 # abo
1434
1434
1435 # Were the correct events captured?
1435 # Were the correct events captured?
1436 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1436 self.assertEqual(event_cache, ['del', 'insert', 'del', 'reverse', 'set', 'set', 'sort'])
1437
1437
1438 # Is the output correct?
1438 # Is the output correct?
1439 self.assertEqual(a.x, [c for c in 'abo'])
1439 self.assertEqual(a.x, [c for c in 'abo'])
1440
1440
1441 def test_dict(self):
1441 def test_dict(self):
1442 """Does the EventfulDict work?"""
1442 """Does the EventfulDict work?"""
1443 event_cache = []
1443 event_cache = []
1444
1444
1445 class A(HasTraits):
1445 class A(HasTraits):
1446 x = EventfulDict({c: c for c in 'abc'})
1446 x = EventfulDict({c: c for c in 'abc'})
1447 a = A()
1447 a = A()
1448 a.x.on_events(lambda k, v: event_cache.append('add'), \
1448 a.x.on_events(lambda k, v: event_cache.append('add'), \
1449 lambda k, v: event_cache.append('set'), \
1449 lambda k, v: event_cache.append('set'), \
1450 lambda k: event_cache.append('del'))
1450 lambda k: event_cache.append('del'))
1451
1451
1452 del a.x['c']
1452 del a.x['c']
1453 # ab
1453 # ab
1454 a.x['z'] = 1
1454 a.x['z'] = 1
1455 # abz
1455 # abz
1456 a.x['z'] = 'z'
1456 a.x['z'] = 'z'
1457 # abz
1457 # abz
1458 a.x.pop('a')
1458 a.x.pop('a')
1459 # bz
1459 # bz
1460
1460
1461 # Were the correct events captured?
1461 # Were the correct events captured?
1462 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1462 self.assertEqual(event_cache, ['del', 'add', 'set', 'del'])
1463
1463
1464 # Is the output correct?
1464 # Is the output correct?
1465 self.assertEqual(a.x, {c: c for c in 'bz'})
1465 self.assertEqual(a.x, {c: c for c in 'bz'})
1466
1466
1467 ###
1467 ###
1468 # Traits for Forward Declaration Tests
1468 # Traits for Forward Declaration Tests
1469 ###
1469 ###
1470 class ForwardDeclaredInstanceTrait(HasTraits):
1470 class ForwardDeclaredInstanceTrait(HasTraits):
1471
1471
1472 value = ForwardDeclaredInstance('ForwardDeclaredBar')
1472 value = ForwardDeclaredInstance('ForwardDeclaredBar')
1473
1473
1474 class ForwardDeclaredTypeTrait(HasTraits):
1474 class ForwardDeclaredTypeTrait(HasTraits):
1475
1475
1476 value = ForwardDeclaredType('ForwardDeclaredBar')
1476 value = ForwardDeclaredType('ForwardDeclaredBar')
1477
1477
1478 class ForwardDeclaredInstanceListTrait(HasTraits):
1478 class ForwardDeclaredInstanceListTrait(HasTraits):
1479
1479
1480 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1480 value = List(ForwardDeclaredInstance('ForwardDeclaredBar'))
1481
1481
1482 class ForwardDeclaredTypeListTrait(HasTraits):
1482 class ForwardDeclaredTypeListTrait(HasTraits):
1483
1483
1484 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1484 value = List(ForwardDeclaredType('ForwardDeclaredBar'))
1485 ###
1485 ###
1486 # End Traits for Forward Declaration Tests
1486 # End Traits for Forward Declaration Tests
1487 ###
1487 ###
1488
1488
1489 ###
1489 ###
1490 # Classes for Forward Declaration Tests
1490 # Classes for Forward Declaration Tests
1491 ###
1491 ###
1492 class ForwardDeclaredBar(object):
1492 class ForwardDeclaredBar(object):
1493 pass
1493 pass
1494
1494
1495 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1495 class ForwardDeclaredBarSub(ForwardDeclaredBar):
1496 pass
1496 pass
1497 ###
1497 ###
1498 # End Classes for Forward Declaration Tests
1498 # End Classes for Forward Declaration Tests
1499 ###
1499 ###
1500
1500
1501 ###
1501 ###
1502 # Forward Declaration Tests
1502 # Forward Declaration Tests
1503 ###
1503 ###
1504 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1504 class TestForwardDeclaredInstanceTrait(TraitTestBase):
1505
1505
1506 obj = ForwardDeclaredInstanceTrait()
1506 obj = ForwardDeclaredInstanceTrait()
1507 _default_value = None
1507 _default_value = None
1508 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1508 _good_values = [None, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1509 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1509 _bad_values = ['foo', 3, ForwardDeclaredBar, ForwardDeclaredBarSub]
1510
1510
1511 class TestForwardDeclaredTypeTrait(TraitTestBase):
1511 class TestForwardDeclaredTypeTrait(TraitTestBase):
1512
1512
1513 obj = ForwardDeclaredTypeTrait()
1513 obj = ForwardDeclaredTypeTrait()
1514 _default_value = None
1514 _default_value = None
1515 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1515 _good_values = [None, ForwardDeclaredBar, ForwardDeclaredBarSub]
1516 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1516 _bad_values = ['foo', 3, ForwardDeclaredBar(), ForwardDeclaredBarSub()]
1517
1517
1518 class TestForwardDeclaredInstanceList(TraitTestBase):
1518 class TestForwardDeclaredInstanceList(TraitTestBase):
1519
1519
1520 obj = ForwardDeclaredInstanceListTrait()
1520 obj = ForwardDeclaredInstanceListTrait()
1521
1521
1522 def test_klass(self):
1522 def test_klass(self):
1523 """Test that the instance klass is properly assigned."""
1523 """Test that the instance klass is properly assigned."""
1524 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1524 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1525
1525
1526 _default_value = []
1526 _default_value = []
1527 _good_values = [
1527 _good_values = [
1528 [ForwardDeclaredBar(), ForwardDeclaredBarSub(), None],
1528 [ForwardDeclaredBar(), ForwardDeclaredBarSub(), None],
1529 [None],
1529 [None],
1530 [],
1530 [],
1531 ]
1531 ]
1532 _bad_values = [
1532 _bad_values = [
1533 ForwardDeclaredBar(),
1533 ForwardDeclaredBar(),
1534 [ForwardDeclaredBar(), 3],
1534 [ForwardDeclaredBar(), 3],
1535 '1',
1535 '1',
1536 # Note that this is the type, not an instance.
1536 # Note that this is the type, not an instance.
1537 [ForwardDeclaredBar],
1537 [ForwardDeclaredBar],
1538 None,
1538 None,
1539 ]
1539 ]
1540
1540
1541 class TestForwardDeclaredTypeList(TraitTestBase):
1541 class TestForwardDeclaredTypeList(TraitTestBase):
1542
1542
1543 obj = ForwardDeclaredTypeListTrait()
1543 obj = ForwardDeclaredTypeListTrait()
1544
1544
1545 def test_klass(self):
1545 def test_klass(self):
1546 """Test that the instance klass is properly assigned."""
1546 """Test that the instance klass is properly assigned."""
1547 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1547 self.assertIs(self.obj.traits()['value']._trait.klass, ForwardDeclaredBar)
1548
1548
1549 _default_value = []
1549 _default_value = []
1550 _good_values = [
1550 _good_values = [
1551 [ForwardDeclaredBar, ForwardDeclaredBarSub, None],
1551 [ForwardDeclaredBar, ForwardDeclaredBarSub, None],
1552 [],
1552 [],
1553 [None],
1553 [None],
1554 ]
1554 ]
1555 _bad_values = [
1555 _bad_values = [
1556 ForwardDeclaredBar,
1556 ForwardDeclaredBar,
1557 [ForwardDeclaredBar, 3],
1557 [ForwardDeclaredBar, 3],
1558 '1',
1558 '1',
1559 # Note that this is an instance, not the type.
1559 # Note that this is an instance, not the type.
1560 [ForwardDeclaredBar()],
1560 [ForwardDeclaredBar()],
1561 None,
1561 None,
1562 ]
1562 ]
1563 ###
1563 ###
1564 # End Forward Declaration Tests
1564 # End Forward Declaration Tests
1565 ###
1565 ###
1566
1567 class TestDynamicTraits(TestCase):
1568
1569 def setUp(self):
1570 self._notify1 = []
1571
1572 def notify1(self, name, old, new):
1573 self._notify1.append((name, old, new))
1574
1575 def test_notify_all(self):
1576
1577 class A(HasTraits):
1578 pass
1579
1580 a = A()
1581 self.assertTrue(not hasattr(a, 'x'))
1582 self.assertTrue(not hasattr(a, 'y'))
1583
1584 # Dynamically add trait x.
1585 a.add_trait('x', Int())
1586 self.assertTrue(hasattr(a, 'x'))
1587 self.assertTrue(isinstance(a, (A, )))
1588
1589 # Dynamically add trait y.
1590 a.add_trait('y', Float())
1591 self.assertTrue(hasattr(a, 'y'))
1592 self.assertTrue(isinstance(a, (A, )))
1593 self.assertEqual(a.__class__.__name__, A.__name__)
1594
1595 # Create a new instance and verify that x and y
1596 # aren't defined.
1597 b = A()
1598 self.assertTrue(not hasattr(b, 'x'))
1599 self.assertTrue(not hasattr(b, 'y'))
1600
1601 # Verify that notification works like normal.
1602 a.on_trait_change(self.notify1)
1603 a.x = 0
1604 self.assertEqual(len(self._notify1), 0)
1605 a.y = 0.0
1606 self.assertEqual(len(self._notify1), 0)
1607 a.x = 10
1608 self.assertTrue(('x', 0, 10) in self._notify1)
1609 a.y = 10.0
1610 self.assertTrue(('y', 0.0, 10.0) in self._notify1)
1611 self.assertRaises(TraitError, setattr, a, 'x', 'bad string')
1612 self.assertRaises(TraitError, setattr, a, 'y', 'bad string')
1613 self._notify1 = []
1614 a.on_trait_change(self.notify1, remove=True)
1615 a.x = 20
1616 a.y = 20.0
1617 self.assertEqual(len(self._notify1), 0)
@@ -1,1834 +1,1840 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A lightweight Traits like module.
3 A lightweight Traits like module.
4
4
5 This is designed to provide a lightweight, simple, pure Python version of
5 This is designed to provide a lightweight, simple, pure Python version of
6 many of the capabilities of enthought.traits. This includes:
6 many of the capabilities of enthought.traits. This includes:
7
7
8 * Validation
8 * Validation
9 * Type specification with defaults
9 * Type specification with defaults
10 * Static and dynamic notification
10 * Static and dynamic notification
11 * Basic predefined types
11 * Basic predefined types
12 * An API that is similar to enthought.traits
12 * An API that is similar to enthought.traits
13
13
14 We don't support:
14 We don't support:
15
15
16 * Delegation
16 * Delegation
17 * Automatic GUI generation
17 * Automatic GUI generation
18 * A full set of trait types. Most importantly, we don't provide container
18 * A full set of trait types. Most importantly, we don't provide container
19 traits (list, dict, tuple) that can trigger notifications if their
19 traits (list, dict, tuple) that can trigger notifications if their
20 contents change.
20 contents change.
21 * API compatibility with enthought.traits
21 * API compatibility with enthought.traits
22
22
23 There are also some important difference in our design:
23 There are also some important difference in our design:
24
24
25 * enthought.traits does not validate default values. We do.
25 * enthought.traits does not validate default values. We do.
26
26
27 We choose to create this module because we need these capabilities, but
27 We choose to create this module because we need these capabilities, but
28 we need them to be pure Python so they work in all Python implementations,
28 we need them to be pure Python so they work in all Python implementations,
29 including Jython and IronPython.
29 including Jython and IronPython.
30
30
31 Inheritance diagram:
31 Inheritance diagram:
32
32
33 .. inheritance-diagram:: IPython.utils.traitlets
33 .. inheritance-diagram:: IPython.utils.traitlets
34 :parts: 3
34 :parts: 3
35 """
35 """
36
36
37 # Copyright (c) IPython Development Team.
37 # Copyright (c) IPython Development Team.
38 # Distributed under the terms of the Modified BSD License.
38 # Distributed under the terms of the Modified BSD License.
39 #
39 #
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
40 # Adapted from enthought.traits, Copyright (c) Enthought, Inc.,
41 # also under the terms of the Modified BSD License.
41 # also under the terms of the Modified BSD License.
42
42
43 import contextlib
43 import contextlib
44 import inspect
44 import inspect
45 import re
45 import re
46 import sys
46 import sys
47 import types
47 import types
48 from types import FunctionType
48 from types import FunctionType
49 try:
49 try:
50 from types import ClassType, InstanceType
50 from types import ClassType, InstanceType
51 ClassTypes = (ClassType, type)
51 ClassTypes = (ClassType, type)
52 except:
52 except:
53 ClassTypes = (type,)
53 ClassTypes = (type,)
54 from warnings import warn
54 from warnings import warn
55
55
56 from .getargspec import getargspec
56 from .getargspec import getargspec
57 from .importstring import import_item
57 from .importstring import import_item
58 from IPython.utils import py3compat
58 from IPython.utils import py3compat
59 from IPython.utils import eventful
59 from IPython.utils import eventful
60 from IPython.utils.py3compat import iteritems, string_types
60 from IPython.utils.py3compat import iteritems, string_types
61 from IPython.testing.skipdoctest import skip_doctest
61 from IPython.testing.skipdoctest import skip_doctest
62
62
63 SequenceTypes = (list, tuple, set, frozenset)
63 SequenceTypes = (list, tuple, set, frozenset)
64
64
65 #-----------------------------------------------------------------------------
65 #-----------------------------------------------------------------------------
66 # Basic classes
66 # Basic classes
67 #-----------------------------------------------------------------------------
67 #-----------------------------------------------------------------------------
68
68
69
69
70 class NoDefaultSpecified ( object ): pass
70 class NoDefaultSpecified ( object ): pass
71 NoDefaultSpecified = NoDefaultSpecified()
71 NoDefaultSpecified = NoDefaultSpecified()
72
72
73
73
74 class Undefined ( object ): pass
74 class Undefined ( object ): pass
75 Undefined = Undefined()
75 Undefined = Undefined()
76
76
77 class TraitError(Exception):
77 class TraitError(Exception):
78 pass
78 pass
79
79
80 #-----------------------------------------------------------------------------
80 #-----------------------------------------------------------------------------
81 # Utilities
81 # Utilities
82 #-----------------------------------------------------------------------------
82 #-----------------------------------------------------------------------------
83
83
84
84
85 def class_of ( object ):
85 def class_of ( object ):
86 """ Returns a string containing the class name of an object with the
86 """ Returns a string containing the class name of an object with the
87 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
87 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
88 'a PlotValue').
88 'a PlotValue').
89 """
89 """
90 if isinstance( object, py3compat.string_types ):
90 if isinstance( object, py3compat.string_types ):
91 return add_article( object )
91 return add_article( object )
92
92
93 return add_article( object.__class__.__name__ )
93 return add_article( object.__class__.__name__ )
94
94
95
95
96 def add_article ( name ):
96 def add_article ( name ):
97 """ Returns a string containing the correct indefinite article ('a' or 'an')
97 """ Returns a string containing the correct indefinite article ('a' or 'an')
98 prefixed to the specified string.
98 prefixed to the specified string.
99 """
99 """
100 if name[:1].lower() in 'aeiou':
100 if name[:1].lower() in 'aeiou':
101 return 'an ' + name
101 return 'an ' + name
102
102
103 return 'a ' + name
103 return 'a ' + name
104
104
105
105
106 def repr_type(obj):
106 def repr_type(obj):
107 """ Return a string representation of a value and its type for readable
107 """ Return a string representation of a value and its type for readable
108 error messages.
108 error messages.
109 """
109 """
110 the_type = type(obj)
110 the_type = type(obj)
111 if (not py3compat.PY3) and the_type is InstanceType:
111 if (not py3compat.PY3) and the_type is InstanceType:
112 # Old-style class.
112 # Old-style class.
113 the_type = obj.__class__
113 the_type = obj.__class__
114 msg = '%r %r' % (obj, the_type)
114 msg = '%r %r' % (obj, the_type)
115 return msg
115 return msg
116
116
117
117
118 def is_trait(t):
118 def is_trait(t):
119 """ Returns whether the given value is an instance or subclass of TraitType.
119 """ Returns whether the given value is an instance or subclass of TraitType.
120 """
120 """
121 return (isinstance(t, TraitType) or
121 return (isinstance(t, TraitType) or
122 (isinstance(t, type) and issubclass(t, TraitType)))
122 (isinstance(t, type) and issubclass(t, TraitType)))
123
123
124
124
125 def parse_notifier_name(name):
125 def parse_notifier_name(name):
126 """Convert the name argument to a list of names.
126 """Convert the name argument to a list of names.
127
127
128 Examples
128 Examples
129 --------
129 --------
130
130
131 >>> parse_notifier_name('a')
131 >>> parse_notifier_name('a')
132 ['a']
132 ['a']
133 >>> parse_notifier_name(['a','b'])
133 >>> parse_notifier_name(['a','b'])
134 ['a', 'b']
134 ['a', 'b']
135 >>> parse_notifier_name(None)
135 >>> parse_notifier_name(None)
136 ['anytrait']
136 ['anytrait']
137 """
137 """
138 if isinstance(name, string_types):
138 if isinstance(name, string_types):
139 return [name]
139 return [name]
140 elif name is None:
140 elif name is None:
141 return ['anytrait']
141 return ['anytrait']
142 elif isinstance(name, (list, tuple)):
142 elif isinstance(name, (list, tuple)):
143 for n in name:
143 for n in name:
144 assert isinstance(n, string_types), "names must be strings"
144 assert isinstance(n, string_types), "names must be strings"
145 return name
145 return name
146
146
147
147
148 class _SimpleTest:
148 class _SimpleTest:
149 def __init__ ( self, value ): self.value = value
149 def __init__ ( self, value ): self.value = value
150 def __call__ ( self, test ):
150 def __call__ ( self, test ):
151 return test == self.value
151 return test == self.value
152 def __repr__(self):
152 def __repr__(self):
153 return "<SimpleTest(%r)" % self.value
153 return "<SimpleTest(%r)" % self.value
154 def __str__(self):
154 def __str__(self):
155 return self.__repr__()
155 return self.__repr__()
156
156
157
157
158 def getmembers(object, predicate=None):
158 def getmembers(object, predicate=None):
159 """A safe version of inspect.getmembers that handles missing attributes.
159 """A safe version of inspect.getmembers that handles missing attributes.
160
160
161 This is useful when there are descriptor based attributes that for
161 This is useful when there are descriptor based attributes that for
162 some reason raise AttributeError even though they exist. This happens
162 some reason raise AttributeError even though they exist. This happens
163 in zope.inteface with the __provides__ attribute.
163 in zope.inteface with the __provides__ attribute.
164 """
164 """
165 results = []
165 results = []
166 for key in dir(object):
166 for key in dir(object):
167 try:
167 try:
168 value = getattr(object, key)
168 value = getattr(object, key)
169 except AttributeError:
169 except AttributeError:
170 pass
170 pass
171 else:
171 else:
172 if not predicate or predicate(value):
172 if not predicate or predicate(value):
173 results.append((key, value))
173 results.append((key, value))
174 results.sort()
174 results.sort()
175 return results
175 return results
176
176
177 def _validate_link(*tuples):
177 def _validate_link(*tuples):
178 """Validate arguments for traitlet link functions"""
178 """Validate arguments for traitlet link functions"""
179 for t in tuples:
179 for t in tuples:
180 if not len(t) == 2:
180 if not len(t) == 2:
181 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
181 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
182 obj, trait_name = t
182 obj, trait_name = t
183 if not isinstance(obj, HasTraits):
183 if not isinstance(obj, HasTraits):
184 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
184 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
185 if not trait_name in obj.traits():
185 if not trait_name in obj.traits():
186 raise TypeError("%r has no trait %r" % (obj, trait_name))
186 raise TypeError("%r has no trait %r" % (obj, trait_name))
187
187
188 @skip_doctest
188 @skip_doctest
189 class link(object):
189 class link(object):
190 """Link traits from different objects together so they remain in sync.
190 """Link traits from different objects together so they remain in sync.
191
191
192 Parameters
192 Parameters
193 ----------
193 ----------
194 *args : pairs of objects/attributes
194 *args : pairs of objects/attributes
195
195
196 Examples
196 Examples
197 --------
197 --------
198
198
199 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
199 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
200 >>> obj1.value = 5 # updates other objects as well
200 >>> obj1.value = 5 # updates other objects as well
201 """
201 """
202 updating = False
202 updating = False
203 def __init__(self, *args):
203 def __init__(self, *args):
204 if len(args) < 2:
204 if len(args) < 2:
205 raise TypeError('At least two traitlets must be provided.')
205 raise TypeError('At least two traitlets must be provided.')
206 _validate_link(*args)
206 _validate_link(*args)
207
207
208 self.objects = {}
208 self.objects = {}
209
209
210 initial = getattr(args[0][0], args[0][1])
210 initial = getattr(args[0][0], args[0][1])
211 for obj, attr in args:
211 for obj, attr in args:
212 setattr(obj, attr, initial)
212 setattr(obj, attr, initial)
213
213
214 callback = self._make_closure(obj, attr)
214 callback = self._make_closure(obj, attr)
215 obj.on_trait_change(callback, attr)
215 obj.on_trait_change(callback, attr)
216 self.objects[(obj, attr)] = callback
216 self.objects[(obj, attr)] = callback
217
217
218 @contextlib.contextmanager
218 @contextlib.contextmanager
219 def _busy_updating(self):
219 def _busy_updating(self):
220 self.updating = True
220 self.updating = True
221 try:
221 try:
222 yield
222 yield
223 finally:
223 finally:
224 self.updating = False
224 self.updating = False
225
225
226 def _make_closure(self, sending_obj, sending_attr):
226 def _make_closure(self, sending_obj, sending_attr):
227 def update(name, old, new):
227 def update(name, old, new):
228 self._update(sending_obj, sending_attr, new)
228 self._update(sending_obj, sending_attr, new)
229 return update
229 return update
230
230
231 def _update(self, sending_obj, sending_attr, new):
231 def _update(self, sending_obj, sending_attr, new):
232 if self.updating:
232 if self.updating:
233 return
233 return
234 with self._busy_updating():
234 with self._busy_updating():
235 for obj, attr in self.objects.keys():
235 for obj, attr in self.objects.keys():
236 setattr(obj, attr, new)
236 setattr(obj, attr, new)
237
237
238 def unlink(self):
238 def unlink(self):
239 for key, callback in self.objects.items():
239 for key, callback in self.objects.items():
240 (obj, attr) = key
240 (obj, attr) = key
241 obj.on_trait_change(callback, attr, remove=True)
241 obj.on_trait_change(callback, attr, remove=True)
242
242
243 @skip_doctest
243 @skip_doctest
244 class directional_link(object):
244 class directional_link(object):
245 """Link the trait of a source object with traits of target objects.
245 """Link the trait of a source object with traits of target objects.
246
246
247 Parameters
247 Parameters
248 ----------
248 ----------
249 source : pair of object, name
249 source : pair of object, name
250 targets : pairs of objects/attributes
250 targets : pairs of objects/attributes
251
251
252 Examples
252 Examples
253 --------
253 --------
254
254
255 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
255 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
256 >>> src.value = 5 # updates target objects
256 >>> src.value = 5 # updates target objects
257 >>> tgt1.value = 6 # does not update other objects
257 >>> tgt1.value = 6 # does not update other objects
258 """
258 """
259 updating = False
259 updating = False
260
260
261 def __init__(self, source, *targets):
261 def __init__(self, source, *targets):
262 if len(targets) < 1:
262 if len(targets) < 1:
263 raise TypeError('At least two traitlets must be provided.')
263 raise TypeError('At least two traitlets must be provided.')
264 _validate_link(source, *targets)
264 _validate_link(source, *targets)
265 self.source = source
265 self.source = source
266 self.targets = targets
266 self.targets = targets
267
267
268 # Update current value
268 # Update current value
269 src_attr_value = getattr(source[0], source[1])
269 src_attr_value = getattr(source[0], source[1])
270 for obj, attr in targets:
270 for obj, attr in targets:
271 setattr(obj, attr, src_attr_value)
271 setattr(obj, attr, src_attr_value)
272
272
273 # Wire
273 # Wire
274 self.source[0].on_trait_change(self._update, self.source[1])
274 self.source[0].on_trait_change(self._update, self.source[1])
275
275
276 @contextlib.contextmanager
276 @contextlib.contextmanager
277 def _busy_updating(self):
277 def _busy_updating(self):
278 self.updating = True
278 self.updating = True
279 try:
279 try:
280 yield
280 yield
281 finally:
281 finally:
282 self.updating = False
282 self.updating = False
283
283
284 def _update(self, name, old, new):
284 def _update(self, name, old, new):
285 if self.updating:
285 if self.updating:
286 return
286 return
287 with self._busy_updating():
287 with self._busy_updating():
288 for obj, attr in self.targets:
288 for obj, attr in self.targets:
289 setattr(obj, attr, new)
289 setattr(obj, attr, new)
290
290
291 def unlink(self):
291 def unlink(self):
292 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
292 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
293 self.source = None
293 self.source = None
294 self.targets = []
294 self.targets = []
295
295
296 dlink = directional_link
296 dlink = directional_link
297
297
298
298
299 #-----------------------------------------------------------------------------
299 #-----------------------------------------------------------------------------
300 # Base TraitType for all traits
300 # Base TraitType for all traits
301 #-----------------------------------------------------------------------------
301 #-----------------------------------------------------------------------------
302
302
303
303
304 class TraitType(object):
304 class TraitType(object):
305 """A base class for all trait descriptors.
305 """A base class for all trait descriptors.
306
306
307 Notes
307 Notes
308 -----
308 -----
309 Our implementation of traits is based on Python's descriptor
309 Our implementation of traits is based on Python's descriptor
310 prototol. This class is the base class for all such descriptors. The
310 prototol. This class is the base class for all such descriptors. The
311 only magic we use is a custom metaclass for the main :class:`HasTraits`
311 only magic we use is a custom metaclass for the main :class:`HasTraits`
312 class that does the following:
312 class that does the following:
313
313
314 1. Sets the :attr:`name` attribute of every :class:`TraitType`
314 1. Sets the :attr:`name` attribute of every :class:`TraitType`
315 instance in the class dict to the name of the attribute.
315 instance in the class dict to the name of the attribute.
316 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
316 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
317 instance in the class dict to the *class* that declared the trait.
317 instance in the class dict to the *class* that declared the trait.
318 This is used by the :class:`This` trait to allow subclasses to
318 This is used by the :class:`This` trait to allow subclasses to
319 accept superclasses for :class:`This` values.
319 accept superclasses for :class:`This` values.
320 """
320 """
321
321
322
322
323 metadata = {}
323 metadata = {}
324 default_value = Undefined
324 default_value = Undefined
325 allow_none = False
325 allow_none = False
326 info_text = 'any value'
326 info_text = 'any value'
327
327
328 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
328 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
329 """Create a TraitType.
329 """Create a TraitType.
330 """
330 """
331 if default_value is not NoDefaultSpecified:
331 if default_value is not NoDefaultSpecified:
332 self.default_value = default_value
332 self.default_value = default_value
333 if allow_none is not None:
333 if allow_none is not None:
334 self.allow_none = allow_none
334 self.allow_none = allow_none
335
335
336 if 'default' in metadata:
336 if 'default' in metadata:
337 # Warn the user that they probably meant default_value.
337 # Warn the user that they probably meant default_value.
338 warn(
338 warn(
339 "Parameter 'default' passed to TraitType. "
339 "Parameter 'default' passed to TraitType. "
340 "Did you mean 'default_value'?"
340 "Did you mean 'default_value'?"
341 )
341 )
342
342
343 if len(metadata) > 0:
343 if len(metadata) > 0:
344 if len(self.metadata) > 0:
344 if len(self.metadata) > 0:
345 self._metadata = self.metadata.copy()
345 self._metadata = self.metadata.copy()
346 self._metadata.update(metadata)
346 self._metadata.update(metadata)
347 else:
347 else:
348 self._metadata = metadata
348 self._metadata = metadata
349 else:
349 else:
350 self._metadata = self.metadata
350 self._metadata = self.metadata
351
351
352 self.init()
352 self.init()
353
353
354 def init(self):
354 def init(self):
355 pass
355 pass
356
356
357 def get_default_value(self):
357 def get_default_value(self):
358 """Create a new instance of the default value."""
358 """Create a new instance of the default value."""
359 return self.default_value
359 return self.default_value
360
360
361 def instance_init(self):
361 def instance_init(self):
362 """Part of the initialization which may depends on the underlying
362 """Part of the initialization which may depends on the underlying
363 HasTraits instance.
363 HasTraits instance.
364
364
365 It is typically overloaded for specific trait types.
365 It is typically overloaded for specific trait types.
366
366
367 This method is called by :meth:`HasTraits.__new__` and in the
367 This method is called by :meth:`HasTraits.__new__` and in the
368 :meth:`TraitType.instance_init` method of trait types holding
368 :meth:`TraitType.instance_init` method of trait types holding
369 other trait types.
369 other trait types.
370 """
370 """
371 pass
371 pass
372
372
373 def init_default_value(self, obj):
373 def init_default_value(self, obj):
374 """Instantiate the default value for the trait type.
374 """Instantiate the default value for the trait type.
375
375
376 This method is called by :meth:`TraitType.set_default_value` in the
376 This method is called by :meth:`TraitType.set_default_value` in the
377 case a default value is provided at construction time or later when
377 case a default value is provided at construction time or later when
378 accessing the trait value for the first time in
378 accessing the trait value for the first time in
379 :meth:`HasTraits.__get__`.
379 :meth:`HasTraits.__get__`.
380 """
380 """
381 value = self.get_default_value()
381 value = self.get_default_value()
382 value = self._validate(obj, value)
382 value = self._validate(obj, value)
383 obj._trait_values[self.name] = value
383 obj._trait_values[self.name] = value
384 return value
384 return value
385
385
386 def set_default_value(self, obj):
386 def set_default_value(self, obj):
387 """Set the default value on a per instance basis.
387 """Set the default value on a per instance basis.
388
388
389 This method is called by :meth:`HasTraits.__new__` to instantiate and
389 This method is called by :meth:`HasTraits.__new__` to instantiate and
390 validate the default value. The creation and validation of
390 validate the default value. The creation and validation of
391 default values must be delayed until the parent :class:`HasTraits`
391 default values must be delayed until the parent :class:`HasTraits`
392 class has been instantiated.
392 class has been instantiated.
393 Parameters
393 Parameters
394 ----------
394 ----------
395 obj : :class:`HasTraits` instance
395 obj : :class:`HasTraits` instance
396 The parent :class:`HasTraits` instance that has just been
396 The parent :class:`HasTraits` instance that has just been
397 created.
397 created.
398 """
398 """
399 # Check for a deferred initializer defined in the same class as the
399 # Check for a deferred initializer defined in the same class as the
400 # trait declaration or above.
400 # trait declaration or above.
401 mro = type(obj).mro()
401 mro = type(obj).mro()
402 meth_name = '_%s_default' % self.name
402 meth_name = '_%s_default' % self.name
403 for cls in mro[:mro.index(self.this_class)+1]:
403 for cls in mro[:mro.index(self.this_class)+1]:
404 if meth_name in cls.__dict__:
404 if meth_name in cls.__dict__:
405 break
405 break
406 else:
406 else:
407 # We didn't find one. Do static initialization.
407 # We didn't find one. Do static initialization.
408 self.init_default_value(obj)
408 self.init_default_value(obj)
409 return
409 return
410 # Complete the dynamic initialization.
410 # Complete the dynamic initialization.
411 obj._trait_dyn_inits[self.name] = meth_name
411 obj._trait_dyn_inits[self.name] = meth_name
412
412
413 def __get__(self, obj, cls=None):
413 def __get__(self, obj, cls=None):
414 """Get the value of the trait by self.name for the instance.
414 """Get the value of the trait by self.name for the instance.
415
415
416 Default values are instantiated when :meth:`HasTraits.__new__`
416 Default values are instantiated when :meth:`HasTraits.__new__`
417 is called. Thus by the time this method gets called either the
417 is called. Thus by the time this method gets called either the
418 default value or a user defined value (they called :meth:`__set__`)
418 default value or a user defined value (they called :meth:`__set__`)
419 is in the :class:`HasTraits` instance.
419 is in the :class:`HasTraits` instance.
420 """
420 """
421 if obj is None:
421 if obj is None:
422 return self
422 return self
423 else:
423 else:
424 try:
424 try:
425 value = obj._trait_values[self.name]
425 value = obj._trait_values[self.name]
426 except KeyError:
426 except KeyError:
427 # Check for a dynamic initializer.
427 # Check for a dynamic initializer.
428 if self.name in obj._trait_dyn_inits:
428 if self.name in obj._trait_dyn_inits:
429 method = getattr(obj, obj._trait_dyn_inits[self.name])
429 method = getattr(obj, obj._trait_dyn_inits[self.name])
430 value = method()
430 value = method()
431 # FIXME: Do we really validate here?
431 # FIXME: Do we really validate here?
432 value = self._validate(obj, value)
432 value = self._validate(obj, value)
433 obj._trait_values[self.name] = value
433 obj._trait_values[self.name] = value
434 return value
434 return value
435 else:
435 else:
436 return self.init_default_value(obj)
436 return self.init_default_value(obj)
437 except Exception:
437 except Exception:
438 # HasTraits should call set_default_value to populate
438 # HasTraits should call set_default_value to populate
439 # this. So this should never be reached.
439 # this. So this should never be reached.
440 raise TraitError('Unexpected error in TraitType: '
440 raise TraitError('Unexpected error in TraitType: '
441 'default value not set properly')
441 'default value not set properly')
442 else:
442 else:
443 return value
443 return value
444
444
445 def __set__(self, obj, value):
445 def __set__(self, obj, value):
446 new_value = self._validate(obj, value)
446 new_value = self._validate(obj, value)
447 try:
447 try:
448 old_value = obj._trait_values[self.name]
448 old_value = obj._trait_values[self.name]
449 except KeyError:
449 except KeyError:
450 old_value = None
450 old_value = None
451
451
452 obj._trait_values[self.name] = new_value
452 obj._trait_values[self.name] = new_value
453 try:
453 try:
454 silent = bool(old_value == new_value)
454 silent = bool(old_value == new_value)
455 except:
455 except:
456 # if there is an error in comparing, default to notify
456 # if there is an error in comparing, default to notify
457 silent = False
457 silent = False
458 if silent is not True:
458 if silent is not True:
459 # we explicitly compare silent to True just in case the equality
459 # we explicitly compare silent to True just in case the equality
460 # comparison above returns something other than True/False
460 # comparison above returns something other than True/False
461 obj._notify_trait(self.name, old_value, new_value)
461 obj._notify_trait(self.name, old_value, new_value)
462
462
463 def _validate(self, obj, value):
463 def _validate(self, obj, value):
464 if value is None and self.allow_none:
464 if value is None and self.allow_none:
465 return value
465 return value
466 if hasattr(self, 'validate'):
466 if hasattr(self, 'validate'):
467 value = self.validate(obj, value)
467 value = self.validate(obj, value)
468 try:
468 try:
469 obj_validate = getattr(obj, '_%s_validate' % self.name)
469 obj_validate = getattr(obj, '_%s_validate' % self.name)
470 except (AttributeError, RuntimeError):
470 except (AttributeError, RuntimeError):
471 # Qt mixins raise RuntimeError on missing attrs accessed before __init__
471 # Qt mixins raise RuntimeError on missing attrs accessed before __init__
472 pass
472 pass
473 else:
473 else:
474 value = obj_validate(value, self)
474 value = obj_validate(value, self)
475 return value
475 return value
476
476
477 def __or__(self, other):
477 def __or__(self, other):
478 if isinstance(other, Union):
478 if isinstance(other, Union):
479 return Union([self] + other.trait_types)
479 return Union([self] + other.trait_types)
480 else:
480 else:
481 return Union([self, other])
481 return Union([self, other])
482
482
483 def info(self):
483 def info(self):
484 return self.info_text
484 return self.info_text
485
485
486 def error(self, obj, value):
486 def error(self, obj, value):
487 if obj is not None:
487 if obj is not None:
488 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
488 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
489 % (self.name, class_of(obj),
489 % (self.name, class_of(obj),
490 self.info(), repr_type(value))
490 self.info(), repr_type(value))
491 else:
491 else:
492 e = "The '%s' trait must be %s, but a value of %r was specified." \
492 e = "The '%s' trait must be %s, but a value of %r was specified." \
493 % (self.name, self.info(), repr_type(value))
493 % (self.name, self.info(), repr_type(value))
494 raise TraitError(e)
494 raise TraitError(e)
495
495
496 def get_metadata(self, key, default=None):
496 def get_metadata(self, key, default=None):
497 return getattr(self, '_metadata', {}).get(key, default)
497 return getattr(self, '_metadata', {}).get(key, default)
498
498
499 def set_metadata(self, key, value):
499 def set_metadata(self, key, value):
500 getattr(self, '_metadata', {})[key] = value
500 getattr(self, '_metadata', {})[key] = value
501
501
502
502
503 #-----------------------------------------------------------------------------
503 #-----------------------------------------------------------------------------
504 # The HasTraits implementation
504 # The HasTraits implementation
505 #-----------------------------------------------------------------------------
505 #-----------------------------------------------------------------------------
506
506
507
507
508 class MetaHasTraits(type):
508 class MetaHasTraits(type):
509 """A metaclass for HasTraits.
509 """A metaclass for HasTraits.
510
510
511 This metaclass makes sure that any TraitType class attributes are
511 This metaclass makes sure that any TraitType class attributes are
512 instantiated and sets their name attribute.
512 instantiated and sets their name attribute.
513 """
513 """
514
514
515 def __new__(mcls, name, bases, classdict):
515 def __new__(mcls, name, bases, classdict):
516 """Create the HasTraits class.
516 """Create the HasTraits class.
517
517
518 This instantiates all TraitTypes in the class dict and sets their
518 This instantiates all TraitTypes in the class dict and sets their
519 :attr:`name` attribute.
519 :attr:`name` attribute.
520 """
520 """
521 # print "MetaHasTraitlets (mcls, name): ", mcls, name
521 # print "MetaHasTraitlets (mcls, name): ", mcls, name
522 # print "MetaHasTraitlets (bases): ", bases
522 # print "MetaHasTraitlets (bases): ", bases
523 # print "MetaHasTraitlets (classdict): ", classdict
523 # print "MetaHasTraitlets (classdict): ", classdict
524 for k,v in iteritems(classdict):
524 for k,v in iteritems(classdict):
525 if isinstance(v, TraitType):
525 if isinstance(v, TraitType):
526 v.name = k
526 v.name = k
527 elif inspect.isclass(v):
527 elif inspect.isclass(v):
528 if issubclass(v, TraitType):
528 if issubclass(v, TraitType):
529 vinst = v()
529 vinst = v()
530 vinst.name = k
530 vinst.name = k
531 classdict[k] = vinst
531 classdict[k] = vinst
532 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
532 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
533
533
534 def __init__(cls, name, bases, classdict):
534 def __init__(cls, name, bases, classdict):
535 """Finish initializing the HasTraits class.
535 """Finish initializing the HasTraits class.
536
536
537 This sets the :attr:`this_class` attribute of each TraitType in the
537 This sets the :attr:`this_class` attribute of each TraitType in the
538 class dict to the newly created class ``cls``.
538 class dict to the newly created class ``cls``.
539 """
539 """
540 for k, v in iteritems(classdict):
540 for k, v in iteritems(classdict):
541 if isinstance(v, TraitType):
541 if isinstance(v, TraitType):
542 v.this_class = cls
542 v.this_class = cls
543 super(MetaHasTraits, cls).__init__(name, bases, classdict)
543 super(MetaHasTraits, cls).__init__(name, bases, classdict)
544
544
545 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
545 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
546
546
547 def __new__(cls, *args, **kw):
547 def __new__(cls, *args, **kw):
548 # This is needed because object.__new__ only accepts
548 # This is needed because object.__new__ only accepts
549 # the cls argument.
549 # the cls argument.
550 new_meth = super(HasTraits, cls).__new__
550 new_meth = super(HasTraits, cls).__new__
551 if new_meth is object.__new__:
551 if new_meth is object.__new__:
552 inst = new_meth(cls)
552 inst = new_meth(cls)
553 else:
553 else:
554 inst = new_meth(cls, **kw)
554 inst = new_meth(cls, **kw)
555 inst._trait_values = {}
555 inst._trait_values = {}
556 inst._trait_notifiers = {}
556 inst._trait_notifiers = {}
557 inst._trait_dyn_inits = {}
557 inst._trait_dyn_inits = {}
558 # Here we tell all the TraitType instances to set their default
558 # Here we tell all the TraitType instances to set their default
559 # values on the instance.
559 # values on the instance.
560 for key in dir(cls):
560 for key in dir(cls):
561 # Some descriptors raise AttributeError like zope.interface's
561 # Some descriptors raise AttributeError like zope.interface's
562 # __provides__ attributes even though they exist. This causes
562 # __provides__ attributes even though they exist. This causes
563 # AttributeErrors even though they are listed in dir(cls).
563 # AttributeErrors even though they are listed in dir(cls).
564 try:
564 try:
565 value = getattr(cls, key)
565 value = getattr(cls, key)
566 except AttributeError:
566 except AttributeError:
567 pass
567 pass
568 else:
568 else:
569 if isinstance(value, TraitType):
569 if isinstance(value, TraitType):
570 value.instance_init()
570 value.instance_init()
571 if key not in kw:
571 if key not in kw:
572 value.set_default_value(inst)
572 value.set_default_value(inst)
573
573
574 return inst
574 return inst
575
575
576 def __init__(self, *args, **kw):
576 def __init__(self, *args, **kw):
577 # Allow trait values to be set using keyword arguments.
577 # Allow trait values to be set using keyword arguments.
578 # We need to use setattr for this to trigger validation and
578 # We need to use setattr for this to trigger validation and
579 # notifications.
579 # notifications.
580
580
581 with self.hold_trait_notifications():
581 with self.hold_trait_notifications():
582 for key, value in iteritems(kw):
582 for key, value in iteritems(kw):
583 setattr(self, key, value)
583 setattr(self, key, value)
584
584
585 @contextlib.contextmanager
585 @contextlib.contextmanager
586 def hold_trait_notifications(self):
586 def hold_trait_notifications(self):
587 """Context manager for bundling trait change notifications
587 """Context manager for bundling trait change notifications
588
588
589 Use this when doing multiple trait assignments (init, config),
589 Use this when doing multiple trait assignments (init, config),
590 to avoid race conditions in trait notifiers requesting other trait values.
590 to avoid race conditions in trait notifiers requesting other trait values.
591 All trait notifications will fire after all values have been assigned.
591 All trait notifications will fire after all values have been assigned.
592 """
592 """
593 _notify_trait = self._notify_trait
593 _notify_trait = self._notify_trait
594 notifications = []
594 notifications = []
595 self._notify_trait = lambda *a: notifications.append(a)
595 self._notify_trait = lambda *a: notifications.append(a)
596
596
597 try:
597 try:
598 yield
598 yield
599 finally:
599 finally:
600 self._notify_trait = _notify_trait
600 self._notify_trait = _notify_trait
601 if isinstance(_notify_trait, types.MethodType):
601 if isinstance(_notify_trait, types.MethodType):
602 # FIXME: remove when support is bumped to 3.4.
602 # FIXME: remove when support is bumped to 3.4.
603 # when original method is restored,
603 # when original method is restored,
604 # remove the redundant value from __dict__
604 # remove the redundant value from __dict__
605 # (only used to preserve pickleability on Python < 3.4)
605 # (only used to preserve pickleability on Python < 3.4)
606 self.__dict__.pop('_notify_trait', None)
606 self.__dict__.pop('_notify_trait', None)
607 # trigger delayed notifications
607 # trigger delayed notifications
608 for args in notifications:
608 for args in notifications:
609 self._notify_trait(*args)
609 self._notify_trait(*args)
610
610
611 def _notify_trait(self, name, old_value, new_value):
611 def _notify_trait(self, name, old_value, new_value):
612
612
613 # First dynamic ones
613 # First dynamic ones
614 callables = []
614 callables = []
615 callables.extend(self._trait_notifiers.get(name,[]))
615 callables.extend(self._trait_notifiers.get(name,[]))
616 callables.extend(self._trait_notifiers.get('anytrait',[]))
616 callables.extend(self._trait_notifiers.get('anytrait',[]))
617
617
618 # Now static ones
618 # Now static ones
619 try:
619 try:
620 cb = getattr(self, '_%s_changed' % name)
620 cb = getattr(self, '_%s_changed' % name)
621 except:
621 except:
622 pass
622 pass
623 else:
623 else:
624 callables.append(cb)
624 callables.append(cb)
625
625
626 # Call them all now
626 # Call them all now
627 for c in callables:
627 for c in callables:
628 # Traits catches and logs errors here. I allow them to raise
628 # Traits catches and logs errors here. I allow them to raise
629 if callable(c):
629 if callable(c):
630 argspec = getargspec(c)
630 argspec = getargspec(c)
631
631
632 nargs = len(argspec[0])
632 nargs = len(argspec[0])
633 # Bound methods have an additional 'self' argument
633 # Bound methods have an additional 'self' argument
634 # I don't know how to treat unbound methods, but they
634 # I don't know how to treat unbound methods, but they
635 # can't really be used for callbacks.
635 # can't really be used for callbacks.
636 if isinstance(c, types.MethodType):
636 if isinstance(c, types.MethodType):
637 offset = -1
637 offset = -1
638 else:
638 else:
639 offset = 0
639 offset = 0
640 if nargs + offset == 0:
640 if nargs + offset == 0:
641 c()
641 c()
642 elif nargs + offset == 1:
642 elif nargs + offset == 1:
643 c(name)
643 c(name)
644 elif nargs + offset == 2:
644 elif nargs + offset == 2:
645 c(name, new_value)
645 c(name, new_value)
646 elif nargs + offset == 3:
646 elif nargs + offset == 3:
647 c(name, old_value, new_value)
647 c(name, old_value, new_value)
648 else:
648 else:
649 raise TraitError('a trait changed callback '
649 raise TraitError('a trait changed callback '
650 'must have 0-3 arguments.')
650 'must have 0-3 arguments.')
651 else:
651 else:
652 raise TraitError('a trait changed callback '
652 raise TraitError('a trait changed callback '
653 'must be callable.')
653 'must be callable.')
654
654
655
655
656 def _add_notifiers(self, handler, name):
656 def _add_notifiers(self, handler, name):
657 if name not in self._trait_notifiers:
657 if name not in self._trait_notifiers:
658 nlist = []
658 nlist = []
659 self._trait_notifiers[name] = nlist
659 self._trait_notifiers[name] = nlist
660 else:
660 else:
661 nlist = self._trait_notifiers[name]
661 nlist = self._trait_notifiers[name]
662 if handler not in nlist:
662 if handler not in nlist:
663 nlist.append(handler)
663 nlist.append(handler)
664
664
665 def _remove_notifiers(self, handler, name):
665 def _remove_notifiers(self, handler, name):
666 if name in self._trait_notifiers:
666 if name in self._trait_notifiers:
667 nlist = self._trait_notifiers[name]
667 nlist = self._trait_notifiers[name]
668 try:
668 try:
669 index = nlist.index(handler)
669 index = nlist.index(handler)
670 except ValueError:
670 except ValueError:
671 pass
671 pass
672 else:
672 else:
673 del nlist[index]
673 del nlist[index]
674
674
675 def on_trait_change(self, handler, name=None, remove=False):
675 def on_trait_change(self, handler, name=None, remove=False):
676 """Setup a handler to be called when a trait changes.
676 """Setup a handler to be called when a trait changes.
677
677
678 This is used to setup dynamic notifications of trait changes.
678 This is used to setup dynamic notifications of trait changes.
679
679
680 Static handlers can be created by creating methods on a HasTraits
680 Static handlers can be created by creating methods on a HasTraits
681 subclass with the naming convention '_[traitname]_changed'. Thus,
681 subclass with the naming convention '_[traitname]_changed'. Thus,
682 to create static handler for the trait 'a', create the method
682 to create static handler for the trait 'a', create the method
683 _a_changed(self, name, old, new) (fewer arguments can be used, see
683 _a_changed(self, name, old, new) (fewer arguments can be used, see
684 below).
684 below).
685
685
686 Parameters
686 Parameters
687 ----------
687 ----------
688 handler : callable
688 handler : callable
689 A callable that is called when a trait changes. Its
689 A callable that is called when a trait changes. Its
690 signature can be handler(), handler(name), handler(name, new)
690 signature can be handler(), handler(name), handler(name, new)
691 or handler(name, old, new).
691 or handler(name, old, new).
692 name : list, str, None
692 name : list, str, None
693 If None, the handler will apply to all traits. If a list
693 If None, the handler will apply to all traits. If a list
694 of str, handler will apply to all names in the list. If a
694 of str, handler will apply to all names in the list. If a
695 str, the handler will apply just to that name.
695 str, the handler will apply just to that name.
696 remove : bool
696 remove : bool
697 If False (the default), then install the handler. If True
697 If False (the default), then install the handler. If True
698 then unintall it.
698 then unintall it.
699 """
699 """
700 if remove:
700 if remove:
701 names = parse_notifier_name(name)
701 names = parse_notifier_name(name)
702 for n in names:
702 for n in names:
703 self._remove_notifiers(handler, n)
703 self._remove_notifiers(handler, n)
704 else:
704 else:
705 names = parse_notifier_name(name)
705 names = parse_notifier_name(name)
706 for n in names:
706 for n in names:
707 self._add_notifiers(handler, n)
707 self._add_notifiers(handler, n)
708
708
709 @classmethod
709 @classmethod
710 def class_trait_names(cls, **metadata):
710 def class_trait_names(cls, **metadata):
711 """Get a list of all the names of this class' traits.
711 """Get a list of all the names of this class' traits.
712
712
713 This method is just like the :meth:`trait_names` method,
713 This method is just like the :meth:`trait_names` method,
714 but is unbound.
714 but is unbound.
715 """
715 """
716 return cls.class_traits(**metadata).keys()
716 return cls.class_traits(**metadata).keys()
717
717
718 @classmethod
718 @classmethod
719 def class_traits(cls, **metadata):
719 def class_traits(cls, **metadata):
720 """Get a `dict` of all the traits of this class. The dictionary
720 """Get a `dict` of all the traits of this class. The dictionary
721 is keyed on the name and the values are the TraitType objects.
721 is keyed on the name and the values are the TraitType objects.
722
722
723 This method is just like the :meth:`traits` method, but is unbound.
723 This method is just like the :meth:`traits` method, but is unbound.
724
724
725 The TraitTypes returned don't know anything about the values
725 The TraitTypes returned don't know anything about the values
726 that the various HasTrait's instances are holding.
726 that the various HasTrait's instances are holding.
727
727
728 The metadata kwargs allow functions to be passed in which
728 The metadata kwargs allow functions to be passed in which
729 filter traits based on metadata values. The functions should
729 filter traits based on metadata values. The functions should
730 take a single value as an argument and return a boolean. If
730 take a single value as an argument and return a boolean. If
731 any function returns False, then the trait is not included in
731 any function returns False, then the trait is not included in
732 the output. This does not allow for any simple way of
732 the output. This does not allow for any simple way of
733 testing that a metadata name exists and has any
733 testing that a metadata name exists and has any
734 value because get_metadata returns None if a metadata key
734 value because get_metadata returns None if a metadata key
735 doesn't exist.
735 doesn't exist.
736 """
736 """
737 traits = dict([memb for memb in getmembers(cls) if
737 traits = dict([memb for memb in getmembers(cls) if
738 isinstance(memb[1], TraitType)])
738 isinstance(memb[1], TraitType)])
739
739
740 if len(metadata) == 0:
740 if len(metadata) == 0:
741 return traits
741 return traits
742
742
743 for meta_name, meta_eval in metadata.items():
743 for meta_name, meta_eval in metadata.items():
744 if type(meta_eval) is not FunctionType:
744 if type(meta_eval) is not FunctionType:
745 metadata[meta_name] = _SimpleTest(meta_eval)
745 metadata[meta_name] = _SimpleTest(meta_eval)
746
746
747 result = {}
747 result = {}
748 for name, trait in traits.items():
748 for name, trait in traits.items():
749 for meta_name, meta_eval in metadata.items():
749 for meta_name, meta_eval in metadata.items():
750 if not meta_eval(trait.get_metadata(meta_name)):
750 if not meta_eval(trait.get_metadata(meta_name)):
751 break
751 break
752 else:
752 else:
753 result[name] = trait
753 result[name] = trait
754
754
755 return result
755 return result
756
756
757 def trait_names(self, **metadata):
757 def trait_names(self, **metadata):
758 """Get a list of all the names of this class' traits."""
758 """Get a list of all the names of this class' traits."""
759 return self.traits(**metadata).keys()
759 return self.traits(**metadata).keys()
760
760
761 def traits(self, **metadata):
761 def traits(self, **metadata):
762 """Get a `dict` of all the traits of this class. The dictionary
762 """Get a `dict` of all the traits of this class. The dictionary
763 is keyed on the name and the values are the TraitType objects.
763 is keyed on the name and the values are the TraitType objects.
764
764
765 The TraitTypes returned don't know anything about the values
765 The TraitTypes returned don't know anything about the values
766 that the various HasTrait's instances are holding.
766 that the various HasTrait's instances are holding.
767
767
768 The metadata kwargs allow functions to be passed in which
768 The metadata kwargs allow functions to be passed in which
769 filter traits based on metadata values. The functions should
769 filter traits based on metadata values. The functions should
770 take a single value as an argument and return a boolean. If
770 take a single value as an argument and return a boolean. If
771 any function returns False, then the trait is not included in
771 any function returns False, then the trait is not included in
772 the output. This does not allow for any simple way of
772 the output. This does not allow for any simple way of
773 testing that a metadata name exists and has any
773 testing that a metadata name exists and has any
774 value because get_metadata returns None if a metadata key
774 value because get_metadata returns None if a metadata key
775 doesn't exist.
775 doesn't exist.
776 """
776 """
777 traits = dict([memb for memb in getmembers(self.__class__) if
777 traits = dict([memb for memb in getmembers(self.__class__) if
778 isinstance(memb[1], TraitType)])
778 isinstance(memb[1], TraitType)])
779
779
780 if len(metadata) == 0:
780 if len(metadata) == 0:
781 return traits
781 return traits
782
782
783 for meta_name, meta_eval in metadata.items():
783 for meta_name, meta_eval in metadata.items():
784 if type(meta_eval) is not FunctionType:
784 if type(meta_eval) is not FunctionType:
785 metadata[meta_name] = _SimpleTest(meta_eval)
785 metadata[meta_name] = _SimpleTest(meta_eval)
786
786
787 result = {}
787 result = {}
788 for name, trait in traits.items():
788 for name, trait in traits.items():
789 for meta_name, meta_eval in metadata.items():
789 for meta_name, meta_eval in metadata.items():
790 if not meta_eval(trait.get_metadata(meta_name)):
790 if not meta_eval(trait.get_metadata(meta_name)):
791 break
791 break
792 else:
792 else:
793 result[name] = trait
793 result[name] = trait
794
794
795 return result
795 return result
796
796
797 def trait_metadata(self, traitname, key, default=None):
797 def trait_metadata(self, traitname, key, default=None):
798 """Get metadata values for trait by key."""
798 """Get metadata values for trait by key."""
799 try:
799 try:
800 trait = getattr(self.__class__, traitname)
800 trait = getattr(self.__class__, traitname)
801 except AttributeError:
801 except AttributeError:
802 raise TraitError("Class %s does not have a trait named %s" %
802 raise TraitError("Class %s does not have a trait named %s" %
803 (self.__class__.__name__, traitname))
803 (self.__class__.__name__, traitname))
804 else:
804 else:
805 return trait.get_metadata(key, default)
805 return trait.get_metadata(key, default)
806
806
807 def add_trait(self, traitname, trait):
808 """Dynamically add a trait attribute to the HasTraits instance."""
809 self.__class__ = type(self.__class__.__name__, (self.__class__,),
810 {traitname: trait})
811 trait.set_default_value(self)
812
807 #-----------------------------------------------------------------------------
813 #-----------------------------------------------------------------------------
808 # Actual TraitTypes implementations/subclasses
814 # Actual TraitTypes implementations/subclasses
809 #-----------------------------------------------------------------------------
815 #-----------------------------------------------------------------------------
810
816
811 #-----------------------------------------------------------------------------
817 #-----------------------------------------------------------------------------
812 # TraitTypes subclasses for handling classes and instances of classes
818 # TraitTypes subclasses for handling classes and instances of classes
813 #-----------------------------------------------------------------------------
819 #-----------------------------------------------------------------------------
814
820
815
821
816 class ClassBasedTraitType(TraitType):
822 class ClassBasedTraitType(TraitType):
817 """
823 """
818 A trait with error reporting and string -> type resolution for Type,
824 A trait with error reporting and string -> type resolution for Type,
819 Instance and This.
825 Instance and This.
820 """
826 """
821
827
822 def _resolve_string(self, string):
828 def _resolve_string(self, string):
823 """
829 """
824 Resolve a string supplied for a type into an actual object.
830 Resolve a string supplied for a type into an actual object.
825 """
831 """
826 return import_item(string)
832 return import_item(string)
827
833
828 def error(self, obj, value):
834 def error(self, obj, value):
829 kind = type(value)
835 kind = type(value)
830 if (not py3compat.PY3) and kind is InstanceType:
836 if (not py3compat.PY3) and kind is InstanceType:
831 msg = 'class %s' % value.__class__.__name__
837 msg = 'class %s' % value.__class__.__name__
832 else:
838 else:
833 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
839 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
834
840
835 if obj is not None:
841 if obj is not None:
836 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
842 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
837 % (self.name, class_of(obj),
843 % (self.name, class_of(obj),
838 self.info(), msg)
844 self.info(), msg)
839 else:
845 else:
840 e = "The '%s' trait must be %s, but a value of %r was specified." \
846 e = "The '%s' trait must be %s, but a value of %r was specified." \
841 % (self.name, self.info(), msg)
847 % (self.name, self.info(), msg)
842
848
843 raise TraitError(e)
849 raise TraitError(e)
844
850
845
851
846 class Type(ClassBasedTraitType):
852 class Type(ClassBasedTraitType):
847 """A trait whose value must be a subclass of a specified class."""
853 """A trait whose value must be a subclass of a specified class."""
848
854
849 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
855 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
850 """Construct a Type trait
856 """Construct a Type trait
851
857
852 A Type trait specifies that its values must be subclasses of
858 A Type trait specifies that its values must be subclasses of
853 a particular class.
859 a particular class.
854
860
855 If only ``default_value`` is given, it is used for the ``klass`` as
861 If only ``default_value`` is given, it is used for the ``klass`` as
856 well.
862 well.
857
863
858 Parameters
864 Parameters
859 ----------
865 ----------
860 default_value : class, str or None
866 default_value : class, str or None
861 The default value must be a subclass of klass. If an str,
867 The default value must be a subclass of klass. If an str,
862 the str must be a fully specified class name, like 'foo.bar.Bah'.
868 the str must be a fully specified class name, like 'foo.bar.Bah'.
863 The string is resolved into real class, when the parent
869 The string is resolved into real class, when the parent
864 :class:`HasTraits` class is instantiated.
870 :class:`HasTraits` class is instantiated.
865 klass : class, str, None
871 klass : class, str, None
866 Values of this trait must be a subclass of klass. The klass
872 Values of this trait must be a subclass of klass. The klass
867 may be specified in a string like: 'foo.bar.MyClass'.
873 may be specified in a string like: 'foo.bar.MyClass'.
868 The string is resolved into real class, when the parent
874 The string is resolved into real class, when the parent
869 :class:`HasTraits` class is instantiated.
875 :class:`HasTraits` class is instantiated.
870 allow_none : bool [ default True ]
876 allow_none : bool [ default True ]
871 Indicates whether None is allowed as an assignable value. Even if
877 Indicates whether None is allowed as an assignable value. Even if
872 ``False``, the default value may be ``None``.
878 ``False``, the default value may be ``None``.
873 """
879 """
874 if default_value is None:
880 if default_value is None:
875 if klass is None:
881 if klass is None:
876 klass = object
882 klass = object
877 elif klass is None:
883 elif klass is None:
878 klass = default_value
884 klass = default_value
879
885
880 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
886 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
881 raise TraitError("A Type trait must specify a class.")
887 raise TraitError("A Type trait must specify a class.")
882
888
883 self.klass = klass
889 self.klass = klass
884
890
885 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
891 super(Type, self).__init__(default_value, allow_none=allow_none, **metadata)
886
892
887 def validate(self, obj, value):
893 def validate(self, obj, value):
888 """Validates that the value is a valid object instance."""
894 """Validates that the value is a valid object instance."""
889 if isinstance(value, py3compat.string_types):
895 if isinstance(value, py3compat.string_types):
890 try:
896 try:
891 value = self._resolve_string(value)
897 value = self._resolve_string(value)
892 except ImportError:
898 except ImportError:
893 raise TraitError("The '%s' trait of %s instance must be a type, but "
899 raise TraitError("The '%s' trait of %s instance must be a type, but "
894 "%r could not be imported" % (self.name, obj, value))
900 "%r could not be imported" % (self.name, obj, value))
895 try:
901 try:
896 if issubclass(value, self.klass):
902 if issubclass(value, self.klass):
897 return value
903 return value
898 except:
904 except:
899 pass
905 pass
900
906
901 self.error(obj, value)
907 self.error(obj, value)
902
908
903 def info(self):
909 def info(self):
904 """ Returns a description of the trait."""
910 """ Returns a description of the trait."""
905 if isinstance(self.klass, py3compat.string_types):
911 if isinstance(self.klass, py3compat.string_types):
906 klass = self.klass
912 klass = self.klass
907 else:
913 else:
908 klass = self.klass.__name__
914 klass = self.klass.__name__
909 result = 'a subclass of ' + klass
915 result = 'a subclass of ' + klass
910 if self.allow_none:
916 if self.allow_none:
911 return result + ' or None'
917 return result + ' or None'
912 return result
918 return result
913
919
914 def instance_init(self):
920 def instance_init(self):
915 self._resolve_classes()
921 self._resolve_classes()
916 super(Type, self).instance_init()
922 super(Type, self).instance_init()
917
923
918 def _resolve_classes(self):
924 def _resolve_classes(self):
919 if isinstance(self.klass, py3compat.string_types):
925 if isinstance(self.klass, py3compat.string_types):
920 self.klass = self._resolve_string(self.klass)
926 self.klass = self._resolve_string(self.klass)
921 if isinstance(self.default_value, py3compat.string_types):
927 if isinstance(self.default_value, py3compat.string_types):
922 self.default_value = self._resolve_string(self.default_value)
928 self.default_value = self._resolve_string(self.default_value)
923
929
924 def get_default_value(self):
930 def get_default_value(self):
925 return self.default_value
931 return self.default_value
926
932
927
933
928 class DefaultValueGenerator(object):
934 class DefaultValueGenerator(object):
929 """A class for generating new default value instances."""
935 """A class for generating new default value instances."""
930
936
931 def __init__(self, *args, **kw):
937 def __init__(self, *args, **kw):
932 self.args = args
938 self.args = args
933 self.kw = kw
939 self.kw = kw
934
940
935 def generate(self, klass):
941 def generate(self, klass):
936 return klass(*self.args, **self.kw)
942 return klass(*self.args, **self.kw)
937
943
938
944
939 class Instance(ClassBasedTraitType):
945 class Instance(ClassBasedTraitType):
940 """A trait whose value must be an instance of a specified class.
946 """A trait whose value must be an instance of a specified class.
941
947
942 The value can also be an instance of a subclass of the specified class.
948 The value can also be an instance of a subclass of the specified class.
943
949
944 Subclasses can declare default classes by overriding the klass attribute
950 Subclasses can declare default classes by overriding the klass attribute
945 """
951 """
946
952
947 klass = None
953 klass = None
948
954
949 def __init__(self, klass=None, args=None, kw=None,
955 def __init__(self, klass=None, args=None, kw=None,
950 allow_none=True, **metadata ):
956 allow_none=True, **metadata ):
951 """Construct an Instance trait.
957 """Construct an Instance trait.
952
958
953 This trait allows values that are instances of a particular
959 This trait allows values that are instances of a particular
954 class or its subclasses. Our implementation is quite different
960 class or its subclasses. Our implementation is quite different
955 from that of enthough.traits as we don't allow instances to be used
961 from that of enthough.traits as we don't allow instances to be used
956 for klass and we handle the ``args`` and ``kw`` arguments differently.
962 for klass and we handle the ``args`` and ``kw`` arguments differently.
957
963
958 Parameters
964 Parameters
959 ----------
965 ----------
960 klass : class, str
966 klass : class, str
961 The class that forms the basis for the trait. Class names
967 The class that forms the basis for the trait. Class names
962 can also be specified as strings, like 'foo.bar.Bar'.
968 can also be specified as strings, like 'foo.bar.Bar'.
963 args : tuple
969 args : tuple
964 Positional arguments for generating the default value.
970 Positional arguments for generating the default value.
965 kw : dict
971 kw : dict
966 Keyword arguments for generating the default value.
972 Keyword arguments for generating the default value.
967 allow_none : bool [default True]
973 allow_none : bool [default True]
968 Indicates whether None is allowed as a value.
974 Indicates whether None is allowed as a value.
969
975
970 Notes
976 Notes
971 -----
977 -----
972 If both ``args`` and ``kw`` are None, then the default value is None.
978 If both ``args`` and ``kw`` are None, then the default value is None.
973 If ``args`` is a tuple and ``kw`` is a dict, then the default is
979 If ``args`` is a tuple and ``kw`` is a dict, then the default is
974 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
980 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
975 None, the None is replaced by ``()`` or ``{}``, respectively.
981 None, the None is replaced by ``()`` or ``{}``, respectively.
976 """
982 """
977 if klass is None:
983 if klass is None:
978 klass = self.klass
984 klass = self.klass
979
985
980 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
986 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
981 self.klass = klass
987 self.klass = klass
982 else:
988 else:
983 raise TraitError('The klass attribute must be a class'
989 raise TraitError('The klass attribute must be a class'
984 ' not: %r' % klass)
990 ' not: %r' % klass)
985
991
986 # self.klass is a class, so handle default_value
992 # self.klass is a class, so handle default_value
987 if args is None and kw is None:
993 if args is None and kw is None:
988 default_value = None
994 default_value = None
989 else:
995 else:
990 if args is None:
996 if args is None:
991 # kw is not None
997 # kw is not None
992 args = ()
998 args = ()
993 elif kw is None:
999 elif kw is None:
994 # args is not None
1000 # args is not None
995 kw = {}
1001 kw = {}
996
1002
997 if not isinstance(kw, dict):
1003 if not isinstance(kw, dict):
998 raise TraitError("The 'kw' argument must be a dict or None.")
1004 raise TraitError("The 'kw' argument must be a dict or None.")
999 if not isinstance(args, tuple):
1005 if not isinstance(args, tuple):
1000 raise TraitError("The 'args' argument must be a tuple or None.")
1006 raise TraitError("The 'args' argument must be a tuple or None.")
1001
1007
1002 default_value = DefaultValueGenerator(*args, **kw)
1008 default_value = DefaultValueGenerator(*args, **kw)
1003
1009
1004 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
1010 super(Instance, self).__init__(default_value, allow_none=allow_none, **metadata)
1005
1011
1006 def validate(self, obj, value):
1012 def validate(self, obj, value):
1007 if isinstance(value, self.klass):
1013 if isinstance(value, self.klass):
1008 return value
1014 return value
1009 else:
1015 else:
1010 self.error(obj, value)
1016 self.error(obj, value)
1011
1017
1012 def info(self):
1018 def info(self):
1013 if isinstance(self.klass, py3compat.string_types):
1019 if isinstance(self.klass, py3compat.string_types):
1014 klass = self.klass
1020 klass = self.klass
1015 else:
1021 else:
1016 klass = self.klass.__name__
1022 klass = self.klass.__name__
1017 result = class_of(klass)
1023 result = class_of(klass)
1018 if self.allow_none:
1024 if self.allow_none:
1019 return result + ' or None'
1025 return result + ' or None'
1020
1026
1021 return result
1027 return result
1022
1028
1023 def instance_init(self):
1029 def instance_init(self):
1024 self._resolve_classes()
1030 self._resolve_classes()
1025 super(Instance, self).instance_init()
1031 super(Instance, self).instance_init()
1026
1032
1027 def _resolve_classes(self):
1033 def _resolve_classes(self):
1028 if isinstance(self.klass, py3compat.string_types):
1034 if isinstance(self.klass, py3compat.string_types):
1029 self.klass = self._resolve_string(self.klass)
1035 self.klass = self._resolve_string(self.klass)
1030
1036
1031 def get_default_value(self):
1037 def get_default_value(self):
1032 """Instantiate a default value instance.
1038 """Instantiate a default value instance.
1033
1039
1034 This is called when the containing HasTraits classes'
1040 This is called when the containing HasTraits classes'
1035 :meth:`__new__` method is called to ensure that a unique instance
1041 :meth:`__new__` method is called to ensure that a unique instance
1036 is created for each HasTraits instance.
1042 is created for each HasTraits instance.
1037 """
1043 """
1038 dv = self.default_value
1044 dv = self.default_value
1039 if isinstance(dv, DefaultValueGenerator):
1045 if isinstance(dv, DefaultValueGenerator):
1040 return dv.generate(self.klass)
1046 return dv.generate(self.klass)
1041 else:
1047 else:
1042 return dv
1048 return dv
1043
1049
1044
1050
1045 class ForwardDeclaredMixin(object):
1051 class ForwardDeclaredMixin(object):
1046 """
1052 """
1047 Mixin for forward-declared versions of Instance and Type.
1053 Mixin for forward-declared versions of Instance and Type.
1048 """
1054 """
1049 def _resolve_string(self, string):
1055 def _resolve_string(self, string):
1050 """
1056 """
1051 Find the specified class name by looking for it in the module in which
1057 Find the specified class name by looking for it in the module in which
1052 our this_class attribute was defined.
1058 our this_class attribute was defined.
1053 """
1059 """
1054 modname = self.this_class.__module__
1060 modname = self.this_class.__module__
1055 return import_item('.'.join([modname, string]))
1061 return import_item('.'.join([modname, string]))
1056
1062
1057
1063
1058 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1064 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1059 """
1065 """
1060 Forward-declared version of Type.
1066 Forward-declared version of Type.
1061 """
1067 """
1062 pass
1068 pass
1063
1069
1064
1070
1065 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1071 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1066 """
1072 """
1067 Forward-declared version of Instance.
1073 Forward-declared version of Instance.
1068 """
1074 """
1069 pass
1075 pass
1070
1076
1071
1077
1072 class This(ClassBasedTraitType):
1078 class This(ClassBasedTraitType):
1073 """A trait for instances of the class containing this trait.
1079 """A trait for instances of the class containing this trait.
1074
1080
1075 Because how how and when class bodies are executed, the ``This``
1081 Because how how and when class bodies are executed, the ``This``
1076 trait can only have a default value of None. This, and because we
1082 trait can only have a default value of None. This, and because we
1077 always validate default values, ``allow_none`` is *always* true.
1083 always validate default values, ``allow_none`` is *always* true.
1078 """
1084 """
1079
1085
1080 info_text = 'an instance of the same type as the receiver or None'
1086 info_text = 'an instance of the same type as the receiver or None'
1081
1087
1082 def __init__(self, **metadata):
1088 def __init__(self, **metadata):
1083 super(This, self).__init__(None, **metadata)
1089 super(This, self).__init__(None, **metadata)
1084
1090
1085 def validate(self, obj, value):
1091 def validate(self, obj, value):
1086 # What if value is a superclass of obj.__class__? This is
1092 # What if value is a superclass of obj.__class__? This is
1087 # complicated if it was the superclass that defined the This
1093 # complicated if it was the superclass that defined the This
1088 # trait.
1094 # trait.
1089 if isinstance(value, self.this_class) or (value is None):
1095 if isinstance(value, self.this_class) or (value is None):
1090 return value
1096 return value
1091 else:
1097 else:
1092 self.error(obj, value)
1098 self.error(obj, value)
1093
1099
1094
1100
1095 class Union(TraitType):
1101 class Union(TraitType):
1096 """A trait type representing a Union type."""
1102 """A trait type representing a Union type."""
1097
1103
1098 def __init__(self, trait_types, **metadata):
1104 def __init__(self, trait_types, **metadata):
1099 """Construct a Union trait.
1105 """Construct a Union trait.
1100
1106
1101 This trait allows values that are allowed by at least one of the
1107 This trait allows values that are allowed by at least one of the
1102 specified trait types. A Union traitlet cannot have metadata on
1108 specified trait types. A Union traitlet cannot have metadata on
1103 its own, besides the metadata of the listed types.
1109 its own, besides the metadata of the listed types.
1104
1110
1105 Parameters
1111 Parameters
1106 ----------
1112 ----------
1107 trait_types: sequence
1113 trait_types: sequence
1108 The list of trait types of length at least 1.
1114 The list of trait types of length at least 1.
1109
1115
1110 Notes
1116 Notes
1111 -----
1117 -----
1112 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1118 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1113 with the validation function of Float, then Bool, and finally Int.
1119 with the validation function of Float, then Bool, and finally Int.
1114 """
1120 """
1115 self.trait_types = trait_types
1121 self.trait_types = trait_types
1116 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1122 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1117 self.default_value = self.trait_types[0].get_default_value()
1123 self.default_value = self.trait_types[0].get_default_value()
1118 super(Union, self).__init__(**metadata)
1124 super(Union, self).__init__(**metadata)
1119
1125
1120 def instance_init(self):
1126 def instance_init(self):
1121 for trait_type in self.trait_types:
1127 for trait_type in self.trait_types:
1122 trait_type.name = self.name
1128 trait_type.name = self.name
1123 trait_type.this_class = self.this_class
1129 trait_type.this_class = self.this_class
1124 trait_type.instance_init()
1130 trait_type.instance_init()
1125 super(Union, self).instance_init()
1131 super(Union, self).instance_init()
1126
1132
1127 def validate(self, obj, value):
1133 def validate(self, obj, value):
1128 for trait_type in self.trait_types:
1134 for trait_type in self.trait_types:
1129 try:
1135 try:
1130 v = trait_type._validate(obj, value)
1136 v = trait_type._validate(obj, value)
1131 self._metadata = trait_type._metadata
1137 self._metadata = trait_type._metadata
1132 return v
1138 return v
1133 except TraitError:
1139 except TraitError:
1134 continue
1140 continue
1135 self.error(obj, value)
1141 self.error(obj, value)
1136
1142
1137 def __or__(self, other):
1143 def __or__(self, other):
1138 if isinstance(other, Union):
1144 if isinstance(other, Union):
1139 return Union(self.trait_types + other.trait_types)
1145 return Union(self.trait_types + other.trait_types)
1140 else:
1146 else:
1141 return Union(self.trait_types + [other])
1147 return Union(self.trait_types + [other])
1142
1148
1143 #-----------------------------------------------------------------------------
1149 #-----------------------------------------------------------------------------
1144 # Basic TraitTypes implementations/subclasses
1150 # Basic TraitTypes implementations/subclasses
1145 #-----------------------------------------------------------------------------
1151 #-----------------------------------------------------------------------------
1146
1152
1147
1153
1148 class Any(TraitType):
1154 class Any(TraitType):
1149 default_value = None
1155 default_value = None
1150 info_text = 'any value'
1156 info_text = 'any value'
1151
1157
1152
1158
1153 class Int(TraitType):
1159 class Int(TraitType):
1154 """An int trait."""
1160 """An int trait."""
1155
1161
1156 default_value = 0
1162 default_value = 0
1157 info_text = 'an int'
1163 info_text = 'an int'
1158
1164
1159 def validate(self, obj, value):
1165 def validate(self, obj, value):
1160 if isinstance(value, int):
1166 if isinstance(value, int):
1161 return value
1167 return value
1162 self.error(obj, value)
1168 self.error(obj, value)
1163
1169
1164 class CInt(Int):
1170 class CInt(Int):
1165 """A casting version of the int trait."""
1171 """A casting version of the int trait."""
1166
1172
1167 def validate(self, obj, value):
1173 def validate(self, obj, value):
1168 try:
1174 try:
1169 return int(value)
1175 return int(value)
1170 except:
1176 except:
1171 self.error(obj, value)
1177 self.error(obj, value)
1172
1178
1173 if py3compat.PY3:
1179 if py3compat.PY3:
1174 Long, CLong = Int, CInt
1180 Long, CLong = Int, CInt
1175 Integer = Int
1181 Integer = Int
1176 else:
1182 else:
1177 class Long(TraitType):
1183 class Long(TraitType):
1178 """A long integer trait."""
1184 """A long integer trait."""
1179
1185
1180 default_value = 0
1186 default_value = 0
1181 info_text = 'a long'
1187 info_text = 'a long'
1182
1188
1183 def validate(self, obj, value):
1189 def validate(self, obj, value):
1184 if isinstance(value, long):
1190 if isinstance(value, long):
1185 return value
1191 return value
1186 if isinstance(value, int):
1192 if isinstance(value, int):
1187 return long(value)
1193 return long(value)
1188 self.error(obj, value)
1194 self.error(obj, value)
1189
1195
1190
1196
1191 class CLong(Long):
1197 class CLong(Long):
1192 """A casting version of the long integer trait."""
1198 """A casting version of the long integer trait."""
1193
1199
1194 def validate(self, obj, value):
1200 def validate(self, obj, value):
1195 try:
1201 try:
1196 return long(value)
1202 return long(value)
1197 except:
1203 except:
1198 self.error(obj, value)
1204 self.error(obj, value)
1199
1205
1200 class Integer(TraitType):
1206 class Integer(TraitType):
1201 """An integer trait.
1207 """An integer trait.
1202
1208
1203 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1209 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1204
1210
1205 default_value = 0
1211 default_value = 0
1206 info_text = 'an integer'
1212 info_text = 'an integer'
1207
1213
1208 def validate(self, obj, value):
1214 def validate(self, obj, value):
1209 if isinstance(value, int):
1215 if isinstance(value, int):
1210 return value
1216 return value
1211 if isinstance(value, long):
1217 if isinstance(value, long):
1212 # downcast longs that fit in int:
1218 # downcast longs that fit in int:
1213 # note that int(n > sys.maxint) returns a long, so
1219 # note that int(n > sys.maxint) returns a long, so
1214 # we don't need a condition on this cast
1220 # we don't need a condition on this cast
1215 return int(value)
1221 return int(value)
1216 if sys.platform == "cli":
1222 if sys.platform == "cli":
1217 from System import Int64
1223 from System import Int64
1218 if isinstance(value, Int64):
1224 if isinstance(value, Int64):
1219 return int(value)
1225 return int(value)
1220 self.error(obj, value)
1226 self.error(obj, value)
1221
1227
1222
1228
1223 class Float(TraitType):
1229 class Float(TraitType):
1224 """A float trait."""
1230 """A float trait."""
1225
1231
1226 default_value = 0.0
1232 default_value = 0.0
1227 info_text = 'a float'
1233 info_text = 'a float'
1228
1234
1229 def validate(self, obj, value):
1235 def validate(self, obj, value):
1230 if isinstance(value, float):
1236 if isinstance(value, float):
1231 return value
1237 return value
1232 if isinstance(value, int):
1238 if isinstance(value, int):
1233 return float(value)
1239 return float(value)
1234 self.error(obj, value)
1240 self.error(obj, value)
1235
1241
1236
1242
1237 class CFloat(Float):
1243 class CFloat(Float):
1238 """A casting version of the float trait."""
1244 """A casting version of the float trait."""
1239
1245
1240 def validate(self, obj, value):
1246 def validate(self, obj, value):
1241 try:
1247 try:
1242 return float(value)
1248 return float(value)
1243 except:
1249 except:
1244 self.error(obj, value)
1250 self.error(obj, value)
1245
1251
1246 class Complex(TraitType):
1252 class Complex(TraitType):
1247 """A trait for complex numbers."""
1253 """A trait for complex numbers."""
1248
1254
1249 default_value = 0.0 + 0.0j
1255 default_value = 0.0 + 0.0j
1250 info_text = 'a complex number'
1256 info_text = 'a complex number'
1251
1257
1252 def validate(self, obj, value):
1258 def validate(self, obj, value):
1253 if isinstance(value, complex):
1259 if isinstance(value, complex):
1254 return value
1260 return value
1255 if isinstance(value, (float, int)):
1261 if isinstance(value, (float, int)):
1256 return complex(value)
1262 return complex(value)
1257 self.error(obj, value)
1263 self.error(obj, value)
1258
1264
1259
1265
1260 class CComplex(Complex):
1266 class CComplex(Complex):
1261 """A casting version of the complex number trait."""
1267 """A casting version of the complex number trait."""
1262
1268
1263 def validate (self, obj, value):
1269 def validate (self, obj, value):
1264 try:
1270 try:
1265 return complex(value)
1271 return complex(value)
1266 except:
1272 except:
1267 self.error(obj, value)
1273 self.error(obj, value)
1268
1274
1269 # We should always be explicit about whether we're using bytes or unicode, both
1275 # We should always be explicit about whether we're using bytes or unicode, both
1270 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1276 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1271 # we don't have a Str type.
1277 # we don't have a Str type.
1272 class Bytes(TraitType):
1278 class Bytes(TraitType):
1273 """A trait for byte strings."""
1279 """A trait for byte strings."""
1274
1280
1275 default_value = b''
1281 default_value = b''
1276 info_text = 'a bytes object'
1282 info_text = 'a bytes object'
1277
1283
1278 def validate(self, obj, value):
1284 def validate(self, obj, value):
1279 if isinstance(value, bytes):
1285 if isinstance(value, bytes):
1280 return value
1286 return value
1281 self.error(obj, value)
1287 self.error(obj, value)
1282
1288
1283
1289
1284 class CBytes(Bytes):
1290 class CBytes(Bytes):
1285 """A casting version of the byte string trait."""
1291 """A casting version of the byte string trait."""
1286
1292
1287 def validate(self, obj, value):
1293 def validate(self, obj, value):
1288 try:
1294 try:
1289 return bytes(value)
1295 return bytes(value)
1290 except:
1296 except:
1291 self.error(obj, value)
1297 self.error(obj, value)
1292
1298
1293
1299
1294 class Unicode(TraitType):
1300 class Unicode(TraitType):
1295 """A trait for unicode strings."""
1301 """A trait for unicode strings."""
1296
1302
1297 default_value = u''
1303 default_value = u''
1298 info_text = 'a unicode string'
1304 info_text = 'a unicode string'
1299
1305
1300 def validate(self, obj, value):
1306 def validate(self, obj, value):
1301 if isinstance(value, py3compat.unicode_type):
1307 if isinstance(value, py3compat.unicode_type):
1302 return value
1308 return value
1303 if isinstance(value, bytes):
1309 if isinstance(value, bytes):
1304 try:
1310 try:
1305 return value.decode('ascii', 'strict')
1311 return value.decode('ascii', 'strict')
1306 except UnicodeDecodeError:
1312 except UnicodeDecodeError:
1307 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1313 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1308 raise TraitError(msg.format(value, self.name, class_of(obj)))
1314 raise TraitError(msg.format(value, self.name, class_of(obj)))
1309 self.error(obj, value)
1315 self.error(obj, value)
1310
1316
1311
1317
1312 class CUnicode(Unicode):
1318 class CUnicode(Unicode):
1313 """A casting version of the unicode trait."""
1319 """A casting version of the unicode trait."""
1314
1320
1315 def validate(self, obj, value):
1321 def validate(self, obj, value):
1316 try:
1322 try:
1317 return py3compat.unicode_type(value)
1323 return py3compat.unicode_type(value)
1318 except:
1324 except:
1319 self.error(obj, value)
1325 self.error(obj, value)
1320
1326
1321
1327
1322 class ObjectName(TraitType):
1328 class ObjectName(TraitType):
1323 """A string holding a valid object name in this version of Python.
1329 """A string holding a valid object name in this version of Python.
1324
1330
1325 This does not check that the name exists in any scope."""
1331 This does not check that the name exists in any scope."""
1326 info_text = "a valid object identifier in Python"
1332 info_text = "a valid object identifier in Python"
1327
1333
1328 if py3compat.PY3:
1334 if py3compat.PY3:
1329 # Python 3:
1335 # Python 3:
1330 coerce_str = staticmethod(lambda _,s: s)
1336 coerce_str = staticmethod(lambda _,s: s)
1331
1337
1332 else:
1338 else:
1333 # Python 2:
1339 # Python 2:
1334 def coerce_str(self, obj, value):
1340 def coerce_str(self, obj, value):
1335 "In Python 2, coerce ascii-only unicode to str"
1341 "In Python 2, coerce ascii-only unicode to str"
1336 if isinstance(value, unicode):
1342 if isinstance(value, unicode):
1337 try:
1343 try:
1338 return str(value)
1344 return str(value)
1339 except UnicodeEncodeError:
1345 except UnicodeEncodeError:
1340 self.error(obj, value)
1346 self.error(obj, value)
1341 return value
1347 return value
1342
1348
1343 def validate(self, obj, value):
1349 def validate(self, obj, value):
1344 value = self.coerce_str(obj, value)
1350 value = self.coerce_str(obj, value)
1345
1351
1346 if isinstance(value, string_types) and py3compat.isidentifier(value):
1352 if isinstance(value, string_types) and py3compat.isidentifier(value):
1347 return value
1353 return value
1348 self.error(obj, value)
1354 self.error(obj, value)
1349
1355
1350 class DottedObjectName(ObjectName):
1356 class DottedObjectName(ObjectName):
1351 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1357 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1352 def validate(self, obj, value):
1358 def validate(self, obj, value):
1353 value = self.coerce_str(obj, value)
1359 value = self.coerce_str(obj, value)
1354
1360
1355 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1361 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1356 return value
1362 return value
1357 self.error(obj, value)
1363 self.error(obj, value)
1358
1364
1359
1365
1360 class Bool(TraitType):
1366 class Bool(TraitType):
1361 """A boolean (True, False) trait."""
1367 """A boolean (True, False) trait."""
1362
1368
1363 default_value = False
1369 default_value = False
1364 info_text = 'a boolean'
1370 info_text = 'a boolean'
1365
1371
1366 def validate(self, obj, value):
1372 def validate(self, obj, value):
1367 if isinstance(value, bool):
1373 if isinstance(value, bool):
1368 return value
1374 return value
1369 self.error(obj, value)
1375 self.error(obj, value)
1370
1376
1371
1377
1372 class CBool(Bool):
1378 class CBool(Bool):
1373 """A casting version of the boolean trait."""
1379 """A casting version of the boolean trait."""
1374
1380
1375 def validate(self, obj, value):
1381 def validate(self, obj, value):
1376 try:
1382 try:
1377 return bool(value)
1383 return bool(value)
1378 except:
1384 except:
1379 self.error(obj, value)
1385 self.error(obj, value)
1380
1386
1381
1387
1382 class Enum(TraitType):
1388 class Enum(TraitType):
1383 """An enum that whose value must be in a given sequence."""
1389 """An enum that whose value must be in a given sequence."""
1384
1390
1385 def __init__(self, values, default_value=None, **metadata):
1391 def __init__(self, values, default_value=None, **metadata):
1386 self.values = values
1392 self.values = values
1387 super(Enum, self).__init__(default_value, **metadata)
1393 super(Enum, self).__init__(default_value, **metadata)
1388
1394
1389 def validate(self, obj, value):
1395 def validate(self, obj, value):
1390 if value in self.values:
1396 if value in self.values:
1391 return value
1397 return value
1392 self.error(obj, value)
1398 self.error(obj, value)
1393
1399
1394 def info(self):
1400 def info(self):
1395 """ Returns a description of the trait."""
1401 """ Returns a description of the trait."""
1396 result = 'any of ' + repr(self.values)
1402 result = 'any of ' + repr(self.values)
1397 if self.allow_none:
1403 if self.allow_none:
1398 return result + ' or None'
1404 return result + ' or None'
1399 return result
1405 return result
1400
1406
1401 class CaselessStrEnum(Enum):
1407 class CaselessStrEnum(Enum):
1402 """An enum of strings that are caseless in validate."""
1408 """An enum of strings that are caseless in validate."""
1403
1409
1404 def validate(self, obj, value):
1410 def validate(self, obj, value):
1405 if not isinstance(value, py3compat.string_types):
1411 if not isinstance(value, py3compat.string_types):
1406 self.error(obj, value)
1412 self.error(obj, value)
1407
1413
1408 for v in self.values:
1414 for v in self.values:
1409 if v.lower() == value.lower():
1415 if v.lower() == value.lower():
1410 return v
1416 return v
1411 self.error(obj, value)
1417 self.error(obj, value)
1412
1418
1413 class Container(Instance):
1419 class Container(Instance):
1414 """An instance of a container (list, set, etc.)
1420 """An instance of a container (list, set, etc.)
1415
1421
1416 To be subclassed by overriding klass.
1422 To be subclassed by overriding klass.
1417 """
1423 """
1418 klass = None
1424 klass = None
1419 _cast_types = ()
1425 _cast_types = ()
1420 _valid_defaults = SequenceTypes
1426 _valid_defaults = SequenceTypes
1421 _trait = None
1427 _trait = None
1422
1428
1423 def __init__(self, trait=None, default_value=None, allow_none=False,
1429 def __init__(self, trait=None, default_value=None, allow_none=False,
1424 **metadata):
1430 **metadata):
1425 """Create a container trait type from a list, set, or tuple.
1431 """Create a container trait type from a list, set, or tuple.
1426
1432
1427 The default value is created by doing ``List(default_value)``,
1433 The default value is created by doing ``List(default_value)``,
1428 which creates a copy of the ``default_value``.
1434 which creates a copy of the ``default_value``.
1429
1435
1430 ``trait`` can be specified, which restricts the type of elements
1436 ``trait`` can be specified, which restricts the type of elements
1431 in the container to that TraitType.
1437 in the container to that TraitType.
1432
1438
1433 If only one arg is given and it is not a Trait, it is taken as
1439 If only one arg is given and it is not a Trait, it is taken as
1434 ``default_value``:
1440 ``default_value``:
1435
1441
1436 ``c = List([1,2,3])``
1442 ``c = List([1,2,3])``
1437
1443
1438 Parameters
1444 Parameters
1439 ----------
1445 ----------
1440
1446
1441 trait : TraitType [ optional ]
1447 trait : TraitType [ optional ]
1442 the type for restricting the contents of the Container. If unspecified,
1448 the type for restricting the contents of the Container. If unspecified,
1443 types are not checked.
1449 types are not checked.
1444
1450
1445 default_value : SequenceType [ optional ]
1451 default_value : SequenceType [ optional ]
1446 The default value for the Trait. Must be list/tuple/set, and
1452 The default value for the Trait. Must be list/tuple/set, and
1447 will be cast to the container type.
1453 will be cast to the container type.
1448
1454
1449 allow_none : bool [ default False ]
1455 allow_none : bool [ default False ]
1450 Whether to allow the value to be None
1456 Whether to allow the value to be None
1451
1457
1452 **metadata : any
1458 **metadata : any
1453 further keys for extensions to the Trait (e.g. config)
1459 further keys for extensions to the Trait (e.g. config)
1454
1460
1455 """
1461 """
1456 # allow List([values]):
1462 # allow List([values]):
1457 if default_value is None and not is_trait(trait):
1463 if default_value is None and not is_trait(trait):
1458 default_value = trait
1464 default_value = trait
1459 trait = None
1465 trait = None
1460
1466
1461 if default_value is None:
1467 if default_value is None:
1462 args = ()
1468 args = ()
1463 elif isinstance(default_value, self._valid_defaults):
1469 elif isinstance(default_value, self._valid_defaults):
1464 args = (default_value,)
1470 args = (default_value,)
1465 else:
1471 else:
1466 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1472 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1467
1473
1468 if is_trait(trait):
1474 if is_trait(trait):
1469 self._trait = trait() if isinstance(trait, type) else trait
1475 self._trait = trait() if isinstance(trait, type) else trait
1470 self._trait.name = 'element'
1476 self._trait.name = 'element'
1471 elif trait is not None:
1477 elif trait is not None:
1472 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1478 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1473
1479
1474 super(Container,self).__init__(klass=self.klass, args=args,
1480 super(Container,self).__init__(klass=self.klass, args=args,
1475 allow_none=allow_none, **metadata)
1481 allow_none=allow_none, **metadata)
1476
1482
1477 def element_error(self, obj, element, validator):
1483 def element_error(self, obj, element, validator):
1478 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1484 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1479 % (self.name, class_of(obj), validator.info(), repr_type(element))
1485 % (self.name, class_of(obj), validator.info(), repr_type(element))
1480 raise TraitError(e)
1486 raise TraitError(e)
1481
1487
1482 def validate(self, obj, value):
1488 def validate(self, obj, value):
1483 if isinstance(value, self._cast_types):
1489 if isinstance(value, self._cast_types):
1484 value = self.klass(value)
1490 value = self.klass(value)
1485 value = super(Container, self).validate(obj, value)
1491 value = super(Container, self).validate(obj, value)
1486 if value is None:
1492 if value is None:
1487 return value
1493 return value
1488
1494
1489 value = self.validate_elements(obj, value)
1495 value = self.validate_elements(obj, value)
1490
1496
1491 return value
1497 return value
1492
1498
1493 def validate_elements(self, obj, value):
1499 def validate_elements(self, obj, value):
1494 validated = []
1500 validated = []
1495 if self._trait is None or isinstance(self._trait, Any):
1501 if self._trait is None or isinstance(self._trait, Any):
1496 return value
1502 return value
1497 for v in value:
1503 for v in value:
1498 try:
1504 try:
1499 v = self._trait._validate(obj, v)
1505 v = self._trait._validate(obj, v)
1500 except TraitError:
1506 except TraitError:
1501 self.element_error(obj, v, self._trait)
1507 self.element_error(obj, v, self._trait)
1502 else:
1508 else:
1503 validated.append(v)
1509 validated.append(v)
1504 return self.klass(validated)
1510 return self.klass(validated)
1505
1511
1506 def instance_init(self):
1512 def instance_init(self):
1507 if isinstance(self._trait, TraitType):
1513 if isinstance(self._trait, TraitType):
1508 self._trait.this_class = self.this_class
1514 self._trait.this_class = self.this_class
1509 self._trait.instance_init()
1515 self._trait.instance_init()
1510 super(Container, self).instance_init()
1516 super(Container, self).instance_init()
1511
1517
1512
1518
1513 class List(Container):
1519 class List(Container):
1514 """An instance of a Python list."""
1520 """An instance of a Python list."""
1515 klass = list
1521 klass = list
1516 _cast_types = (tuple,)
1522 _cast_types = (tuple,)
1517
1523
1518 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1524 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1519 """Create a List trait type from a list, set, or tuple.
1525 """Create a List trait type from a list, set, or tuple.
1520
1526
1521 The default value is created by doing ``List(default_value)``,
1527 The default value is created by doing ``List(default_value)``,
1522 which creates a copy of the ``default_value``.
1528 which creates a copy of the ``default_value``.
1523
1529
1524 ``trait`` can be specified, which restricts the type of elements
1530 ``trait`` can be specified, which restricts the type of elements
1525 in the container to that TraitType.
1531 in the container to that TraitType.
1526
1532
1527 If only one arg is given and it is not a Trait, it is taken as
1533 If only one arg is given and it is not a Trait, it is taken as
1528 ``default_value``:
1534 ``default_value``:
1529
1535
1530 ``c = List([1,2,3])``
1536 ``c = List([1,2,3])``
1531
1537
1532 Parameters
1538 Parameters
1533 ----------
1539 ----------
1534
1540
1535 trait : TraitType [ optional ]
1541 trait : TraitType [ optional ]
1536 the type for restricting the contents of the Container. If unspecified,
1542 the type for restricting the contents of the Container. If unspecified,
1537 types are not checked.
1543 types are not checked.
1538
1544
1539 default_value : SequenceType [ optional ]
1545 default_value : SequenceType [ optional ]
1540 The default value for the Trait. Must be list/tuple/set, and
1546 The default value for the Trait. Must be list/tuple/set, and
1541 will be cast to the container type.
1547 will be cast to the container type.
1542
1548
1543 minlen : Int [ default 0 ]
1549 minlen : Int [ default 0 ]
1544 The minimum length of the input list
1550 The minimum length of the input list
1545
1551
1546 maxlen : Int [ default sys.maxsize ]
1552 maxlen : Int [ default sys.maxsize ]
1547 The maximum length of the input list
1553 The maximum length of the input list
1548
1554
1549 allow_none : bool [ default False ]
1555 allow_none : bool [ default False ]
1550 Whether to allow the value to be None
1556 Whether to allow the value to be None
1551
1557
1552 **metadata : any
1558 **metadata : any
1553 further keys for extensions to the Trait (e.g. config)
1559 further keys for extensions to the Trait (e.g. config)
1554
1560
1555 """
1561 """
1556 self._minlen = minlen
1562 self._minlen = minlen
1557 self._maxlen = maxlen
1563 self._maxlen = maxlen
1558 super(List, self).__init__(trait=trait, default_value=default_value,
1564 super(List, self).__init__(trait=trait, default_value=default_value,
1559 **metadata)
1565 **metadata)
1560
1566
1561 def length_error(self, obj, value):
1567 def length_error(self, obj, value):
1562 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1568 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1563 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1569 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1564 raise TraitError(e)
1570 raise TraitError(e)
1565
1571
1566 def validate_elements(self, obj, value):
1572 def validate_elements(self, obj, value):
1567 length = len(value)
1573 length = len(value)
1568 if length < self._minlen or length > self._maxlen:
1574 if length < self._minlen or length > self._maxlen:
1569 self.length_error(obj, value)
1575 self.length_error(obj, value)
1570
1576
1571 return super(List, self).validate_elements(obj, value)
1577 return super(List, self).validate_elements(obj, value)
1572
1578
1573 def validate(self, obj, value):
1579 def validate(self, obj, value):
1574 value = super(List, self).validate(obj, value)
1580 value = super(List, self).validate(obj, value)
1575 value = self.validate_elements(obj, value)
1581 value = self.validate_elements(obj, value)
1576 return value
1582 return value
1577
1583
1578
1584
1579 class Set(List):
1585 class Set(List):
1580 """An instance of a Python set."""
1586 """An instance of a Python set."""
1581 klass = set
1587 klass = set
1582 _cast_types = (tuple, list)
1588 _cast_types = (tuple, list)
1583
1589
1584
1590
1585 class Tuple(Container):
1591 class Tuple(Container):
1586 """An instance of a Python tuple."""
1592 """An instance of a Python tuple."""
1587 klass = tuple
1593 klass = tuple
1588 _cast_types = (list,)
1594 _cast_types = (list,)
1589
1595
1590 def __init__(self, *traits, **metadata):
1596 def __init__(self, *traits, **metadata):
1591 """Tuple(*traits, default_value=None, **medatata)
1597 """Tuple(*traits, default_value=None, **medatata)
1592
1598
1593 Create a tuple from a list, set, or tuple.
1599 Create a tuple from a list, set, or tuple.
1594
1600
1595 Create a fixed-type tuple with Traits:
1601 Create a fixed-type tuple with Traits:
1596
1602
1597 ``t = Tuple(Int, Str, CStr)``
1603 ``t = Tuple(Int, Str, CStr)``
1598
1604
1599 would be length 3, with Int,Str,CStr for each element.
1605 would be length 3, with Int,Str,CStr for each element.
1600
1606
1601 If only one arg is given and it is not a Trait, it is taken as
1607 If only one arg is given and it is not a Trait, it is taken as
1602 default_value:
1608 default_value:
1603
1609
1604 ``t = Tuple((1,2,3))``
1610 ``t = Tuple((1,2,3))``
1605
1611
1606 Otherwise, ``default_value`` *must* be specified by keyword.
1612 Otherwise, ``default_value`` *must* be specified by keyword.
1607
1613
1608 Parameters
1614 Parameters
1609 ----------
1615 ----------
1610
1616
1611 *traits : TraitTypes [ optional ]
1617 *traits : TraitTypes [ optional ]
1612 the types for restricting the contents of the Tuple. If unspecified,
1618 the types for restricting the contents of the Tuple. If unspecified,
1613 types are not checked. If specified, then each positional argument
1619 types are not checked. If specified, then each positional argument
1614 corresponds to an element of the tuple. Tuples defined with traits
1620 corresponds to an element of the tuple. Tuples defined with traits
1615 are of fixed length.
1621 are of fixed length.
1616
1622
1617 default_value : SequenceType [ optional ]
1623 default_value : SequenceType [ optional ]
1618 The default value for the Tuple. Must be list/tuple/set, and
1624 The default value for the Tuple. Must be list/tuple/set, and
1619 will be cast to a tuple. If `traits` are specified, the
1625 will be cast to a tuple. If `traits` are specified, the
1620 `default_value` must conform to the shape and type they specify.
1626 `default_value` must conform to the shape and type they specify.
1621
1627
1622 allow_none : bool [ default False ]
1628 allow_none : bool [ default False ]
1623 Whether to allow the value to be None
1629 Whether to allow the value to be None
1624
1630
1625 **metadata : any
1631 **metadata : any
1626 further keys for extensions to the Trait (e.g. config)
1632 further keys for extensions to the Trait (e.g. config)
1627
1633
1628 """
1634 """
1629 default_value = metadata.pop('default_value', None)
1635 default_value = metadata.pop('default_value', None)
1630 allow_none = metadata.pop('allow_none', True)
1636 allow_none = metadata.pop('allow_none', True)
1631
1637
1632 # allow Tuple((values,)):
1638 # allow Tuple((values,)):
1633 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1639 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1634 default_value = traits[0]
1640 default_value = traits[0]
1635 traits = ()
1641 traits = ()
1636
1642
1637 if default_value is None:
1643 if default_value is None:
1638 args = ()
1644 args = ()
1639 elif isinstance(default_value, self._valid_defaults):
1645 elif isinstance(default_value, self._valid_defaults):
1640 args = (default_value,)
1646 args = (default_value,)
1641 else:
1647 else:
1642 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1648 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1643
1649
1644 self._traits = []
1650 self._traits = []
1645 for trait in traits:
1651 for trait in traits:
1646 t = trait() if isinstance(trait, type) else trait
1652 t = trait() if isinstance(trait, type) else trait
1647 t.name = 'element'
1653 t.name = 'element'
1648 self._traits.append(t)
1654 self._traits.append(t)
1649
1655
1650 if self._traits and default_value is None:
1656 if self._traits and default_value is None:
1651 # don't allow default to be an empty container if length is specified
1657 # don't allow default to be an empty container if length is specified
1652 args = None
1658 args = None
1653 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1659 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1654
1660
1655 def validate_elements(self, obj, value):
1661 def validate_elements(self, obj, value):
1656 if not self._traits:
1662 if not self._traits:
1657 # nothing to validate
1663 # nothing to validate
1658 return value
1664 return value
1659 if len(value) != len(self._traits):
1665 if len(value) != len(self._traits):
1660 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1666 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1661 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1667 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1662 raise TraitError(e)
1668 raise TraitError(e)
1663
1669
1664 validated = []
1670 validated = []
1665 for t, v in zip(self._traits, value):
1671 for t, v in zip(self._traits, value):
1666 try:
1672 try:
1667 v = t._validate(obj, v)
1673 v = t._validate(obj, v)
1668 except TraitError:
1674 except TraitError:
1669 self.element_error(obj, v, t)
1675 self.element_error(obj, v, t)
1670 else:
1676 else:
1671 validated.append(v)
1677 validated.append(v)
1672 return tuple(validated)
1678 return tuple(validated)
1673
1679
1674 def instance_init(self):
1680 def instance_init(self):
1675 for trait in self._traits:
1681 for trait in self._traits:
1676 if isinstance(trait, TraitType):
1682 if isinstance(trait, TraitType):
1677 trait.this_class = self.this_class
1683 trait.this_class = self.this_class
1678 trait.instance_init()
1684 trait.instance_init()
1679 super(Container, self).instance_init()
1685 super(Container, self).instance_init()
1680
1686
1681
1687
1682 class Dict(Instance):
1688 class Dict(Instance):
1683 """An instance of a Python dict."""
1689 """An instance of a Python dict."""
1684 _trait = None
1690 _trait = None
1685
1691
1686 def __init__(self, trait=None, default_value=NoDefaultSpecified, allow_none=False, **metadata):
1692 def __init__(self, trait=None, default_value=NoDefaultSpecified, allow_none=False, **metadata):
1687 """Create a dict trait type from a dict.
1693 """Create a dict trait type from a dict.
1688
1694
1689 The default value is created by doing ``dict(default_value)``,
1695 The default value is created by doing ``dict(default_value)``,
1690 which creates a copy of the ``default_value``.
1696 which creates a copy of the ``default_value``.
1691
1697
1692 trait : TraitType [ optional ]
1698 trait : TraitType [ optional ]
1693 the type for restricting the contents of the Container. If unspecified,
1699 the type for restricting the contents of the Container. If unspecified,
1694 types are not checked.
1700 types are not checked.
1695
1701
1696 default_value : SequenceType [ optional ]
1702 default_value : SequenceType [ optional ]
1697 The default value for the Dict. Must be dict, tuple, or None, and
1703 The default value for the Dict. Must be dict, tuple, or None, and
1698 will be cast to a dict if not None. If `trait` is specified, the
1704 will be cast to a dict if not None. If `trait` is specified, the
1699 `default_value` must conform to the constraints it specifies.
1705 `default_value` must conform to the constraints it specifies.
1700
1706
1701 allow_none : bool [ default False ]
1707 allow_none : bool [ default False ]
1702 Whether to allow the value to be None
1708 Whether to allow the value to be None
1703
1709
1704 """
1710 """
1705 if default_value is NoDefaultSpecified and trait is not None:
1711 if default_value is NoDefaultSpecified and trait is not None:
1706 if not is_trait(trait):
1712 if not is_trait(trait):
1707 default_value = trait
1713 default_value = trait
1708 trait = None
1714 trait = None
1709 if default_value is NoDefaultSpecified:
1715 if default_value is NoDefaultSpecified:
1710 default_value = {}
1716 default_value = {}
1711 if default_value is None:
1717 if default_value is None:
1712 args = None
1718 args = None
1713 elif isinstance(default_value, dict):
1719 elif isinstance(default_value, dict):
1714 args = (default_value,)
1720 args = (default_value,)
1715 elif isinstance(default_value, SequenceTypes):
1721 elif isinstance(default_value, SequenceTypes):
1716 args = (default_value,)
1722 args = (default_value,)
1717 else:
1723 else:
1718 raise TypeError('default value of Dict was %s' % default_value)
1724 raise TypeError('default value of Dict was %s' % default_value)
1719
1725
1720 if is_trait(trait):
1726 if is_trait(trait):
1721 self._trait = trait() if isinstance(trait, type) else trait
1727 self._trait = trait() if isinstance(trait, type) else trait
1722 self._trait.name = 'element'
1728 self._trait.name = 'element'
1723 elif trait is not None:
1729 elif trait is not None:
1724 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1730 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1725
1731
1726 super(Dict,self).__init__(klass=dict, args=args,
1732 super(Dict,self).__init__(klass=dict, args=args,
1727 allow_none=allow_none, **metadata)
1733 allow_none=allow_none, **metadata)
1728
1734
1729 def element_error(self, obj, element, validator):
1735 def element_error(self, obj, element, validator):
1730 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1736 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1731 % (self.name, class_of(obj), validator.info(), repr_type(element))
1737 % (self.name, class_of(obj), validator.info(), repr_type(element))
1732 raise TraitError(e)
1738 raise TraitError(e)
1733
1739
1734 def validate(self, obj, value):
1740 def validate(self, obj, value):
1735 value = super(Dict, self).validate(obj, value)
1741 value = super(Dict, self).validate(obj, value)
1736 if value is None:
1742 if value is None:
1737 return value
1743 return value
1738 value = self.validate_elements(obj, value)
1744 value = self.validate_elements(obj, value)
1739 return value
1745 return value
1740
1746
1741 def validate_elements(self, obj, value):
1747 def validate_elements(self, obj, value):
1742 if self._trait is None or isinstance(self._trait, Any):
1748 if self._trait is None or isinstance(self._trait, Any):
1743 return value
1749 return value
1744 validated = {}
1750 validated = {}
1745 for key in value:
1751 for key in value:
1746 v = value[key]
1752 v = value[key]
1747 try:
1753 try:
1748 v = self._trait._validate(obj, v)
1754 v = self._trait._validate(obj, v)
1749 except TraitError:
1755 except TraitError:
1750 self.element_error(obj, v, self._trait)
1756 self.element_error(obj, v, self._trait)
1751 else:
1757 else:
1752 validated[key] = v
1758 validated[key] = v
1753 return self.klass(validated)
1759 return self.klass(validated)
1754
1760
1755 def instance_init(self):
1761 def instance_init(self):
1756 if isinstance(self._trait, TraitType):
1762 if isinstance(self._trait, TraitType):
1757 self._trait.this_class = self.this_class
1763 self._trait.this_class = self.this_class
1758 self._trait.instance_init()
1764 self._trait.instance_init()
1759 super(Dict, self).instance_init()
1765 super(Dict, self).instance_init()
1760
1766
1761
1767
1762 class EventfulDict(Instance):
1768 class EventfulDict(Instance):
1763 """An instance of an EventfulDict."""
1769 """An instance of an EventfulDict."""
1764
1770
1765 def __init__(self, default_value={}, allow_none=False, **metadata):
1771 def __init__(self, default_value={}, allow_none=False, **metadata):
1766 """Create a EventfulDict trait type from a dict.
1772 """Create a EventfulDict trait type from a dict.
1767
1773
1768 The default value is created by doing
1774 The default value is created by doing
1769 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1775 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1770 ``default_value``.
1776 ``default_value``.
1771 """
1777 """
1772 if default_value is None:
1778 if default_value is None:
1773 args = None
1779 args = None
1774 elif isinstance(default_value, dict):
1780 elif isinstance(default_value, dict):
1775 args = (default_value,)
1781 args = (default_value,)
1776 elif isinstance(default_value, SequenceTypes):
1782 elif isinstance(default_value, SequenceTypes):
1777 args = (default_value,)
1783 args = (default_value,)
1778 else:
1784 else:
1779 raise TypeError('default value of EventfulDict was %s' % default_value)
1785 raise TypeError('default value of EventfulDict was %s' % default_value)
1780
1786
1781 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1787 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1782 allow_none=allow_none, **metadata)
1788 allow_none=allow_none, **metadata)
1783
1789
1784
1790
1785 class EventfulList(Instance):
1791 class EventfulList(Instance):
1786 """An instance of an EventfulList."""
1792 """An instance of an EventfulList."""
1787
1793
1788 def __init__(self, default_value=None, allow_none=False, **metadata):
1794 def __init__(self, default_value=None, allow_none=False, **metadata):
1789 """Create a EventfulList trait type from a dict.
1795 """Create a EventfulList trait type from a dict.
1790
1796
1791 The default value is created by doing
1797 The default value is created by doing
1792 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1798 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1793 ``default_value``.
1799 ``default_value``.
1794 """
1800 """
1795 if default_value is None:
1801 if default_value is None:
1796 args = ((),)
1802 args = ((),)
1797 else:
1803 else:
1798 args = (default_value,)
1804 args = (default_value,)
1799
1805
1800 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1806 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1801 allow_none=allow_none, **metadata)
1807 allow_none=allow_none, **metadata)
1802
1808
1803
1809
1804 class TCPAddress(TraitType):
1810 class TCPAddress(TraitType):
1805 """A trait for an (ip, port) tuple.
1811 """A trait for an (ip, port) tuple.
1806
1812
1807 This allows for both IPv4 IP addresses as well as hostnames.
1813 This allows for both IPv4 IP addresses as well as hostnames.
1808 """
1814 """
1809
1815
1810 default_value = ('127.0.0.1', 0)
1816 default_value = ('127.0.0.1', 0)
1811 info_text = 'an (ip, port) tuple'
1817 info_text = 'an (ip, port) tuple'
1812
1818
1813 def validate(self, obj, value):
1819 def validate(self, obj, value):
1814 if isinstance(value, tuple):
1820 if isinstance(value, tuple):
1815 if len(value) == 2:
1821 if len(value) == 2:
1816 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1822 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1817 port = value[1]
1823 port = value[1]
1818 if port >= 0 and port <= 65535:
1824 if port >= 0 and port <= 65535:
1819 return value
1825 return value
1820 self.error(obj, value)
1826 self.error(obj, value)
1821
1827
1822 class CRegExp(TraitType):
1828 class CRegExp(TraitType):
1823 """A casting compiled regular expression trait.
1829 """A casting compiled regular expression trait.
1824
1830
1825 Accepts both strings and compiled regular expressions. The resulting
1831 Accepts both strings and compiled regular expressions. The resulting
1826 attribute will be a compiled regular expression."""
1832 attribute will be a compiled regular expression."""
1827
1833
1828 info_text = 'a regular expression'
1834 info_text = 'a regular expression'
1829
1835
1830 def validate(self, obj, value):
1836 def validate(self, obj, value):
1831 try:
1837 try:
1832 return re.compile(value)
1838 return re.compile(value)
1833 except:
1839 except:
1834 self.error(obj, value)
1840 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now