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