##// END OF EJS Templates
minor fixes on custom serialization, and traitlet hold_traits
Sylvain Corlay -
Show More
@@ -1,396 +1,396 b''
1 1 # encoding: utf-8
2 2 """
3 3 An application for IPython.
4 4
5 5 All top-level applications should use the classes in this module for
6 6 handling configuration and creating configurables.
7 7
8 8 The job of an :class:`Application` is to create the master configuration
9 9 object and then create the configurable objects, passing the config to them.
10 10 """
11 11
12 12 # Copyright (c) IPython Development Team.
13 13 # Distributed under the terms of the Modified BSD License.
14 14
15 15 import atexit
16 16 import glob
17 17 import logging
18 18 import os
19 19 import shutil
20 20 import sys
21 21
22 22 from IPython.config.application import Application, catch_config_error
23 23 from IPython.config.loader import ConfigFileNotFound, PyFileConfigLoader
24 24 from IPython.core import release, crashhandler
25 25 from IPython.core.profiledir import ProfileDir, ProfileDirError
26 26 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir, ensure_dir_exists
27 27 from IPython.utils import py3compat
28 28 from IPython.utils.traitlets import List, Unicode, Type, Bool, Dict, Set, Instance, Undefined
29 29
30 30 if os.name == 'nt':
31 31 programdata = os.environ.get('PROGRAMDATA', None)
32 32 if programdata:
33 33 SYSTEM_CONFIG_DIRS = [os.path.join(programdata, 'ipython')]
34 34 else: # PROGRAMDATA is not defined by default on XP.
35 35 SYSTEM_CONFIG_DIRS = []
36 36 else:
37 37 SYSTEM_CONFIG_DIRS = [
38 38 "/usr/local/etc/ipython",
39 39 "/etc/ipython",
40 40 ]
41 41
42 42
43 43 # aliases and flags
44 44
45 45 base_aliases = {
46 46 'profile-dir' : 'ProfileDir.location',
47 47 'profile' : 'BaseIPythonApplication.profile',
48 48 'ipython-dir' : 'BaseIPythonApplication.ipython_dir',
49 49 'log-level' : 'Application.log_level',
50 50 'config' : 'BaseIPythonApplication.extra_config_file',
51 51 }
52 52
53 53 base_flags = dict(
54 54 debug = ({'Application' : {'log_level' : logging.DEBUG}},
55 55 "set log level to logging.DEBUG (maximize logging output)"),
56 56 quiet = ({'Application' : {'log_level' : logging.CRITICAL}},
57 57 "set log level to logging.CRITICAL (minimize logging output)"),
58 58 init = ({'BaseIPythonApplication' : {
59 59 'copy_config_files' : True,
60 60 'auto_create' : True}
61 61 }, """Initialize profile with default config files. This is equivalent
62 62 to running `ipython profile create <profile>` prior to startup.
63 63 """)
64 64 )
65 65
66 66 class ProfileAwareConfigLoader(PyFileConfigLoader):
67 67 """A Python file config loader that is aware of IPython profiles."""
68 68 def load_subconfig(self, fname, path=None, profile=None):
69 69 if profile is not None:
70 70 try:
71 71 profile_dir = ProfileDir.find_profile_dir_by_name(
72 72 get_ipython_dir(),
73 73 profile,
74 74 )
75 75 except ProfileDirError:
76 76 return
77 77 path = profile_dir.location
78 78 return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
79 79
80 80 class BaseIPythonApplication(Application):
81 81
82 82 name = Unicode(u'ipython')
83 83 description = Unicode(u'IPython: an enhanced interactive Python shell.')
84 84 version = Unicode(release.version)
85 85
86 86 aliases = Dict(base_aliases)
87 87 flags = Dict(base_flags)
88 88 classes = List([ProfileDir])
89 89
90 90 # enable `load_subconfig('cfg.py', profile='name')`
91 91 python_config_loader_class = ProfileAwareConfigLoader
92 92
93 93 # Track whether the config_file has changed,
94 94 # because some logic happens only if we aren't using the default.
95 95 config_file_specified = Set()
96 96
97 97 config_file_name = Unicode()
98 98 def _config_file_name_default(self):
99 99 return self.name.replace('-','_') + u'_config.py'
100 100 def _config_file_name_changed(self, name, old, new):
101 101 if new != old:
102 102 self.config_file_specified.add(new)
103 103
104 104 # The directory that contains IPython's builtin profiles.
105 105 builtin_profile_dir = Unicode(
106 106 os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
107 107 )
108 108
109 109 config_file_paths = List(Unicode)
110 110 def _config_file_paths_default(self):
111 111 return [py3compat.getcwd()]
112 112
113 113 extra_config_file = Unicode(config=True,
114 114 help="""Path to an extra config file to load.
115 115
116 116 If specified, load this config file in addition to any other IPython config.
117 117 """)
118 118 def _extra_config_file_changed(self, name, old, new):
119 119 try:
120 120 self.config_files.remove(old)
121 121 except ValueError:
122 122 pass
123 123 self.config_file_specified.add(new)
124 124 self.config_files.append(new)
125 125
126 126 profile = Unicode(u'default', config=True,
127 127 help="""The IPython profile to use."""
128 128 )
129 129
130 130 def _profile_changed(self, name, old, new):
131 131 self.builtin_profile_dir = os.path.join(
132 132 get_ipython_package_dir(), u'config', u'profile', new
133 133 )
134 134
135 135 ipython_dir = Unicode(config=True,
136 136 help="""
137 137 The name of the IPython directory. This directory is used for logging
138 138 configuration (through profiles), history storage, etc. The default
139 139 is usually $HOME/.ipython. This option can also be specified through
140 140 the environment variable IPYTHONDIR.
141 141 """
142 142 )
143 143 def _ipython_dir_default(self):
144 144 d = get_ipython_dir()
145 145 self._ipython_dir_changed('ipython_dir', d, d)
146 146 return d
147 147
148 148 _in_init_profile_dir = False
149 149 profile_dir = Instance(ProfileDir, allow_none=True)
150 150 def _profile_dir_default(self):
151 151 # avoid recursion
152 152 if self._in_init_profile_dir:
153 153 return
154 154 # profile_dir requested early, force initialization
155 155 self.init_profile_dir()
156 156 return self.profile_dir
157 157
158 158 overwrite = Bool(False, config=True,
159 159 help="""Whether to overwrite existing config files when copying""")
160 160 auto_create = Bool(False, config=True,
161 161 help="""Whether to create profile dir if it doesn't exist""")
162 162
163 163 config_files = List(Unicode)
164 164 def _config_files_default(self):
165 165 return [self.config_file_name]
166 166
167 167 copy_config_files = Bool(False, config=True,
168 168 help="""Whether to install the default config files into the profile dir.
169 169 If a new profile is being created, and IPython contains config files for that
170 170 profile, then they will be staged into the new directory. Otherwise,
171 171 default config files will be automatically generated.
172 172 """)
173 173
174 174 verbose_crash = Bool(False, config=True,
175 175 help="""Create a massive crash report when IPython encounters what may be an
176 176 internal error. The default is to append a short message to the
177 177 usual traceback""")
178 178
179 179 # The class to use as the crash handler.
180 180 crash_handler_class = Type(crashhandler.CrashHandler)
181 181
182 182 @catch_config_error
183 183 def __init__(self, **kwargs):
184 184 super(BaseIPythonApplication, self).__init__(**kwargs)
185 185 # ensure current working directory exists
186 186 try:
187 187 directory = py3compat.getcwd()
188 188 except:
189 189 # exit if cwd doesn't exist
190 190 self.log.error("Current working directory doesn't exist.")
191 191 self.exit(1)
192 192
193 193 #-------------------------------------------------------------------------
194 194 # Various stages of Application creation
195 195 #-------------------------------------------------------------------------
196 196
197 197 def init_crash_handler(self):
198 198 """Create a crash handler, typically setting sys.excepthook to it."""
199 199 self.crash_handler = self.crash_handler_class(self)
200 200 sys.excepthook = self.excepthook
201 201 def unset_crashhandler():
202 202 sys.excepthook = sys.__excepthook__
203 203 atexit.register(unset_crashhandler)
204 204
205 205 def excepthook(self, etype, evalue, tb):
206 206 """this is sys.excepthook after init_crashhandler
207 207
208 208 set self.verbose_crash=True to use our full crashhandler, instead of
209 209 a regular traceback with a short message (crash_handler_lite)
210 210 """
211 211
212 212 if self.verbose_crash:
213 213 return self.crash_handler(etype, evalue, tb)
214 214 else:
215 215 return crashhandler.crash_handler_lite(etype, evalue, tb)
216 216
217 217 def _ipython_dir_changed(self, name, old, new):
218 if old is not None and old is not Undefined:
218 if old is not Undefined:
219 219 str_old = py3compat.cast_bytes_py2(os.path.abspath(old),
220 220 sys.getfilesystemencoding()
221 221 )
222 222 if str_old in sys.path:
223 223 sys.path.remove(str_old)
224 224 str_path = py3compat.cast_bytes_py2(os.path.abspath(new),
225 225 sys.getfilesystemencoding()
226 226 )
227 227 sys.path.append(str_path)
228 228 ensure_dir_exists(new)
229 229 readme = os.path.join(new, 'README')
230 230 readme_src = os.path.join(get_ipython_package_dir(), u'config', u'profile', 'README')
231 231 if not os.path.exists(readme) and os.path.exists(readme_src):
232 232 shutil.copy(readme_src, readme)
233 233 for d in ('extensions', 'nbextensions'):
234 234 path = os.path.join(new, d)
235 235 try:
236 236 ensure_dir_exists(path)
237 237 except OSError:
238 238 # this will not be EEXIST
239 239 self.log.error("couldn't create path %s: %s", path, e)
240 240 self.log.debug("IPYTHONDIR set to: %s" % new)
241 241
242 242 def load_config_file(self, suppress_errors=True):
243 243 """Load the config file.
244 244
245 245 By default, errors in loading config are handled, and a warning
246 246 printed on screen. For testing, the suppress_errors option is set
247 247 to False, so errors will make tests fail.
248 248 """
249 249 self.log.debug("Searching path %s for config files", self.config_file_paths)
250 250 base_config = 'ipython_config.py'
251 251 self.log.debug("Attempting to load config file: %s" %
252 252 base_config)
253 253 try:
254 254 Application.load_config_file(
255 255 self,
256 256 base_config,
257 257 path=self.config_file_paths
258 258 )
259 259 except ConfigFileNotFound:
260 260 # ignore errors loading parent
261 261 self.log.debug("Config file %s not found", base_config)
262 262 pass
263 263
264 264 for config_file_name in self.config_files:
265 265 if not config_file_name or config_file_name == base_config:
266 266 continue
267 267 self.log.debug("Attempting to load config file: %s" %
268 268 self.config_file_name)
269 269 try:
270 270 Application.load_config_file(
271 271 self,
272 272 config_file_name,
273 273 path=self.config_file_paths
274 274 )
275 275 except ConfigFileNotFound:
276 276 # Only warn if the default config file was NOT being used.
277 277 if config_file_name in self.config_file_specified:
278 278 msg = self.log.warn
279 279 else:
280 280 msg = self.log.debug
281 281 msg("Config file not found, skipping: %s", config_file_name)
282 282 except:
283 283 # For testing purposes.
284 284 if not suppress_errors:
285 285 raise
286 286 self.log.warn("Error loading config file: %s" %
287 287 self.config_file_name, exc_info=True)
288 288
289 289 def init_profile_dir(self):
290 290 """initialize the profile dir"""
291 291 self._in_init_profile_dir = True
292 292 if self.profile_dir is not None:
293 293 # already ran
294 294 return
295 295 if 'ProfileDir.location' not in self.config:
296 296 # location not specified, find by profile name
297 297 try:
298 298 p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
299 299 except ProfileDirError:
300 300 # not found, maybe create it (always create default profile)
301 301 if self.auto_create or self.profile == 'default':
302 302 try:
303 303 p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
304 304 except ProfileDirError:
305 305 self.log.fatal("Could not create profile: %r"%self.profile)
306 306 self.exit(1)
307 307 else:
308 308 self.log.info("Created profile dir: %r"%p.location)
309 309 else:
310 310 self.log.fatal("Profile %r not found."%self.profile)
311 311 self.exit(1)
312 312 else:
313 313 self.log.debug("Using existing profile dir: %r"%p.location)
314 314 else:
315 315 location = self.config.ProfileDir.location
316 316 # location is fully specified
317 317 try:
318 318 p = ProfileDir.find_profile_dir(location, self.config)
319 319 except ProfileDirError:
320 320 # not found, maybe create it
321 321 if self.auto_create:
322 322 try:
323 323 p = ProfileDir.create_profile_dir(location, self.config)
324 324 except ProfileDirError:
325 325 self.log.fatal("Could not create profile directory: %r"%location)
326 326 self.exit(1)
327 327 else:
328 328 self.log.debug("Creating new profile dir: %r"%location)
329 329 else:
330 330 self.log.fatal("Profile directory %r not found."%location)
331 331 self.exit(1)
332 332 else:
333 333 self.log.info("Using existing profile dir: %r"%location)
334 334 # if profile_dir is specified explicitly, set profile name
335 335 dir_name = os.path.basename(p.location)
336 336 if dir_name.startswith('profile_'):
337 337 self.profile = dir_name[8:]
338 338
339 339 self.profile_dir = p
340 340 self.config_file_paths.append(p.location)
341 341 self._in_init_profile_dir = False
342 342
343 343 def init_config_files(self):
344 344 """[optionally] copy default config files into profile dir."""
345 345 self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
346 346 # copy config files
347 347 path = self.builtin_profile_dir
348 348 if self.copy_config_files:
349 349 src = self.profile
350 350
351 351 cfg = self.config_file_name
352 352 if path and os.path.exists(os.path.join(path, cfg)):
353 353 self.log.warn("Staging %r from %s into %r [overwrite=%s]"%(
354 354 cfg, src, self.profile_dir.location, self.overwrite)
355 355 )
356 356 self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
357 357 else:
358 358 self.stage_default_config_file()
359 359 else:
360 360 # Still stage *bundled* config files, but not generated ones
361 361 # This is necessary for `ipython profile=sympy` to load the profile
362 362 # on the first go
363 363 files = glob.glob(os.path.join(path, '*.py'))
364 364 for fullpath in files:
365 365 cfg = os.path.basename(fullpath)
366 366 if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
367 367 # file was copied
368 368 self.log.warn("Staging bundled %s from %s into %r"%(
369 369 cfg, self.profile, self.profile_dir.location)
370 370 )
371 371
372 372
373 373 def stage_default_config_file(self):
374 374 """auto generate default config file, and stage it into the profile."""
375 375 s = self.generate_config_file()
376 376 fname = os.path.join(self.profile_dir.location, self.config_file_name)
377 377 if self.overwrite or not os.path.exists(fname):
378 378 self.log.warn("Generating default config file: %r"%(fname))
379 379 with open(fname, 'w') as f:
380 380 f.write(s)
381 381
382 382 @catch_config_error
383 383 def initialize(self, argv=None):
384 384 # don't hook up crash handler before parsing command-line
385 385 self.parse_command_line(argv)
386 386 self.init_crash_handler()
387 387 if self.subapp is not None:
388 388 # stop here if subapp is taking over
389 389 return
390 390 cl_config = self.config
391 391 self.init_profile_dir()
392 392 self.init_config_files()
393 393 self.load_config_file()
394 394 # enforce cl-opts override configfile opts:
395 395 self.update_config(cl_config)
396 396
@@ -1,806 +1,806 b''
1 1 // Copyright (c) IPython Development Team.
2 2 // Distributed under the terms of the Modified BSD License.
3 3
4 4 define(["widgets/js/manager",
5 5 "underscore",
6 6 "backbone",
7 7 "jquery",
8 8 "base/js/utils",
9 9 "base/js/namespace",
10 10 ], function(widgetmanager, _, Backbone, $, utils, IPython){
11 11 "use strict";
12 12
13 13 var unpack_models = function unpack_models(value, model) {
14 14 /**
15 15 * Replace model ids with models recursively.
16 16 */
17 17 var unpacked;
18 18 if ($.isArray(value)) {
19 19 unpacked = [];
20 20 _.each(value, function(sub_value, key) {
21 21 unpacked.push(unpack_models(sub_value, model));
22 22 });
23 23 return Promise.all(unpacked);
24 24 } else if (value instanceof Object) {
25 25 unpacked = {};
26 26 _.each(value, function(sub_value, key) {
27 27 unpacked[key] = unpack_models(sub_value, model);
28 28 });
29 29 return utils.resolve_promises_dict(unpacked);
30 30 } else if (typeof value === 'string' && value.slice(0,10) === "IPY_MODEL_") {
31 31 // get_model returns a promise already
32 32 return model.widget_manager.get_model(value.slice(10, value.length));
33 33 } else {
34 34 return Promise.resolve(value);
35 35 }
36 36 };
37 37
38 38 var WidgetModel = Backbone.Model.extend({
39 39 constructor: function (widget_manager, model_id, comm) {
40 40 /**
41 41 * Constructor
42 42 *
43 43 * Creates a WidgetModel instance.
44 44 *
45 45 * Parameters
46 46 * ----------
47 47 * widget_manager : WidgetManager instance
48 48 * model_id : string
49 49 * An ID unique to this model.
50 50 * comm : Comm instance (optional)
51 51 */
52 52 this.widget_manager = widget_manager;
53 53 this.state_change = Promise.resolve();
54 54 this._buffered_state_diff = {};
55 55 this.pending_msgs = 0;
56 56 this.msg_buffer = null;
57 57 this.state_lock = null;
58 58 this.id = model_id;
59 59 this.views = {};
60 60 this._resolve_received_state = {};
61 61
62 62 if (comm !== undefined) {
63 63 // Remember comm associated with the model.
64 64 this.comm = comm;
65 65 comm.model = this;
66 66
67 67 // Hook comm messages up to model.
68 68 comm.on_close($.proxy(this._handle_comm_closed, this));
69 69 comm.on_msg($.proxy(this._handle_comm_msg, this));
70 70
71 71 // Assume the comm is alive.
72 72 this.set_comm_live(true);
73 73 } else {
74 74 this.set_comm_live(false);
75 75 }
76 76
77 77 // Listen for the events that lead to the websocket being terminated.
78 78 var that = this;
79 79 var died = function() {
80 80 that.set_comm_live(false);
81 81 };
82 82 widget_manager.notebook.events.on('kernel_disconnected.Kernel', died);
83 83 widget_manager.notebook.events.on('kernel_killed.Kernel', died);
84 84 widget_manager.notebook.events.on('kernel_restarting.Kernel', died);
85 85 widget_manager.notebook.events.on('kernel_dead.Kernel', died);
86 86
87 87 return Backbone.Model.apply(this);
88 88 },
89 89
90 90 send: function (content, callbacks, buffers) {
91 91 /**
92 92 * Send a custom msg over the comm.
93 93 */
94 94 if (this.comm !== undefined) {
95 95 var data = {method: 'custom', content: content};
96 96 this.comm.send(data, callbacks, {}, buffers);
97 97 this.pending_msgs++;
98 98 }
99 99 },
100 100
101 101 request_state: function(callbacks) {
102 102 /**
103 103 * Request a state push from the back-end.
104 104 */
105 105 if (!this.comm) {
106 106 console.error("Could not request_state because comm doesn't exist!");
107 107 return;
108 108 }
109 109
110 110 var msg_id = this.comm.send({method: 'request_state'}, callbacks || this.widget_manager.callbacks());
111 111
112 112 // Promise that is resolved when a state is received
113 113 // from the back-end.
114 114 var that = this;
115 115 var received_state = new Promise(function(resolve) {
116 116 that._resolve_received_state[msg_id] = resolve;
117 117 });
118 118 return received_state;
119 119 },
120 120
121 121 set_comm_live: function(live) {
122 122 /**
123 123 * Change the comm_live state of the model.
124 124 */
125 125 if (this.comm_live === undefined || this.comm_live != live) {
126 126 this.comm_live = live;
127 127 this.trigger(live ? 'comm:live' : 'comm:dead', {model: this});
128 128 }
129 129 },
130 130
131 131 close: function(comm_closed) {
132 132 /**
133 133 * Close model
134 134 */
135 135 if (this.comm && !comm_closed) {
136 136 this.comm.close();
137 137 }
138 138 this.stopListening();
139 139 this.trigger('destroy', this);
140 140 delete this.comm.model; // Delete ref so GC will collect widget model.
141 141 delete this.comm;
142 142 delete this.model_id; // Delete id from model so widget manager cleans up.
143 143 _.each(this.views, function(v, id, views) {
144 144 v.then(function(view) {
145 145 view.remove();
146 146 delete views[id];
147 147 });
148 148 });
149 149 },
150 150
151 151 _handle_comm_closed: function (msg) {
152 152 /**
153 153 * Handle when a widget is closed.
154 154 */
155 155 this.trigger('comm:close');
156 156 this.close(true);
157 157 },
158 158
159 159 _handle_comm_msg: function (msg) {
160 160 /**
161 161 * Handle incoming comm msg.
162 162 */
163 163 var method = msg.content.data.method;
164 164
165 165 var that = this;
166 166 switch (method) {
167 167 case 'update':
168 168 this.state_change = this.state_change
169 169 .then(function() {
170 170 var state = msg.content.data.state || {};
171 171 var buffer_keys = msg.content.data.buffers || [];
172 172 var buffers = msg.buffers || [];
173 173 for (var i=0; i<buffer_keys.length; i++) {
174 174 state[buffer_keys[i]] = buffers[i];
175 175 }
176 176
177 177 // deserialize fields that have custom deserializers
178 178 var serializers = that.constructor.serializers;
179 179 if (serializers) {
180 180 for (var k in state) {
181 181 if (serializers[k] && serializers[k].deserialize) {
182 182 state[k] = (serializers[k].deserialize)(state[k], that);
183 183 }
184 184 }
185 185 }
186 186 return utils.resolve_promises_dict(state);
187 187 }).then(function(state) {
188 188 return that.set_state(state);
189 189 }).catch(utils.reject("Couldn't process update msg for model id '" + String(that.id) + "'", true))
190 190 .then(function() {
191 191 var parent_id = msg.parent_header.msg_id;
192 192 if (that._resolve_received_state[parent_id] !== undefined) {
193 193 that._resolve_received_state[parent_id].call();
194 194 delete that._resolve_received_state[parent_id];
195 195 }
196 196 }).catch(utils.reject("Couldn't resolve state request promise", true));
197 197 break;
198 198 case 'custom':
199 199 this.trigger('msg:custom', msg.content.data.content, msg.buffers);
200 200 break;
201 201 case 'display':
202 202 this.state_change = this.state_change.then(function() {
203 203 that.widget_manager.display_view(msg, that);
204 204 }).catch(utils.reject('Could not process display view msg', true));
205 205 break;
206 206 }
207 207 },
208 208
209 209 set_state: function (state) {
210 210 var that = this;
211 211 // Handle when a widget is updated via the python side.
212 212 return new Promise(function(resolve, reject) {
213 213 that.state_lock = state;
214 214 try {
215 215 WidgetModel.__super__.set.call(that, state);
216 216 } finally {
217 217 that.state_lock = null;
218 218 }
219 219 resolve();
220 220 }).catch(utils.reject("Couldn't set model state", true));
221 221 },
222 222
223 223 get_state: function() {
224 224 // Get the serializable state of the model.
225 225 // Equivalent to Backbone.Model.toJSON()
226 226 return _.clone(this.attributes);
227 227 },
228 228
229 229 _handle_status: function (msg, callbacks) {
230 230 /**
231 231 * Handle status msgs.
232 232 *
233 233 * execution_state : ('busy', 'idle', 'starting')
234 234 */
235 235 if (this.comm !== undefined) {
236 236 if (msg.content.execution_state ==='idle') {
237 237 // Send buffer if this message caused another message to be
238 238 // throttled.
239 239 if (this.msg_buffer !== null &&
240 240 (this.get('msg_throttle') || 3) === this.pending_msgs) {
241 241 var data = {method: 'backbone', sync_method: 'update', sync_data: this.msg_buffer};
242 242 this.comm.send(data, callbacks);
243 243 this.msg_buffer = null;
244 244 } else {
245 245 --this.pending_msgs;
246 246 }
247 247 }
248 248 }
249 249 },
250 250
251 251 callbacks: function(view) {
252 252 /**
253 253 * Create msg callbacks for a comm msg.
254 254 */
255 255 var callbacks = this.widget_manager.callbacks(view);
256 256
257 257 if (callbacks.iopub === undefined) {
258 258 callbacks.iopub = {};
259 259 }
260 260
261 261 var that = this;
262 262 callbacks.iopub.status = function (msg) {
263 263 that._handle_status(msg, callbacks);
264 264 };
265 265 return callbacks;
266 266 },
267 267
268 268 set: function(key, val, options) {
269 269 /**
270 270 * Set a value.
271 271 */
272 272 var return_value = WidgetModel.__super__.set.apply(this, arguments);
273 273
274 274 // Backbone only remembers the diff of the most recent set()
275 275 // operation. Calling set multiple times in a row results in a
276 276 // loss of diff information. Here we keep our own running diff.
277 277 this._buffered_state_diff = $.extend(this._buffered_state_diff, this.changedAttributes() || {});
278 278 return return_value;
279 279 },
280 280
281 281 sync: function (method, model, options) {
282 282 /**
283 283 * Handle sync to the back-end. Called when a model.save() is called.
284 284 *
285 285 * Make sure a comm exists.
286 286
287 287 * Parameters
288 288 * ----------
289 289 * method : create, update, patch, delete, read
290 290 * create/update always send the full attribute set
291 291 * patch - only send attributes listed in options.attrs, and if we are queuing
292 292 * up messages, combine with previous messages that have not been sent yet
293 293 * model : the model we are syncing
294 294 * will normally be the same as `this`
295 295 * options : dict
296 296 * the `attrs` key, if it exists, gives an {attr: value} dict that should be synced,
297 297 * otherwise, sync all attributes
298 298 *
299 299 */
300 300 var error = options.error || function() {
301 301 console.error('Backbone sync error:', arguments);
302 302 };
303 303 if (this.comm === undefined) {
304 304 error();
305 305 return false;
306 306 }
307 307
308 308 var attrs = (method === 'patch') ? options.attrs : model.get_state(options);
309 309
310 310 // the state_lock lists attributes that are currently be changed right now from a kernel message
311 311 // we don't want to send these non-changes back to the kernel, so we delete them out of attrs
312 312 // (but we only delete them if the value hasn't changed from the value stored in the state_lock
313 313 if (this.state_lock !== null) {
314 314 var keys = Object.keys(this.state_lock);
315 315 for (var i=0; i<keys.length; i++) {
316 316 var key = keys[i];
317 317 if (attrs[key] === this.state_lock[key]) {
318 318 delete attrs[key];
319 319 }
320 320 }
321 321 }
322 322
323 323 if (_.size(attrs) > 0) {
324 324
325 325 // If this message was sent via backbone itself, it will not
326 326 // have any callbacks. It's important that we create callbacks
327 327 // so we can listen for status messages, etc...
328 328 var callbacks = options.callbacks || this.callbacks();
329 329
330 330 // Check throttle.
331 331 if (this.pending_msgs >= (this.get('msg_throttle') || 3)) {
332 332 // The throttle has been exceeded, buffer the current msg so
333 333 // it can be sent once the kernel has finished processing
334 334 // some of the existing messages.
335 335
336 336 // Combine updates if it is a 'patch' sync, otherwise replace updates
337 337 switch (method) {
338 338 case 'patch':
339 339 this.msg_buffer = $.extend(this.msg_buffer || {}, attrs);
340 340 break;
341 341 case 'update':
342 342 case 'create':
343 343 this.msg_buffer = attrs;
344 344 break;
345 345 default:
346 346 error();
347 347 return false;
348 348 }
349 349 this.msg_buffer_callbacks = callbacks;
350 350
351 351 } else {
352 352 // We haven't exceeded the throttle, send the message like
353 353 // normal.
354 354 this.send_sync_message(attrs, callbacks);
355 355 this.pending_msgs++;
356 356 }
357 357 }
358 358 // Since the comm is a one-way communication, assume the message
359 359 // arrived. Don't call success since we don't have a model back from the server
360 360 // this means we miss out on the 'sync' event.
361 361 this._buffered_state_diff = {};
362 362 },
363 363
364 364
365 365 send_sync_message: function(attrs, callbacks) {
366 366 // prepare and send a comm message syncing attrs
367 367 var that = this;
368 368 // first, build a state dictionary with key=the attribute and the value
369 369 // being the value or the promise of the serialized value
370 370 var serializers = this.constructor.serializers;
371 371 if (serializers) {
372 for (k in attrs) {
372 for (var k in attrs) {
373 373 if (serializers[k] && serializers[k].serialize) {
374 374 attrs[k] = (serializers[k].serialize)(attrs[k], this);
375 375 }
376 376 }
377 377 }
378 378 utils.resolve_promises_dict(attrs).then(function(state) {
379 379 // get binary values, then send
380 380 var keys = Object.keys(state);
381 381 var buffers = [];
382 382 var buffer_keys = [];
383 383 for (var i=0; i<keys.length; i++) {
384 384 var key = keys[i];
385 385 var value = state[key];
386 386 if (value.buffer instanceof ArrayBuffer
387 387 || value instanceof ArrayBuffer) {
388 388 buffers.push(value);
389 389 buffer_keys.push(key);
390 390 delete state[key];
391 391 }
392 392 }
393 393 that.comm.send({method: 'backbone', sync_data: state, buffer_keys: buffer_keys}, callbacks, {}, buffers);
394 394 }).catch(function(error) {
395 395 that.pending_msgs--;
396 396 return (utils.reject("Couldn't send widget sync message", true))(error);
397 397 });
398 398 },
399 399
400 400 save_changes: function(callbacks) {
401 401 /**
402 402 * Push this model's state to the back-end
403 403 *
404 404 * This invokes a Backbone.Sync.
405 405 */
406 406 this.save(this._buffered_state_diff, {patch: true, callbacks: callbacks});
407 407 },
408 408
409 409 on_some_change: function(keys, callback, context) {
410 410 /**
411 411 * on_some_change(["key1", "key2"], foo, context) differs from
412 412 * on("change:key1 change:key2", foo, context).
413 413 * If the widget attributes key1 and key2 are both modified,
414 414 * the second form will result in foo being called twice
415 415 * while the first will call foo only once.
416 416 */
417 417 this.on('change', function() {
418 418 if (keys.some(this.hasChanged, this)) {
419 419 callback.apply(context);
420 420 }
421 421 }, this);
422 422
423 423 },
424 424
425 425 toJSON: function(options) {
426 426 /**
427 427 * Serialize the model. See the types.js deserialization function
428 428 * and the kernel-side serializer/deserializer
429 429 */
430 430 return "IPY_MODEL_"+this.id;
431 431 }
432 432 });
433 433 widgetmanager.WidgetManager.register_widget_model('WidgetModel', WidgetModel);
434 434
435 435
436 436 var WidgetView = Backbone.View.extend({
437 437 initialize: function(parameters) {
438 438 /**
439 439 * Public constructor.
440 440 */
441 441 this.model.on('change',this.update,this);
442 442
443 443 // Bubble the comm live events.
444 444 this.model.on('comm:live', function() {
445 445 this.trigger('comm:live', this);
446 446 }, this);
447 447 this.model.on('comm:dead', function() {
448 448 this.trigger('comm:dead', this);
449 449 }, this);
450 450
451 451 this.options = parameters.options;
452 452 this.on('displayed', function() {
453 453 this.is_displayed = true;
454 454 }, this);
455 455 },
456 456
457 457 update: function(){
458 458 /**
459 459 * Triggered on model change.
460 460 *
461 461 * Update view to be consistent with this.model
462 462 */
463 463 },
464 464
465 465 create_child_view: function(child_model, options) {
466 466 /**
467 467 * Create and promise that resolves to a child view of a given model
468 468 */
469 469 var that = this;
470 470 options = $.extend({ parent: this }, options || {});
471 471 return this.model.widget_manager.create_view(child_model, options).catch(utils.reject("Couldn't create child view", true));
472 472 },
473 473
474 474 callbacks: function(){
475 475 /**
476 476 * Create msg callbacks for a comm msg.
477 477 */
478 478 return this.model.callbacks(this);
479 479 },
480 480
481 481 render: function(){
482 482 /**
483 483 * Render the view.
484 484 *
485 485 * By default, this is only called the first time the view is created
486 486 */
487 487 },
488 488
489 489 send: function (content, buffers) {
490 490 /**
491 491 * Send a custom msg associated with this view.
492 492 */
493 493 this.model.send(content, this.callbacks(), buffers);
494 494 },
495 495
496 496 touch: function () {
497 497 this.model.save_changes(this.callbacks());
498 498 },
499 499
500 500 after_displayed: function (callback, context) {
501 501 /**
502 502 * Calls the callback right away is the view is already displayed
503 503 * otherwise, register the callback to the 'displayed' event.
504 504 */
505 505 if (this.is_displayed) {
506 506 callback.apply(context);
507 507 } else {
508 508 this.on('displayed', callback, context);
509 509 }
510 510 },
511 511
512 512 remove: function () {
513 513 // Raise a remove event when the view is removed.
514 514 WidgetView.__super__.remove.apply(this, arguments);
515 515 this.trigger('remove');
516 516 }
517 517 });
518 518
519 519
520 520 var DOMWidgetView = WidgetView.extend({
521 521 initialize: function (parameters) {
522 522 /**
523 523 * Public constructor
524 524 */
525 525 DOMWidgetView.__super__.initialize.apply(this, [parameters]);
526 526 this.model.on('change:visible', this.update_visible, this);
527 527 this.model.on('change:_css', this.update_css, this);
528 528
529 529 this.model.on('change:_dom_classes', function(model, new_classes) {
530 530 var old_classes = model.previous('_dom_classes');
531 531 this.update_classes(old_classes, new_classes);
532 532 }, this);
533 533
534 534 this.model.on('change:color', function (model, value) {
535 535 this.update_attr('color', value); }, this);
536 536
537 537 this.model.on('change:background_color', function (model, value) {
538 538 this.update_attr('background', value); }, this);
539 539
540 540 this.model.on('change:width', function (model, value) {
541 541 this.update_attr('width', value); }, this);
542 542
543 543 this.model.on('change:height', function (model, value) {
544 544 this.update_attr('height', value); }, this);
545 545
546 546 this.model.on('change:border_color', function (model, value) {
547 547 this.update_attr('border-color', value); }, this);
548 548
549 549 this.model.on('change:border_width', function (model, value) {
550 550 this.update_attr('border-width', value); }, this);
551 551
552 552 this.model.on('change:border_style', function (model, value) {
553 553 this.update_attr('border-style', value); }, this);
554 554
555 555 this.model.on('change:font_style', function (model, value) {
556 556 this.update_attr('font-style', value); }, this);
557 557
558 558 this.model.on('change:font_weight', function (model, value) {
559 559 this.update_attr('font-weight', value); }, this);
560 560
561 561 this.model.on('change:font_size', function (model, value) {
562 562 this.update_attr('font-size', this._default_px(value)); }, this);
563 563
564 564 this.model.on('change:font_family', function (model, value) {
565 565 this.update_attr('font-family', value); }, this);
566 566
567 567 this.model.on('change:padding', function (model, value) {
568 568 this.update_attr('padding', value); }, this);
569 569
570 570 this.model.on('change:margin', function (model, value) {
571 571 this.update_attr('margin', this._default_px(value)); }, this);
572 572
573 573 this.model.on('change:border_radius', function (model, value) {
574 574 this.update_attr('border-radius', this._default_px(value)); }, this);
575 575
576 576 this.after_displayed(function() {
577 577 this.update_visible(this.model, this.model.get("visible"));
578 578 this.update_classes([], this.model.get('_dom_classes'));
579 579
580 580 this.update_attr('color', this.model.get('color'));
581 581 this.update_attr('background', this.model.get('background_color'));
582 582 this.update_attr('width', this.model.get('width'));
583 583 this.update_attr('height', this.model.get('height'));
584 584 this.update_attr('border-color', this.model.get('border_color'));
585 585 this.update_attr('border-width', this.model.get('border_width'));
586 586 this.update_attr('border-style', this.model.get('border_style'));
587 587 this.update_attr('font-style', this.model.get('font_style'));
588 588 this.update_attr('font-weight', this.model.get('font_weight'));
589 589 this.update_attr('font-size', this._default_px(this.model.get('font_size')));
590 590 this.update_attr('font-family', this.model.get('font_family'));
591 591 this.update_attr('padding', this.model.get('padding'));
592 592 this.update_attr('margin', this._default_px(this.model.get('margin')));
593 593 this.update_attr('border-radius', this._default_px(this.model.get('border_radius')));
594 594
595 595 this.update_css(this.model, this.model.get("_css"));
596 596 }, this);
597 597 },
598 598
599 599 _default_px: function(value) {
600 600 /**
601 601 * Makes browser interpret a numerical string as a pixel value.
602 602 */
603 603 if (value && /^\d+\.?(\d+)?$/.test(value.trim())) {
604 604 return value.trim() + 'px';
605 605 }
606 606 return value;
607 607 },
608 608
609 609 update_attr: function(name, value) {
610 610 /**
611 611 * Set a css attr of the widget view.
612 612 */
613 613 this.$el.css(name, value);
614 614 },
615 615
616 616 update_visible: function(model, value) {
617 617 /**
618 618 * Update visibility
619 619 */
620 620 switch(value) {
621 621 case null: // python None
622 622 this.$el.show().css('visibility', 'hidden'); break;
623 623 case false:
624 624 this.$el.hide(); break;
625 625 case true:
626 626 this.$el.show().css('visibility', ''); break;
627 627 }
628 628 },
629 629
630 630 update_css: function (model, css) {
631 631 /**
632 632 * Update the css styling of this view.
633 633 */
634 634 if (css === undefined) {return;}
635 635 for (var i = 0; i < css.length; i++) {
636 636 // Apply the css traits to all elements that match the selector.
637 637 var selector = css[i][0];
638 638 var elements = this._get_selector_element(selector);
639 639 if (elements.length > 0) {
640 640 var trait_key = css[i][1];
641 641 var trait_value = css[i][2];
642 642 elements.css(trait_key ,trait_value);
643 643 }
644 644 }
645 645 },
646 646
647 647 update_classes: function (old_classes, new_classes, $el) {
648 648 /**
649 649 * Update the DOM classes applied to an element, default to this.$el.
650 650 */
651 651 if ($el===undefined) {
652 652 $el = this.$el;
653 653 }
654 654 _.difference(old_classes, new_classes).map(function(c) {$el.removeClass(c);})
655 655 _.difference(new_classes, old_classes).map(function(c) {$el.addClass(c);})
656 656 },
657 657
658 658 update_mapped_classes: function(class_map, trait_name, previous_trait_value, $el) {
659 659 /**
660 660 * Update the DOM classes applied to the widget based on a single
661 661 * trait's value.
662 662 *
663 663 * Given a trait value classes map, this function automatically
664 664 * handles applying the appropriate classes to the widget element
665 665 * and removing classes that are no longer valid.
666 666 *
667 667 * Parameters
668 668 * ----------
669 669 * class_map: dictionary
670 670 * Dictionary of trait values to class lists.
671 671 * Example:
672 672 * {
673 673 * success: ['alert', 'alert-success'],
674 674 * info: ['alert', 'alert-info'],
675 675 * warning: ['alert', 'alert-warning'],
676 676 * danger: ['alert', 'alert-danger']
677 677 * };
678 678 * trait_name: string
679 679 * Name of the trait to check the value of.
680 680 * previous_trait_value: optional string, default ''
681 681 * Last trait value
682 682 * $el: optional jQuery element handle, defaults to this.$el
683 683 * Element that the classes are applied to.
684 684 */
685 685 var key = previous_trait_value;
686 686 if (key === undefined) {
687 687 key = this.model.previous(trait_name);
688 688 }
689 689 var old_classes = class_map[key] ? class_map[key] : [];
690 690 key = this.model.get(trait_name);
691 691 var new_classes = class_map[key] ? class_map[key] : [];
692 692
693 693 this.update_classes(old_classes, new_classes, $el || this.$el);
694 694 },
695 695
696 696 _get_selector_element: function (selector) {
697 697 /**
698 698 * Get the elements via the css selector.
699 699 */
700 700 var elements;
701 701 if (!selector) {
702 702 elements = this.$el;
703 703 } else {
704 704 elements = this.$el.find(selector).addBack(selector);
705 705 }
706 706 return elements;
707 707 },
708 708
709 709 typeset: function(element, text){
710 710 utils.typeset.apply(null, arguments);
711 711 },
712 712 });
713 713
714 714
715 715 var ViewList = function(create_view, remove_view, context) {
716 716 /**
717 717 * - create_view and remove_view are default functions called when adding or removing views
718 718 * - create_view takes a model and returns a view or a promise for a view for that model
719 719 * - remove_view takes a view and destroys it (including calling `view.remove()`)
720 720 * - each time the update() function is called with a new list, the create and remove
721 721 * callbacks will be called in an order so that if you append the views created in the
722 722 * create callback and remove the views in the remove callback, you will duplicate
723 723 * the order of the list.
724 724 * - the remove callback defaults to just removing the view (e.g., pass in null for the second parameter)
725 725 * - the context defaults to the created ViewList. If you pass another context, the create and remove
726 726 * will be called in that context.
727 727 */
728 728
729 729 this.initialize.apply(this, arguments);
730 730 };
731 731
732 732 _.extend(ViewList.prototype, {
733 733 initialize: function(create_view, remove_view, context) {
734 734 this._handler_context = context || this;
735 735 this._models = [];
736 736 this.views = []; // list of promises for views
737 737 this._create_view = create_view;
738 738 this._remove_view = remove_view || function(view) {view.remove();};
739 739 },
740 740
741 741 update: function(new_models, create_view, remove_view, context) {
742 742 /**
743 743 * the create_view, remove_view, and context arguments override the defaults
744 744 * specified when the list is created.
745 745 * after this function, the .views attribute is a list of promises for views
746 746 * if you want to perform some action on the list of views, do something like
747 747 * `Promise.all(myviewlist.views).then(function(views) {...});`
748 748 */
749 749 var remove = remove_view || this._remove_view;
750 750 var create = create_view || this._create_view;
751 751 context = context || this._handler_context;
752 752 var i = 0;
753 753 // first, skip past the beginning of the lists if they are identical
754 754 for (; i < new_models.length; i++) {
755 755 if (i >= this._models.length || new_models[i] !== this._models[i]) {
756 756 break;
757 757 }
758 758 }
759 759
760 760 var first_removed = i;
761 761 // Remove the non-matching items from the old list.
762 762 var removed = this.views.splice(first_removed, this.views.length-first_removed);
763 763 for (var j = 0; j < removed.length; j++) {
764 764 removed[j].then(function(view) {
765 765 remove.call(context, view)
766 766 });
767 767 }
768 768
769 769 // Add the rest of the new list items.
770 770 for (; i < new_models.length; i++) {
771 771 this.views.push(Promise.resolve(create.call(context, new_models[i])));
772 772 }
773 773 // make a copy of the input array
774 774 this._models = new_models.slice();
775 775 },
776 776
777 777 remove: function() {
778 778 /**
779 779 * removes every view in the list; convenience function for `.update([])`
780 780 * that should be faster
781 781 * returns a promise that resolves after this removal is done
782 782 */
783 783 var that = this;
784 784 return Promise.all(this.views).then(function(views) {
785 785 for (var i = 0; i < that.views.length; i++) {
786 786 that._remove_view.call(that._handler_context, views[i]);
787 787 }
788 788 that.views = [];
789 789 that._models = [];
790 790 });
791 791 },
792 792 });
793 793
794 794 var widget = {
795 795 'unpack_models': unpack_models,
796 796 'WidgetModel': WidgetModel,
797 797 'WidgetView': WidgetView,
798 798 'DOMWidgetView': DOMWidgetView,
799 799 'ViewList': ViewList,
800 800 };
801 801
802 802 // For backwards compatability.
803 803 $.extend(IPython, widget);
804 804
805 805 return widget;
806 806 });
@@ -1,1868 +1,1868 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 IPython.utils import py3compat
57 57 from IPython.utils import eventful
58 58 from IPython.utils.getargspec import getargspec
59 59 from IPython.utils.importstring import import_item
60 60 from IPython.utils.py3compat import iteritems, string_types
61 61
62 62 from .sentinel import Sentinel
63 63 SequenceTypes = (list, tuple, set, frozenset)
64 64
65 65 #-----------------------------------------------------------------------------
66 66 # Basic classes
67 67 #-----------------------------------------------------------------------------
68 68
69 69
70 70 NoDefaultSpecified = Sentinel('NoDefaultSpecified', __name__,
71 71 '''
72 72 Used in Traitlets to specify that no defaults are set in kwargs
73 73 '''
74 74 )
75 75
76 76
77 77 class Undefined ( object ): pass
78 78 Undefined = Undefined()
79 79
80 80 class TraitError(Exception):
81 81 pass
82 82
83 83 #-----------------------------------------------------------------------------
84 84 # Utilities
85 85 #-----------------------------------------------------------------------------
86 86
87 87
88 88 def class_of ( object ):
89 89 """ Returns a string containing the class name of an object with the
90 90 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
91 91 'a PlotValue').
92 92 """
93 93 if isinstance( object, py3compat.string_types ):
94 94 return add_article( object )
95 95
96 96 return add_article( object.__class__.__name__ )
97 97
98 98
99 99 def add_article ( name ):
100 100 """ Returns a string containing the correct indefinite article ('a' or 'an')
101 101 prefixed to the specified string.
102 102 """
103 103 if name[:1].lower() in 'aeiou':
104 104 return 'an ' + name
105 105
106 106 return 'a ' + name
107 107
108 108
109 109 def repr_type(obj):
110 110 """ Return a string representation of a value and its type for readable
111 111 error messages.
112 112 """
113 113 the_type = type(obj)
114 114 if (not py3compat.PY3) and the_type is InstanceType:
115 115 # Old-style class.
116 116 the_type = obj.__class__
117 117 msg = '%r %r' % (obj, the_type)
118 118 return msg
119 119
120 120
121 121 def is_trait(t):
122 122 """ Returns whether the given value is an instance or subclass of TraitType.
123 123 """
124 124 return (isinstance(t, TraitType) or
125 125 (isinstance(t, type) and issubclass(t, TraitType)))
126 126
127 127
128 128 def parse_notifier_name(name):
129 129 """Convert the name argument to a list of names.
130 130
131 131 Examples
132 132 --------
133 133
134 134 >>> parse_notifier_name('a')
135 135 ['a']
136 136 >>> parse_notifier_name(['a','b'])
137 137 ['a', 'b']
138 138 >>> parse_notifier_name(None)
139 139 ['anytrait']
140 140 """
141 141 if isinstance(name, string_types):
142 142 return [name]
143 143 elif name is None:
144 144 return ['anytrait']
145 145 elif isinstance(name, (list, tuple)):
146 146 for n in name:
147 147 assert isinstance(n, string_types), "names must be strings"
148 148 return name
149 149
150 150
151 151 class _SimpleTest:
152 152 def __init__ ( self, value ): self.value = value
153 153 def __call__ ( self, test ):
154 154 return test == self.value
155 155 def __repr__(self):
156 156 return "<SimpleTest(%r)" % self.value
157 157 def __str__(self):
158 158 return self.__repr__()
159 159
160 160
161 161 def getmembers(object, predicate=None):
162 162 """A safe version of inspect.getmembers that handles missing attributes.
163 163
164 164 This is useful when there are descriptor based attributes that for
165 165 some reason raise AttributeError even though they exist. This happens
166 166 in zope.inteface with the __provides__ attribute.
167 167 """
168 168 results = []
169 169 for key in dir(object):
170 170 try:
171 171 value = getattr(object, key)
172 172 except AttributeError:
173 173 pass
174 174 else:
175 175 if not predicate or predicate(value):
176 176 results.append((key, value))
177 177 results.sort()
178 178 return results
179 179
180 180 def _validate_link(*tuples):
181 181 """Validate arguments for traitlet link functions"""
182 182 for t in tuples:
183 183 if not len(t) == 2:
184 184 raise TypeError("Each linked traitlet must be specified as (HasTraits, 'trait_name'), not %r" % t)
185 185 obj, trait_name = t
186 186 if not isinstance(obj, HasTraits):
187 187 raise TypeError("Each object must be HasTraits, not %r" % type(obj))
188 188 if not trait_name in obj.traits():
189 189 raise TypeError("%r has no trait %r" % (obj, trait_name))
190 190
191 191 class link(object):
192 192 """Link traits from different objects together so they remain in sync.
193 193
194 194 Parameters
195 195 ----------
196 196 *args : pairs of objects/attributes
197 197
198 198 Examples
199 199 --------
200 200
201 201 >>> c = link((obj1, 'value'), (obj2, 'value'), (obj3, 'value'))
202 202 >>> obj1.value = 5 # updates other objects as well
203 203 """
204 204 updating = False
205 205 def __init__(self, *args):
206 206 if len(args) < 2:
207 207 raise TypeError('At least two traitlets must be provided.')
208 208 _validate_link(*args)
209 209
210 210 self.objects = {}
211 211
212 212 initial = getattr(args[0][0], args[0][1])
213 213 for obj, attr in args:
214 214 setattr(obj, attr, initial)
215 215
216 216 callback = self._make_closure(obj, attr)
217 217 obj.on_trait_change(callback, attr)
218 218 self.objects[(obj, attr)] = callback
219 219
220 220 @contextlib.contextmanager
221 221 def _busy_updating(self):
222 222 self.updating = True
223 223 try:
224 224 yield
225 225 finally:
226 226 self.updating = False
227 227
228 228 def _make_closure(self, sending_obj, sending_attr):
229 229 def update(name, old, new):
230 230 self._update(sending_obj, sending_attr, new)
231 231 return update
232 232
233 233 def _update(self, sending_obj, sending_attr, new):
234 234 if self.updating:
235 235 return
236 236 with self._busy_updating():
237 237 for obj, attr in self.objects.keys():
238 238 setattr(obj, attr, new)
239 239
240 240 def unlink(self):
241 241 for key, callback in self.objects.items():
242 242 (obj, attr) = key
243 243 obj.on_trait_change(callback, attr, remove=True)
244 244
245 245 class directional_link(object):
246 246 """Link the trait of a source object with traits of target objects.
247 247
248 248 Parameters
249 249 ----------
250 250 source : pair of object, name
251 251 targets : pairs of objects/attributes
252 252
253 253 Examples
254 254 --------
255 255
256 256 >>> c = directional_link((src, 'value'), (tgt1, 'value'), (tgt2, 'value'))
257 257 >>> src.value = 5 # updates target objects
258 258 >>> tgt1.value = 6 # does not update other objects
259 259 """
260 260 updating = False
261 261
262 262 def __init__(self, source, *targets):
263 263 if len(targets) < 1:
264 264 raise TypeError('At least two traitlets must be provided.')
265 265 _validate_link(source, *targets)
266 266 self.source = source
267 267 self.targets = targets
268 268
269 269 # Update current value
270 270 src_attr_value = getattr(source[0], source[1])
271 271 for obj, attr in targets:
272 272 setattr(obj, attr, src_attr_value)
273 273
274 274 # Wire
275 275 self.source[0].on_trait_change(self._update, self.source[1])
276 276
277 277 @contextlib.contextmanager
278 278 def _busy_updating(self):
279 279 self.updating = True
280 280 try:
281 281 yield
282 282 finally:
283 283 self.updating = False
284 284
285 285 def _update(self, name, old, new):
286 286 if self.updating:
287 287 return
288 288 with self._busy_updating():
289 289 for obj, attr in self.targets:
290 290 setattr(obj, attr, new)
291 291
292 292 def unlink(self):
293 293 self.source[0].on_trait_change(self._update, self.source[1], remove=True)
294 294 self.source = None
295 295 self.targets = []
296 296
297 297 dlink = directional_link
298 298
299 299
300 300 #-----------------------------------------------------------------------------
301 301 # Base TraitType for all traits
302 302 #-----------------------------------------------------------------------------
303 303
304 304
305 305 class TraitType(object):
306 306 """A base class for all trait descriptors.
307 307
308 308 Notes
309 309 -----
310 310 Our implementation of traits is based on Python's descriptor
311 311 prototol. This class is the base class for all such descriptors. The
312 312 only magic we use is a custom metaclass for the main :class:`HasTraits`
313 313 class that does the following:
314 314
315 315 1. Sets the :attr:`name` attribute of every :class:`TraitType`
316 316 instance in the class dict to the name of the attribute.
317 317 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
318 318 instance in the class dict to the *class* that declared the trait.
319 319 This is used by the :class:`This` trait to allow subclasses to
320 320 accept superclasses for :class:`This` values.
321 321 """
322 322
323 323 metadata = {}
324 324 default_value = Undefined
325 325 allow_none = False
326 326 info_text = 'any value'
327 327
328 328 def __init__(self, default_value=NoDefaultSpecified, allow_none=None, **metadata):
329 329 """Create a TraitType.
330 330 """
331 331 if default_value is not NoDefaultSpecified:
332 332 self.default_value = default_value
333 333 if allow_none is not None:
334 334 self.allow_none = allow_none
335 335
336 336 if 'default' in metadata:
337 337 # Warn the user that they probably meant default_value.
338 338 warn(
339 339 "Parameter 'default' passed to TraitType. "
340 340 "Did you mean 'default_value'?"
341 341 )
342 342
343 343 if len(metadata) > 0:
344 344 if len(self.metadata) > 0:
345 345 self._metadata = self.metadata.copy()
346 346 self._metadata.update(metadata)
347 347 else:
348 348 self._metadata = metadata
349 349 else:
350 350 self._metadata = self.metadata
351 351
352 352 self.init()
353 353
354 354 def init(self):
355 355 pass
356 356
357 357 def get_default_value(self):
358 358 """Create a new instance of the default value."""
359 359 return self.default_value
360 360
361 361 def instance_init(self):
362 362 """Part of the initialization which may depends on the underlying
363 363 HasTraits instance.
364 364
365 365 It is typically overloaded for specific trait types.
366 366
367 367 This method is called by :meth:`HasTraits.__new__` and in the
368 368 :meth:`TraitType.instance_init` method of trait types holding
369 369 other trait types.
370 370 """
371 371 pass
372 372
373 373 def init_default_value(self, obj):
374 374 """Instantiate the default value for the trait type.
375 375
376 376 This method is called by :meth:`TraitType.set_default_value` in the
377 377 case a default value is provided at construction time or later when
378 378 accessing the trait value for the first time in
379 379 :meth:`HasTraits.__get__`.
380 380 """
381 381 value = self.get_default_value()
382 382 value = self._validate(obj, value)
383 383 obj._trait_values[self.name] = value
384 384 return value
385 385
386 386 def set_default_value(self, obj):
387 387 """Set the default value on a per instance basis.
388 388
389 389 This method is called by :meth:`HasTraits.__new__` to instantiate and
390 390 validate the default value. The creation and validation of
391 391 default values must be delayed until the parent :class:`HasTraits`
392 392 class has been instantiated.
393 393 Parameters
394 394 ----------
395 395 obj : :class:`HasTraits` instance
396 396 The parent :class:`HasTraits` instance that has just been
397 397 created.
398 398 """
399 399 # Check for a deferred initializer defined in the same class as the
400 400 # trait declaration or above.
401 401 mro = type(obj).mro()
402 402 meth_name = '_%s_default' % self.name
403 403 for cls in mro[:mro.index(self.this_class)+1]:
404 404 if meth_name in cls.__dict__:
405 405 break
406 406 else:
407 407 # We didn't find one. Do static initialization.
408 408 self.init_default_value(obj)
409 409 return
410 410 # Complete the dynamic initialization.
411 411 obj._trait_dyn_inits[self.name] = meth_name
412 412
413 413 def __get__(self, obj, cls=None):
414 414 """Get the value of the trait by self.name for the instance.
415 415
416 416 Default values are instantiated when :meth:`HasTraits.__new__`
417 417 is called. Thus by the time this method gets called either the
418 418 default value or a user defined value (they called :meth:`__set__`)
419 419 is in the :class:`HasTraits` instance.
420 420 """
421 421 if obj is None:
422 422 return self
423 423 else:
424 424 try:
425 425 value = obj._trait_values[self.name]
426 426 except KeyError:
427 427 # Check for a dynamic initializer.
428 428 if self.name in obj._trait_dyn_inits:
429 429 method = getattr(obj, obj._trait_dyn_inits[self.name])
430 430 value = method()
431 431 # FIXME: Do we really validate here?
432 432 value = self._validate(obj, value)
433 433 obj._trait_values[self.name] = value
434 434 return value
435 435 else:
436 436 return self.init_default_value(obj)
437 437 except Exception:
438 438 # HasTraits should call set_default_value to populate
439 439 # this. So this should never be reached.
440 440 raise TraitError('Unexpected error in TraitType: '
441 441 'default value not set properly')
442 442 else:
443 443 return value
444 444
445 445 def __set__(self, obj, value):
446 446 new_value = self._validate(obj, value)
447 447 try:
448 448 old_value = obj._trait_values[self.name]
449 449 except KeyError:
450 450 old_value = Undefined
451 451
452 452 obj._trait_values[self.name] = new_value
453 453 try:
454 454 silent = bool(old_value == new_value)
455 455 except:
456 456 # if there is an error in comparing, default to notify
457 457 silent = False
458 458 if silent is not True:
459 459 # we explicitly compare silent to True just in case the equality
460 460 # comparison above returns something other than True/False
461 461 obj._notify_trait(self.name, old_value, new_value)
462 462
463 463 def _validate(self, obj, value):
464 464 if value is None and self.allow_none:
465 465 return value
466 466 if hasattr(self, 'validate'):
467 467 value = self.validate(obj, value)
468 468 if obj._cross_validation_lock is False:
469 469 value = self._cross_validate(obj, value)
470 470 return value
471 471
472 472 def _cross_validate(self, obj, value):
473 473 if hasattr(obj, '_%s_validate' % self.name):
474 474 cross_validate = getattr(obj, '_%s_validate' % self.name)
475 475 value = cross_validate(value, self)
476 476 return value
477 477
478 478 def __or__(self, other):
479 479 if isinstance(other, Union):
480 480 return Union([self] + other.trait_types)
481 481 else:
482 482 return Union([self, other])
483 483
484 484 def info(self):
485 485 return self.info_text
486 486
487 487 def error(self, obj, value):
488 488 if obj is not None:
489 489 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
490 490 % (self.name, class_of(obj),
491 491 self.info(), repr_type(value))
492 492 else:
493 493 e = "The '%s' trait must be %s, but a value of %r was specified." \
494 494 % (self.name, self.info(), repr_type(value))
495 495 raise TraitError(e)
496 496
497 497 def get_metadata(self, key, default=None):
498 498 return getattr(self, '_metadata', {}).get(key, default)
499 499
500 500 def set_metadata(self, key, value):
501 501 getattr(self, '_metadata', {})[key] = value
502 502
503 503
504 504 #-----------------------------------------------------------------------------
505 505 # The HasTraits implementation
506 506 #-----------------------------------------------------------------------------
507 507
508 508
509 509 class MetaHasTraits(type):
510 510 """A metaclass for HasTraits.
511 511
512 512 This metaclass makes sure that any TraitType class attributes are
513 513 instantiated and sets their name attribute.
514 514 """
515 515
516 516 def __new__(mcls, name, bases, classdict):
517 517 """Create the HasTraits class.
518 518
519 519 This instantiates all TraitTypes in the class dict and sets their
520 520 :attr:`name` attribute.
521 521 """
522 522 # print "MetaHasTraitlets (mcls, name): ", mcls, name
523 523 # print "MetaHasTraitlets (bases): ", bases
524 524 # print "MetaHasTraitlets (classdict): ", classdict
525 525 for k,v in iteritems(classdict):
526 526 if isinstance(v, TraitType):
527 527 v.name = k
528 528 elif inspect.isclass(v):
529 529 if issubclass(v, TraitType):
530 530 vinst = v()
531 531 vinst.name = k
532 532 classdict[k] = vinst
533 533 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
534 534
535 535 def __init__(cls, name, bases, classdict):
536 536 """Finish initializing the HasTraits class.
537 537
538 538 This sets the :attr:`this_class` attribute of each TraitType in the
539 539 class dict to the newly created class ``cls``.
540 540 """
541 541 for k, v in iteritems(classdict):
542 542 if isinstance(v, TraitType):
543 543 v.this_class = cls
544 544 super(MetaHasTraits, cls).__init__(name, bases, classdict)
545 545
546 546
547 547 class HasTraits(py3compat.with_metaclass(MetaHasTraits, object)):
548 548
549 549 def __new__(cls, *args, **kw):
550 550 # This is needed because object.__new__ only accepts
551 551 # the cls argument.
552 552 new_meth = super(HasTraits, cls).__new__
553 553 if new_meth is object.__new__:
554 554 inst = new_meth(cls)
555 555 else:
556 556 inst = new_meth(cls, **kw)
557 557 inst._trait_values = {}
558 558 inst._trait_notifiers = {}
559 559 inst._trait_dyn_inits = {}
560 560 inst._cross_validation_lock = True
561 561 # Here we tell all the TraitType instances to set their default
562 562 # values on the instance.
563 563 for key in dir(cls):
564 564 # Some descriptors raise AttributeError like zope.interface's
565 565 # __provides__ attributes even though they exist. This causes
566 566 # AttributeErrors even though they are listed in dir(cls).
567 567 try:
568 568 value = getattr(cls, key)
569 569 except AttributeError:
570 570 pass
571 571 else:
572 572 if isinstance(value, TraitType):
573 573 value.instance_init()
574 574 if key not in kw:
575 575 value.set_default_value(inst)
576 576 inst._cross_validation_lock = False
577 577 return inst
578 578
579 579 def __init__(self, *args, **kw):
580 580 # Allow trait values to be set using keyword arguments.
581 581 # We need to use setattr for this to trigger validation and
582 582 # notifications.
583 583 with self.hold_trait_notifications():
584 584 for key, value in iteritems(kw):
585 585 setattr(self, key, value)
586 586
587 587 @contextlib.contextmanager
588 588 def hold_trait_notifications(self):
589 589 """Context manager for bundling trait change notifications and cross
590 590 validation.
591 591
592 592 Use this when doing multiple trait assignments (init, config), to avoid
593 593 race conditions in trait notifiers requesting other trait values.
594 594 All trait notifications will fire after all values have been assigned.
595 595 """
596 596 if self._cross_validation_lock is True:
597 597 yield
598 598 return
599 599 else:
600 600 cache = {}
601 601 _notify_trait = self._notify_trait
602 602
603 603 def merge(previous, current):
604 604 """merges notifications of the form (name, old, value)"""
605 605 if previous is None:
606 606 return current
607 607 else:
608 608 return (current[0], previous[1], current[2])
609 609
610 610 def hold(*a):
611 611 cache[a[0]] = merge(cache.get(a[0]), a)
612 612
613 613 try:
614 614 self._notify_trait = hold
615 615 self._cross_validation_lock = True
616 616 yield
617 617 for name in cache:
618 618 if hasattr(self, '_%s_validate' % name):
619 619 cross_validate = getattr(self, '_%s_validate' % name)
620 620 setattr(self, name, cross_validate(getattr(self, name), self))
621 621 except TraitError as e:
622 622 self._notify_trait = lambda *x: None
623 623 for name in cache:
624 624 if cache[name][1] is not Undefined:
625 625 setattr(self, name, cache[name][1])
626 626 else:
627 delattr(self, name)
627 self._trait_values.pop(name)
628 628 cache = {}
629 629 raise e
630 630 finally:
631 631 self._notify_trait = _notify_trait
632 632 self._cross_validation_lock = False
633 633 if isinstance(_notify_trait, types.MethodType):
634 634 # FIXME: remove when support is bumped to 3.4.
635 635 # when original method is restored,
636 636 # remove the redundant value from __dict__
637 637 # (only used to preserve pickleability on Python < 3.4)
638 638 self.__dict__.pop('_notify_trait', None)
639 639
640 640 # trigger delayed notifications
641 641 for v in cache.values():
642 642 self._notify_trait(*v)
643 643
644 644 def _notify_trait(self, name, old_value, new_value):
645 645
646 646 # First dynamic ones
647 647 callables = []
648 648 callables.extend(self._trait_notifiers.get(name,[]))
649 649 callables.extend(self._trait_notifiers.get('anytrait',[]))
650 650
651 651 # Now static ones
652 652 try:
653 653 cb = getattr(self, '_%s_changed' % name)
654 654 except:
655 655 pass
656 656 else:
657 657 callables.append(cb)
658 658
659 659 # Call them all now
660 660 for c in callables:
661 661 # Traits catches and logs errors here. I allow them to raise
662 662 if callable(c):
663 663 argspec = getargspec(c)
664 664
665 665 nargs = len(argspec[0])
666 666 # Bound methods have an additional 'self' argument
667 667 # I don't know how to treat unbound methods, but they
668 668 # can't really be used for callbacks.
669 669 if isinstance(c, types.MethodType):
670 670 offset = -1
671 671 else:
672 672 offset = 0
673 673 if nargs + offset == 0:
674 674 c()
675 675 elif nargs + offset == 1:
676 676 c(name)
677 677 elif nargs + offset == 2:
678 678 c(name, new_value)
679 679 elif nargs + offset == 3:
680 680 c(name, old_value, new_value)
681 681 else:
682 682 raise TraitError('a trait changed callback '
683 683 'must have 0-3 arguments.')
684 684 else:
685 685 raise TraitError('a trait changed callback '
686 686 'must be callable.')
687 687
688 688
689 689 def _add_notifiers(self, handler, name):
690 690 if name not in self._trait_notifiers:
691 691 nlist = []
692 692 self._trait_notifiers[name] = nlist
693 693 else:
694 694 nlist = self._trait_notifiers[name]
695 695 if handler not in nlist:
696 696 nlist.append(handler)
697 697
698 698 def _remove_notifiers(self, handler, name):
699 699 if name in self._trait_notifiers:
700 700 nlist = self._trait_notifiers[name]
701 701 try:
702 702 index = nlist.index(handler)
703 703 except ValueError:
704 704 pass
705 705 else:
706 706 del nlist[index]
707 707
708 708 def on_trait_change(self, handler, name=None, remove=False):
709 709 """Setup a handler to be called when a trait changes.
710 710
711 711 This is used to setup dynamic notifications of trait changes.
712 712
713 713 Static handlers can be created by creating methods on a HasTraits
714 714 subclass with the naming convention '_[traitname]_changed'. Thus,
715 715 to create static handler for the trait 'a', create the method
716 716 _a_changed(self, name, old, new) (fewer arguments can be used, see
717 717 below).
718 718
719 719 Parameters
720 720 ----------
721 721 handler : callable
722 722 A callable that is called when a trait changes. Its
723 723 signature can be handler(), handler(name), handler(name, new)
724 724 or handler(name, old, new).
725 725 name : list, str, None
726 726 If None, the handler will apply to all traits. If a list
727 727 of str, handler will apply to all names in the list. If a
728 728 str, the handler will apply just to that name.
729 729 remove : bool
730 730 If False (the default), then install the handler. If True
731 731 then unintall it.
732 732 """
733 733 if remove:
734 734 names = parse_notifier_name(name)
735 735 for n in names:
736 736 self._remove_notifiers(handler, n)
737 737 else:
738 738 names = parse_notifier_name(name)
739 739 for n in names:
740 740 self._add_notifiers(handler, n)
741 741
742 742 @classmethod
743 743 def class_trait_names(cls, **metadata):
744 744 """Get a list of all the names of this class' traits.
745 745
746 746 This method is just like the :meth:`trait_names` method,
747 747 but is unbound.
748 748 """
749 749 return cls.class_traits(**metadata).keys()
750 750
751 751 @classmethod
752 752 def class_traits(cls, **metadata):
753 753 """Get a `dict` of all the traits of this class. The dictionary
754 754 is keyed on the name and the values are the TraitType objects.
755 755
756 756 This method is just like the :meth:`traits` method, but is unbound.
757 757
758 758 The TraitTypes returned don't know anything about the values
759 759 that the various HasTrait's instances are holding.
760 760
761 761 The metadata kwargs allow functions to be passed in which
762 762 filter traits based on metadata values. The functions should
763 763 take a single value as an argument and return a boolean. If
764 764 any function returns False, then the trait is not included in
765 765 the output. This does not allow for any simple way of
766 766 testing that a metadata name exists and has any
767 767 value because get_metadata returns None if a metadata key
768 768 doesn't exist.
769 769 """
770 770 traits = dict([memb for memb in getmembers(cls) if
771 771 isinstance(memb[1], TraitType)])
772 772
773 773 if len(metadata) == 0:
774 774 return traits
775 775
776 776 for meta_name, meta_eval in metadata.items():
777 777 if type(meta_eval) is not FunctionType:
778 778 metadata[meta_name] = _SimpleTest(meta_eval)
779 779
780 780 result = {}
781 781 for name, trait in traits.items():
782 782 for meta_name, meta_eval in metadata.items():
783 783 if not meta_eval(trait.get_metadata(meta_name)):
784 784 break
785 785 else:
786 786 result[name] = trait
787 787
788 788 return result
789 789
790 790 def trait_names(self, **metadata):
791 791 """Get a list of all the names of this class' traits."""
792 792 return self.traits(**metadata).keys()
793 793
794 794 def traits(self, **metadata):
795 795 """Get a `dict` of all the traits of this class. The dictionary
796 796 is keyed on the name and the values are the TraitType objects.
797 797
798 798 The TraitTypes returned don't know anything about the values
799 799 that the various HasTrait's instances are holding.
800 800
801 801 The metadata kwargs allow functions to be passed in which
802 802 filter traits based on metadata values. The functions should
803 803 take a single value as an argument and return a boolean. If
804 804 any function returns False, then the trait is not included in
805 805 the output. This does not allow for any simple way of
806 806 testing that a metadata name exists and has any
807 807 value because get_metadata returns None if a metadata key
808 808 doesn't exist.
809 809 """
810 810 traits = dict([memb for memb in getmembers(self.__class__) if
811 811 isinstance(memb[1], TraitType)])
812 812
813 813 if len(metadata) == 0:
814 814 return traits
815 815
816 816 for meta_name, meta_eval in metadata.items():
817 817 if type(meta_eval) is not FunctionType:
818 818 metadata[meta_name] = _SimpleTest(meta_eval)
819 819
820 820 result = {}
821 821 for name, trait in traits.items():
822 822 for meta_name, meta_eval in metadata.items():
823 823 if not meta_eval(trait.get_metadata(meta_name)):
824 824 break
825 825 else:
826 826 result[name] = trait
827 827
828 828 return result
829 829
830 830 def trait_metadata(self, traitname, key, default=None):
831 831 """Get metadata values for trait by key."""
832 832 try:
833 833 trait = getattr(self.__class__, traitname)
834 834 except AttributeError:
835 835 raise TraitError("Class %s does not have a trait named %s" %
836 836 (self.__class__.__name__, traitname))
837 837 else:
838 838 return trait.get_metadata(key, default)
839 839
840 840 def add_trait(self, traitname, trait):
841 841 """Dynamically add a trait attribute to the HasTraits instance."""
842 842 self.__class__ = type(self.__class__.__name__, (self.__class__,),
843 843 {traitname: trait})
844 844 trait.set_default_value(self)
845 845
846 846 #-----------------------------------------------------------------------------
847 847 # Actual TraitTypes implementations/subclasses
848 848 #-----------------------------------------------------------------------------
849 849
850 850 #-----------------------------------------------------------------------------
851 851 # TraitTypes subclasses for handling classes and instances of classes
852 852 #-----------------------------------------------------------------------------
853 853
854 854
855 855 class ClassBasedTraitType(TraitType):
856 856 """
857 857 A trait with error reporting and string -> type resolution for Type,
858 858 Instance and This.
859 859 """
860 860
861 861 def _resolve_string(self, string):
862 862 """
863 863 Resolve a string supplied for a type into an actual object.
864 864 """
865 865 return import_item(string)
866 866
867 867 def error(self, obj, value):
868 868 kind = type(value)
869 869 if (not py3compat.PY3) and kind is InstanceType:
870 870 msg = 'class %s' % value.__class__.__name__
871 871 else:
872 872 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
873 873
874 874 if obj is not None:
875 875 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
876 876 % (self.name, class_of(obj),
877 877 self.info(), msg)
878 878 else:
879 879 e = "The '%s' trait must be %s, but a value of %r was specified." \
880 880 % (self.name, self.info(), msg)
881 881
882 882 raise TraitError(e)
883 883
884 884
885 885 class Type(ClassBasedTraitType):
886 886 """A trait whose value must be a subclass of a specified class."""
887 887
888 888 def __init__ (self, default_value=None, klass=None, **metadata):
889 889 """Construct a Type trait
890 890
891 891 A Type trait specifies that its values must be subclasses of
892 892 a particular class.
893 893
894 894 If only ``default_value`` is given, it is used for the ``klass`` as
895 895 well.
896 896
897 897 Parameters
898 898 ----------
899 899 default_value : class, str or None
900 900 The default value must be a subclass of klass. If an str,
901 901 the str must be a fully specified class name, like 'foo.bar.Bah'.
902 902 The string is resolved into real class, when the parent
903 903 :class:`HasTraits` class is instantiated.
904 904 klass : class, str, None
905 905 Values of this trait must be a subclass of klass. The klass
906 906 may be specified in a string like: 'foo.bar.MyClass'.
907 907 The string is resolved into real class, when the parent
908 908 :class:`HasTraits` class is instantiated.
909 909 allow_none : bool [ default True ]
910 910 Indicates whether None is allowed as an assignable value. Even if
911 911 ``False``, the default value may be ``None``.
912 912 """
913 913 if default_value is None:
914 914 if klass is None:
915 915 klass = object
916 916 elif klass is None:
917 917 klass = default_value
918 918
919 919 if not (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
920 920 raise TraitError("A Type trait must specify a class.")
921 921
922 922 self.klass = klass
923 923
924 924 super(Type, self).__init__(default_value, **metadata)
925 925
926 926 def validate(self, obj, value):
927 927 """Validates that the value is a valid object instance."""
928 928 if isinstance(value, py3compat.string_types):
929 929 try:
930 930 value = self._resolve_string(value)
931 931 except ImportError:
932 932 raise TraitError("The '%s' trait of %s instance must be a type, but "
933 933 "%r could not be imported" % (self.name, obj, value))
934 934 try:
935 935 if issubclass(value, self.klass):
936 936 return value
937 937 except:
938 938 pass
939 939
940 940 self.error(obj, value)
941 941
942 942 def info(self):
943 943 """ Returns a description of the trait."""
944 944 if isinstance(self.klass, py3compat.string_types):
945 945 klass = self.klass
946 946 else:
947 947 klass = self.klass.__name__
948 948 result = 'a subclass of ' + klass
949 949 if self.allow_none:
950 950 return result + ' or None'
951 951 return result
952 952
953 953 def instance_init(self):
954 954 self._resolve_classes()
955 955 super(Type, self).instance_init()
956 956
957 957 def _resolve_classes(self):
958 958 if isinstance(self.klass, py3compat.string_types):
959 959 self.klass = self._resolve_string(self.klass)
960 960 if isinstance(self.default_value, py3compat.string_types):
961 961 self.default_value = self._resolve_string(self.default_value)
962 962
963 963 def get_default_value(self):
964 964 return self.default_value
965 965
966 966
967 967 class DefaultValueGenerator(object):
968 968 """A class for generating new default value instances."""
969 969
970 970 def __init__(self, *args, **kw):
971 971 self.args = args
972 972 self.kw = kw
973 973
974 974 def generate(self, klass):
975 975 return klass(*self.args, **self.kw)
976 976
977 977
978 978 class Instance(ClassBasedTraitType):
979 979 """A trait whose value must be an instance of a specified class.
980 980
981 981 The value can also be an instance of a subclass of the specified class.
982 982
983 983 Subclasses can declare default classes by overriding the klass attribute
984 984 """
985 985
986 986 klass = None
987 987
988 988 def __init__(self, klass=None, args=None, kw=None, **metadata):
989 989 """Construct an Instance trait.
990 990
991 991 This trait allows values that are instances of a particular
992 992 class or its subclasses. Our implementation is quite different
993 993 from that of enthough.traits as we don't allow instances to be used
994 994 for klass and we handle the ``args`` and ``kw`` arguments differently.
995 995
996 996 Parameters
997 997 ----------
998 998 klass : class, str
999 999 The class that forms the basis for the trait. Class names
1000 1000 can also be specified as strings, like 'foo.bar.Bar'.
1001 1001 args : tuple
1002 1002 Positional arguments for generating the default value.
1003 1003 kw : dict
1004 1004 Keyword arguments for generating the default value.
1005 1005 allow_none : bool [default True]
1006 1006 Indicates whether None is allowed as a value.
1007 1007
1008 1008 Notes
1009 1009 -----
1010 1010 If both ``args`` and ``kw`` are None, then the default value is None.
1011 1011 If ``args`` is a tuple and ``kw`` is a dict, then the default is
1012 1012 created as ``klass(*args, **kw)``. If exactly one of ``args`` or ``kw`` is
1013 1013 None, the None is replaced by ``()`` or ``{}``, respectively.
1014 1014 """
1015 1015 if klass is None:
1016 1016 klass = self.klass
1017 1017
1018 1018 if (klass is not None) and (inspect.isclass(klass) or isinstance(klass, py3compat.string_types)):
1019 1019 self.klass = klass
1020 1020 else:
1021 1021 raise TraitError('The klass attribute must be a class'
1022 1022 ' not: %r' % klass)
1023 1023
1024 1024 # self.klass is a class, so handle default_value
1025 1025 if args is None and kw is None:
1026 1026 default_value = None
1027 1027 else:
1028 1028 if args is None:
1029 1029 # kw is not None
1030 1030 args = ()
1031 1031 elif kw is None:
1032 1032 # args is not None
1033 1033 kw = {}
1034 1034
1035 1035 if not isinstance(kw, dict):
1036 1036 raise TraitError("The 'kw' argument must be a dict or None.")
1037 1037 if not isinstance(args, tuple):
1038 1038 raise TraitError("The 'args' argument must be a tuple or None.")
1039 1039
1040 1040 default_value = DefaultValueGenerator(*args, **kw)
1041 1041
1042 1042 super(Instance, self).__init__(default_value, **metadata)
1043 1043
1044 1044 def validate(self, obj, value):
1045 1045 if isinstance(value, self.klass):
1046 1046 return value
1047 1047 else:
1048 1048 self.error(obj, value)
1049 1049
1050 1050 def info(self):
1051 1051 if isinstance(self.klass, py3compat.string_types):
1052 1052 klass = self.klass
1053 1053 else:
1054 1054 klass = self.klass.__name__
1055 1055 result = class_of(klass)
1056 1056 if self.allow_none:
1057 1057 return result + ' or None'
1058 1058
1059 1059 return result
1060 1060
1061 1061 def instance_init(self):
1062 1062 self._resolve_classes()
1063 1063 super(Instance, self).instance_init()
1064 1064
1065 1065 def _resolve_classes(self):
1066 1066 if isinstance(self.klass, py3compat.string_types):
1067 1067 self.klass = self._resolve_string(self.klass)
1068 1068
1069 1069 def get_default_value(self):
1070 1070 """Instantiate a default value instance.
1071 1071
1072 1072 This is called when the containing HasTraits classes'
1073 1073 :meth:`__new__` method is called to ensure that a unique instance
1074 1074 is created for each HasTraits instance.
1075 1075 """
1076 1076 dv = self.default_value
1077 1077 if isinstance(dv, DefaultValueGenerator):
1078 1078 return dv.generate(self.klass)
1079 1079 else:
1080 1080 return dv
1081 1081
1082 1082
1083 1083 class ForwardDeclaredMixin(object):
1084 1084 """
1085 1085 Mixin for forward-declared versions of Instance and Type.
1086 1086 """
1087 1087 def _resolve_string(self, string):
1088 1088 """
1089 1089 Find the specified class name by looking for it in the module in which
1090 1090 our this_class attribute was defined.
1091 1091 """
1092 1092 modname = self.this_class.__module__
1093 1093 return import_item('.'.join([modname, string]))
1094 1094
1095 1095
1096 1096 class ForwardDeclaredType(ForwardDeclaredMixin, Type):
1097 1097 """
1098 1098 Forward-declared version of Type.
1099 1099 """
1100 1100 pass
1101 1101
1102 1102
1103 1103 class ForwardDeclaredInstance(ForwardDeclaredMixin, Instance):
1104 1104 """
1105 1105 Forward-declared version of Instance.
1106 1106 """
1107 1107 pass
1108 1108
1109 1109
1110 1110 class This(ClassBasedTraitType):
1111 1111 """A trait for instances of the class containing this trait.
1112 1112
1113 1113 Because how how and when class bodies are executed, the ``This``
1114 1114 trait can only have a default value of None. This, and because we
1115 1115 always validate default values, ``allow_none`` is *always* true.
1116 1116 """
1117 1117
1118 1118 info_text = 'an instance of the same type as the receiver or None'
1119 1119
1120 1120 def __init__(self, **metadata):
1121 1121 super(This, self).__init__(None, **metadata)
1122 1122
1123 1123 def validate(self, obj, value):
1124 1124 # What if value is a superclass of obj.__class__? This is
1125 1125 # complicated if it was the superclass that defined the This
1126 1126 # trait.
1127 1127 if isinstance(value, self.this_class) or (value is None):
1128 1128 return value
1129 1129 else:
1130 1130 self.error(obj, value)
1131 1131
1132 1132
1133 1133 class Union(TraitType):
1134 1134 """A trait type representing a Union type."""
1135 1135
1136 1136 def __init__(self, trait_types, **metadata):
1137 1137 """Construct a Union trait.
1138 1138
1139 1139 This trait allows values that are allowed by at least one of the
1140 1140 specified trait types. A Union traitlet cannot have metadata on
1141 1141 its own, besides the metadata of the listed types.
1142 1142
1143 1143 Parameters
1144 1144 ----------
1145 1145 trait_types: sequence
1146 1146 The list of trait types of length at least 1.
1147 1147
1148 1148 Notes
1149 1149 -----
1150 1150 Union([Float(), Bool(), Int()]) attempts to validate the provided values
1151 1151 with the validation function of Float, then Bool, and finally Int.
1152 1152 """
1153 1153 self.trait_types = trait_types
1154 1154 self.info_text = " or ".join([tt.info_text for tt in self.trait_types])
1155 1155 self.default_value = self.trait_types[0].get_default_value()
1156 1156 super(Union, self).__init__(**metadata)
1157 1157
1158 1158 def instance_init(self):
1159 1159 for trait_type in self.trait_types:
1160 1160 trait_type.name = self.name
1161 1161 trait_type.this_class = self.this_class
1162 1162 trait_type.instance_init()
1163 1163 super(Union, self).instance_init()
1164 1164
1165 1165 def validate(self, obj, value):
1166 1166 for trait_type in self.trait_types:
1167 1167 try:
1168 1168 v = trait_type._validate(obj, value)
1169 1169 self._metadata = trait_type._metadata
1170 1170 return v
1171 1171 except TraitError:
1172 1172 continue
1173 1173 self.error(obj, value)
1174 1174
1175 1175 def __or__(self, other):
1176 1176 if isinstance(other, Union):
1177 1177 return Union(self.trait_types + other.trait_types)
1178 1178 else:
1179 1179 return Union(self.trait_types + [other])
1180 1180
1181 1181 #-----------------------------------------------------------------------------
1182 1182 # Basic TraitTypes implementations/subclasses
1183 1183 #-----------------------------------------------------------------------------
1184 1184
1185 1185
1186 1186 class Any(TraitType):
1187 1187 default_value = None
1188 1188 info_text = 'any value'
1189 1189
1190 1190
1191 1191 class Int(TraitType):
1192 1192 """An int trait."""
1193 1193
1194 1194 default_value = 0
1195 1195 info_text = 'an int'
1196 1196
1197 1197 def validate(self, obj, value):
1198 1198 if isinstance(value, int):
1199 1199 return value
1200 1200 self.error(obj, value)
1201 1201
1202 1202 class CInt(Int):
1203 1203 """A casting version of the int trait."""
1204 1204
1205 1205 def validate(self, obj, value):
1206 1206 try:
1207 1207 return int(value)
1208 1208 except:
1209 1209 self.error(obj, value)
1210 1210
1211 1211 if py3compat.PY3:
1212 1212 Long, CLong = Int, CInt
1213 1213 Integer = Int
1214 1214 else:
1215 1215 class Long(TraitType):
1216 1216 """A long integer trait."""
1217 1217
1218 1218 default_value = 0
1219 1219 info_text = 'a long'
1220 1220
1221 1221 def validate(self, obj, value):
1222 1222 if isinstance(value, long):
1223 1223 return value
1224 1224 if isinstance(value, int):
1225 1225 return long(value)
1226 1226 self.error(obj, value)
1227 1227
1228 1228
1229 1229 class CLong(Long):
1230 1230 """A casting version of the long integer trait."""
1231 1231
1232 1232 def validate(self, obj, value):
1233 1233 try:
1234 1234 return long(value)
1235 1235 except:
1236 1236 self.error(obj, value)
1237 1237
1238 1238 class Integer(TraitType):
1239 1239 """An integer trait.
1240 1240
1241 1241 Longs that are unnecessary (<= sys.maxint) are cast to ints."""
1242 1242
1243 1243 default_value = 0
1244 1244 info_text = 'an integer'
1245 1245
1246 1246 def validate(self, obj, value):
1247 1247 if isinstance(value, int):
1248 1248 return value
1249 1249 if isinstance(value, long):
1250 1250 # downcast longs that fit in int:
1251 1251 # note that int(n > sys.maxint) returns a long, so
1252 1252 # we don't need a condition on this cast
1253 1253 return int(value)
1254 1254 if sys.platform == "cli":
1255 1255 from System import Int64
1256 1256 if isinstance(value, Int64):
1257 1257 return int(value)
1258 1258 self.error(obj, value)
1259 1259
1260 1260
1261 1261 class Float(TraitType):
1262 1262 """A float trait."""
1263 1263
1264 1264 default_value = 0.0
1265 1265 info_text = 'a float'
1266 1266
1267 1267 def validate(self, obj, value):
1268 1268 if isinstance(value, float):
1269 1269 return value
1270 1270 if isinstance(value, int):
1271 1271 return float(value)
1272 1272 self.error(obj, value)
1273 1273
1274 1274
1275 1275 class CFloat(Float):
1276 1276 """A casting version of the float trait."""
1277 1277
1278 1278 def validate(self, obj, value):
1279 1279 try:
1280 1280 return float(value)
1281 1281 except:
1282 1282 self.error(obj, value)
1283 1283
1284 1284 class Complex(TraitType):
1285 1285 """A trait for complex numbers."""
1286 1286
1287 1287 default_value = 0.0 + 0.0j
1288 1288 info_text = 'a complex number'
1289 1289
1290 1290 def validate(self, obj, value):
1291 1291 if isinstance(value, complex):
1292 1292 return value
1293 1293 if isinstance(value, (float, int)):
1294 1294 return complex(value)
1295 1295 self.error(obj, value)
1296 1296
1297 1297
1298 1298 class CComplex(Complex):
1299 1299 """A casting version of the complex number trait."""
1300 1300
1301 1301 def validate (self, obj, value):
1302 1302 try:
1303 1303 return complex(value)
1304 1304 except:
1305 1305 self.error(obj, value)
1306 1306
1307 1307 # We should always be explicit about whether we're using bytes or unicode, both
1308 1308 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
1309 1309 # we don't have a Str type.
1310 1310 class Bytes(TraitType):
1311 1311 """A trait for byte strings."""
1312 1312
1313 1313 default_value = b''
1314 1314 info_text = 'a bytes object'
1315 1315
1316 1316 def validate(self, obj, value):
1317 1317 if isinstance(value, bytes):
1318 1318 return value
1319 1319 self.error(obj, value)
1320 1320
1321 1321
1322 1322 class CBytes(Bytes):
1323 1323 """A casting version of the byte string trait."""
1324 1324
1325 1325 def validate(self, obj, value):
1326 1326 try:
1327 1327 return bytes(value)
1328 1328 except:
1329 1329 self.error(obj, value)
1330 1330
1331 1331
1332 1332 class Unicode(TraitType):
1333 1333 """A trait for unicode strings."""
1334 1334
1335 1335 default_value = u''
1336 1336 info_text = 'a unicode string'
1337 1337
1338 1338 def validate(self, obj, value):
1339 1339 if isinstance(value, py3compat.unicode_type):
1340 1340 return value
1341 1341 if isinstance(value, bytes):
1342 1342 try:
1343 1343 return value.decode('ascii', 'strict')
1344 1344 except UnicodeDecodeError:
1345 1345 msg = "Could not decode {!r} for unicode trait '{}' of {} instance."
1346 1346 raise TraitError(msg.format(value, self.name, class_of(obj)))
1347 1347 self.error(obj, value)
1348 1348
1349 1349
1350 1350 class CUnicode(Unicode):
1351 1351 """A casting version of the unicode trait."""
1352 1352
1353 1353 def validate(self, obj, value):
1354 1354 try:
1355 1355 return py3compat.unicode_type(value)
1356 1356 except:
1357 1357 self.error(obj, value)
1358 1358
1359 1359
1360 1360 class ObjectName(TraitType):
1361 1361 """A string holding a valid object name in this version of Python.
1362 1362
1363 1363 This does not check that the name exists in any scope."""
1364 1364 info_text = "a valid object identifier in Python"
1365 1365
1366 1366 if py3compat.PY3:
1367 1367 # Python 3:
1368 1368 coerce_str = staticmethod(lambda _,s: s)
1369 1369
1370 1370 else:
1371 1371 # Python 2:
1372 1372 def coerce_str(self, obj, value):
1373 1373 "In Python 2, coerce ascii-only unicode to str"
1374 1374 if isinstance(value, unicode):
1375 1375 try:
1376 1376 return str(value)
1377 1377 except UnicodeEncodeError:
1378 1378 self.error(obj, value)
1379 1379 return value
1380 1380
1381 1381 def validate(self, obj, value):
1382 1382 value = self.coerce_str(obj, value)
1383 1383
1384 1384 if isinstance(value, string_types) and py3compat.isidentifier(value):
1385 1385 return value
1386 1386 self.error(obj, value)
1387 1387
1388 1388 class DottedObjectName(ObjectName):
1389 1389 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1390 1390 def validate(self, obj, value):
1391 1391 value = self.coerce_str(obj, value)
1392 1392
1393 1393 if isinstance(value, string_types) and py3compat.isidentifier(value, dotted=True):
1394 1394 return value
1395 1395 self.error(obj, value)
1396 1396
1397 1397
1398 1398 class Bool(TraitType):
1399 1399 """A boolean (True, False) trait."""
1400 1400
1401 1401 default_value = False
1402 1402 info_text = 'a boolean'
1403 1403
1404 1404 def validate(self, obj, value):
1405 1405 if isinstance(value, bool):
1406 1406 return value
1407 1407 self.error(obj, value)
1408 1408
1409 1409
1410 1410 class CBool(Bool):
1411 1411 """A casting version of the boolean trait."""
1412 1412
1413 1413 def validate(self, obj, value):
1414 1414 try:
1415 1415 return bool(value)
1416 1416 except:
1417 1417 self.error(obj, value)
1418 1418
1419 1419
1420 1420 class Enum(TraitType):
1421 1421 """An enum that whose value must be in a given sequence."""
1422 1422
1423 1423 def __init__(self, values, default_value=None, **metadata):
1424 1424 self.values = values
1425 1425 super(Enum, self).__init__(default_value, **metadata)
1426 1426
1427 1427 def validate(self, obj, value):
1428 1428 if value in self.values:
1429 1429 return value
1430 1430 self.error(obj, value)
1431 1431
1432 1432 def info(self):
1433 1433 """ Returns a description of the trait."""
1434 1434 result = 'any of ' + repr(self.values)
1435 1435 if self.allow_none:
1436 1436 return result + ' or None'
1437 1437 return result
1438 1438
1439 1439 class CaselessStrEnum(Enum):
1440 1440 """An enum of strings that are caseless in validate."""
1441 1441
1442 1442 def validate(self, obj, value):
1443 1443 if not isinstance(value, py3compat.string_types):
1444 1444 self.error(obj, value)
1445 1445
1446 1446 for v in self.values:
1447 1447 if v.lower() == value.lower():
1448 1448 return v
1449 1449 self.error(obj, value)
1450 1450
1451 1451 class Container(Instance):
1452 1452 """An instance of a container (list, set, etc.)
1453 1453
1454 1454 To be subclassed by overriding klass.
1455 1455 """
1456 1456 klass = None
1457 1457 _cast_types = ()
1458 1458 _valid_defaults = SequenceTypes
1459 1459 _trait = None
1460 1460
1461 1461 def __init__(self, trait=None, default_value=None, **metadata):
1462 1462 """Create a container trait type from a list, set, or tuple.
1463 1463
1464 1464 The default value is created by doing ``List(default_value)``,
1465 1465 which creates a copy of the ``default_value``.
1466 1466
1467 1467 ``trait`` can be specified, which restricts the type of elements
1468 1468 in the container to that TraitType.
1469 1469
1470 1470 If only one arg is given and it is not a Trait, it is taken as
1471 1471 ``default_value``:
1472 1472
1473 1473 ``c = List([1,2,3])``
1474 1474
1475 1475 Parameters
1476 1476 ----------
1477 1477
1478 1478 trait : TraitType [ optional ]
1479 1479 the type for restricting the contents of the Container. If unspecified,
1480 1480 types are not checked.
1481 1481
1482 1482 default_value : SequenceType [ optional ]
1483 1483 The default value for the Trait. Must be list/tuple/set, and
1484 1484 will be cast to the container type.
1485 1485
1486 1486 allow_none : bool [ default False ]
1487 1487 Whether to allow the value to be None
1488 1488
1489 1489 **metadata : any
1490 1490 further keys for extensions to the Trait (e.g. config)
1491 1491
1492 1492 """
1493 1493 # allow List([values]):
1494 1494 if default_value is None and not is_trait(trait):
1495 1495 default_value = trait
1496 1496 trait = None
1497 1497
1498 1498 if default_value is None:
1499 1499 args = ()
1500 1500 elif isinstance(default_value, self._valid_defaults):
1501 1501 args = (default_value,)
1502 1502 else:
1503 1503 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1504 1504
1505 1505 if is_trait(trait):
1506 1506 self._trait = trait() if isinstance(trait, type) else trait
1507 1507 self._trait.name = 'element'
1508 1508 elif trait is not None:
1509 1509 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1510 1510
1511 1511 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1512 1512
1513 1513 def element_error(self, obj, element, validator):
1514 1514 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1515 1515 % (self.name, class_of(obj), validator.info(), repr_type(element))
1516 1516 raise TraitError(e)
1517 1517
1518 1518 def validate(self, obj, value):
1519 1519 if isinstance(value, self._cast_types):
1520 1520 value = self.klass(value)
1521 1521 value = super(Container, self).validate(obj, value)
1522 1522 if value is None:
1523 1523 return value
1524 1524
1525 1525 value = self.validate_elements(obj, value)
1526 1526
1527 1527 return value
1528 1528
1529 1529 def validate_elements(self, obj, value):
1530 1530 validated = []
1531 1531 if self._trait is None or isinstance(self._trait, Any):
1532 1532 return value
1533 1533 for v in value:
1534 1534 try:
1535 1535 v = self._trait._validate(obj, v)
1536 1536 except TraitError:
1537 1537 self.element_error(obj, v, self._trait)
1538 1538 else:
1539 1539 validated.append(v)
1540 1540 return self.klass(validated)
1541 1541
1542 1542 def instance_init(self):
1543 1543 if isinstance(self._trait, TraitType):
1544 1544 self._trait.this_class = self.this_class
1545 1545 self._trait.instance_init()
1546 1546 super(Container, self).instance_init()
1547 1547
1548 1548
1549 1549 class List(Container):
1550 1550 """An instance of a Python list."""
1551 1551 klass = list
1552 1552 _cast_types = (tuple,)
1553 1553
1554 1554 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxsize, **metadata):
1555 1555 """Create a List trait type from a list, set, or tuple.
1556 1556
1557 1557 The default value is created by doing ``List(default_value)``,
1558 1558 which creates a copy of the ``default_value``.
1559 1559
1560 1560 ``trait`` can be specified, which restricts the type of elements
1561 1561 in the container to that TraitType.
1562 1562
1563 1563 If only one arg is given and it is not a Trait, it is taken as
1564 1564 ``default_value``:
1565 1565
1566 1566 ``c = List([1,2,3])``
1567 1567
1568 1568 Parameters
1569 1569 ----------
1570 1570
1571 1571 trait : TraitType [ optional ]
1572 1572 the type for restricting the contents of the Container. If unspecified,
1573 1573 types are not checked.
1574 1574
1575 1575 default_value : SequenceType [ optional ]
1576 1576 The default value for the Trait. Must be list/tuple/set, and
1577 1577 will be cast to the container type.
1578 1578
1579 1579 minlen : Int [ default 0 ]
1580 1580 The minimum length of the input list
1581 1581
1582 1582 maxlen : Int [ default sys.maxsize ]
1583 1583 The maximum length of the input list
1584 1584
1585 1585 allow_none : bool [ default False ]
1586 1586 Whether to allow the value to be None
1587 1587
1588 1588 **metadata : any
1589 1589 further keys for extensions to the Trait (e.g. config)
1590 1590
1591 1591 """
1592 1592 self._minlen = minlen
1593 1593 self._maxlen = maxlen
1594 1594 super(List, self).__init__(trait=trait, default_value=default_value,
1595 1595 **metadata)
1596 1596
1597 1597 def length_error(self, obj, value):
1598 1598 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1599 1599 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1600 1600 raise TraitError(e)
1601 1601
1602 1602 def validate_elements(self, obj, value):
1603 1603 length = len(value)
1604 1604 if length < self._minlen or length > self._maxlen:
1605 1605 self.length_error(obj, value)
1606 1606
1607 1607 return super(List, self).validate_elements(obj, value)
1608 1608
1609 1609 def validate(self, obj, value):
1610 1610 value = super(List, self).validate(obj, value)
1611 1611 value = self.validate_elements(obj, value)
1612 1612 return value
1613 1613
1614 1614
1615 1615 class Set(List):
1616 1616 """An instance of a Python set."""
1617 1617 klass = set
1618 1618 _cast_types = (tuple, list)
1619 1619
1620 1620
1621 1621 class Tuple(Container):
1622 1622 """An instance of a Python tuple."""
1623 1623 klass = tuple
1624 1624 _cast_types = (list,)
1625 1625
1626 1626 def __init__(self, *traits, **metadata):
1627 1627 """Tuple(*traits, default_value=None, **medatata)
1628 1628
1629 1629 Create a tuple from a list, set, or tuple.
1630 1630
1631 1631 Create a fixed-type tuple with Traits:
1632 1632
1633 1633 ``t = Tuple(Int, Str, CStr)``
1634 1634
1635 1635 would be length 3, with Int,Str,CStr for each element.
1636 1636
1637 1637 If only one arg is given and it is not a Trait, it is taken as
1638 1638 default_value:
1639 1639
1640 1640 ``t = Tuple((1,2,3))``
1641 1641
1642 1642 Otherwise, ``default_value`` *must* be specified by keyword.
1643 1643
1644 1644 Parameters
1645 1645 ----------
1646 1646
1647 1647 *traits : TraitTypes [ optional ]
1648 1648 the types for restricting the contents of the Tuple. If unspecified,
1649 1649 types are not checked. If specified, then each positional argument
1650 1650 corresponds to an element of the tuple. Tuples defined with traits
1651 1651 are of fixed length.
1652 1652
1653 1653 default_value : SequenceType [ optional ]
1654 1654 The default value for the Tuple. Must be list/tuple/set, and
1655 1655 will be cast to a tuple. If `traits` are specified, the
1656 1656 `default_value` must conform to the shape and type they specify.
1657 1657
1658 1658 allow_none : bool [ default False ]
1659 1659 Whether to allow the value to be None
1660 1660
1661 1661 **metadata : any
1662 1662 further keys for extensions to the Trait (e.g. config)
1663 1663
1664 1664 """
1665 1665 default_value = metadata.pop('default_value', None)
1666 1666
1667 1667 # allow Tuple((values,)):
1668 1668 if len(traits) == 1 and default_value is None and not is_trait(traits[0]):
1669 1669 default_value = traits[0]
1670 1670 traits = ()
1671 1671
1672 1672 if default_value is None:
1673 1673 args = ()
1674 1674 elif isinstance(default_value, self._valid_defaults):
1675 1675 args = (default_value,)
1676 1676 else:
1677 1677 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1678 1678
1679 1679 self._traits = []
1680 1680 for trait in traits:
1681 1681 t = trait() if isinstance(trait, type) else trait
1682 1682 t.name = 'element'
1683 1683 self._traits.append(t)
1684 1684
1685 1685 if self._traits and default_value is None:
1686 1686 # don't allow default to be an empty container if length is specified
1687 1687 args = None
1688 1688 super(Container,self).__init__(klass=self.klass, args=args, **metadata)
1689 1689
1690 1690 def validate_elements(self, obj, value):
1691 1691 if not self._traits:
1692 1692 # nothing to validate
1693 1693 return value
1694 1694 if len(value) != len(self._traits):
1695 1695 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1696 1696 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1697 1697 raise TraitError(e)
1698 1698
1699 1699 validated = []
1700 1700 for t, v in zip(self._traits, value):
1701 1701 try:
1702 1702 v = t._validate(obj, v)
1703 1703 except TraitError:
1704 1704 self.element_error(obj, v, t)
1705 1705 else:
1706 1706 validated.append(v)
1707 1707 return tuple(validated)
1708 1708
1709 1709 def instance_init(self):
1710 1710 for trait in self._traits:
1711 1711 if isinstance(trait, TraitType):
1712 1712 trait.this_class = self.this_class
1713 1713 trait.instance_init()
1714 1714 super(Container, self).instance_init()
1715 1715
1716 1716
1717 1717 class Dict(Instance):
1718 1718 """An instance of a Python dict."""
1719 1719 _trait = None
1720 1720
1721 1721 def __init__(self, trait=None, default_value=NoDefaultSpecified, **metadata):
1722 1722 """Create a dict trait type from a dict.
1723 1723
1724 1724 The default value is created by doing ``dict(default_value)``,
1725 1725 which creates a copy of the ``default_value``.
1726 1726
1727 1727 trait : TraitType [ optional ]
1728 1728 the type for restricting the contents of the Container. If unspecified,
1729 1729 types are not checked.
1730 1730
1731 1731 default_value : SequenceType [ optional ]
1732 1732 The default value for the Dict. Must be dict, tuple, or None, and
1733 1733 will be cast to a dict if not None. If `trait` is specified, the
1734 1734 `default_value` must conform to the constraints it specifies.
1735 1735
1736 1736 allow_none : bool [ default False ]
1737 1737 Whether to allow the value to be None
1738 1738
1739 1739 """
1740 1740 if default_value is NoDefaultSpecified and trait is not None:
1741 1741 if not is_trait(trait):
1742 1742 default_value = trait
1743 1743 trait = None
1744 1744 if default_value is NoDefaultSpecified:
1745 1745 default_value = {}
1746 1746 if default_value is None:
1747 1747 args = None
1748 1748 elif isinstance(default_value, dict):
1749 1749 args = (default_value,)
1750 1750 elif isinstance(default_value, SequenceTypes):
1751 1751 args = (default_value,)
1752 1752 else:
1753 1753 raise TypeError('default value of Dict was %s' % default_value)
1754 1754
1755 1755 if is_trait(trait):
1756 1756 self._trait = trait() if isinstance(trait, type) else trait
1757 1757 self._trait.name = 'element'
1758 1758 elif trait is not None:
1759 1759 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1760 1760
1761 1761 super(Dict,self).__init__(klass=dict, args=args, **metadata)
1762 1762
1763 1763 def element_error(self, obj, element, validator):
1764 1764 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1765 1765 % (self.name, class_of(obj), validator.info(), repr_type(element))
1766 1766 raise TraitError(e)
1767 1767
1768 1768 def validate(self, obj, value):
1769 1769 value = super(Dict, self).validate(obj, value)
1770 1770 if value is None:
1771 1771 return value
1772 1772 value = self.validate_elements(obj, value)
1773 1773 return value
1774 1774
1775 1775 def validate_elements(self, obj, value):
1776 1776 if self._trait is None or isinstance(self._trait, Any):
1777 1777 return value
1778 1778 validated = {}
1779 1779 for key in value:
1780 1780 v = value[key]
1781 1781 try:
1782 1782 v = self._trait._validate(obj, v)
1783 1783 except TraitError:
1784 1784 self.element_error(obj, v, self._trait)
1785 1785 else:
1786 1786 validated[key] = v
1787 1787 return self.klass(validated)
1788 1788
1789 1789 def instance_init(self):
1790 1790 if isinstance(self._trait, TraitType):
1791 1791 self._trait.this_class = self.this_class
1792 1792 self._trait.instance_init()
1793 1793 super(Dict, self).instance_init()
1794 1794
1795 1795
1796 1796 class EventfulDict(Instance):
1797 1797 """An instance of an EventfulDict."""
1798 1798
1799 1799 def __init__(self, default_value={}, **metadata):
1800 1800 """Create a EventfulDict trait type from a dict.
1801 1801
1802 1802 The default value is created by doing
1803 1803 ``eventful.EvenfulDict(default_value)``, which creates a copy of the
1804 1804 ``default_value``.
1805 1805 """
1806 1806 if default_value is None:
1807 1807 args = None
1808 1808 elif isinstance(default_value, dict):
1809 1809 args = (default_value,)
1810 1810 elif isinstance(default_value, SequenceTypes):
1811 1811 args = (default_value,)
1812 1812 else:
1813 1813 raise TypeError('default value of EventfulDict was %s' % default_value)
1814 1814
1815 1815 super(EventfulDict, self).__init__(klass=eventful.EventfulDict, args=args,
1816 1816 **metadata)
1817 1817
1818 1818
1819 1819 class EventfulList(Instance):
1820 1820 """An instance of an EventfulList."""
1821 1821
1822 1822 def __init__(self, default_value=None, **metadata):
1823 1823 """Create a EventfulList trait type from a dict.
1824 1824
1825 1825 The default value is created by doing
1826 1826 ``eventful.EvenfulList(default_value)``, which creates a copy of the
1827 1827 ``default_value``.
1828 1828 """
1829 1829 if default_value is None:
1830 1830 args = ((),)
1831 1831 else:
1832 1832 args = (default_value,)
1833 1833
1834 1834 super(EventfulList, self).__init__(klass=eventful.EventfulList, args=args,
1835 1835 **metadata)
1836 1836
1837 1837
1838 1838 class TCPAddress(TraitType):
1839 1839 """A trait for an (ip, port) tuple.
1840 1840
1841 1841 This allows for both IPv4 IP addresses as well as hostnames.
1842 1842 """
1843 1843
1844 1844 default_value = ('127.0.0.1', 0)
1845 1845 info_text = 'an (ip, port) tuple'
1846 1846
1847 1847 def validate(self, obj, value):
1848 1848 if isinstance(value, tuple):
1849 1849 if len(value) == 2:
1850 1850 if isinstance(value[0], py3compat.string_types) and isinstance(value[1], int):
1851 1851 port = value[1]
1852 1852 if port >= 0 and port <= 65535:
1853 1853 return value
1854 1854 self.error(obj, value)
1855 1855
1856 1856 class CRegExp(TraitType):
1857 1857 """A casting compiled regular expression trait.
1858 1858
1859 1859 Accepts both strings and compiled regular expressions. The resulting
1860 1860 attribute will be a compiled regular expression."""
1861 1861
1862 1862 info_text = 'a regular expression'
1863 1863
1864 1864 def validate(self, obj, value):
1865 1865 try:
1866 1866 return re.compile(value)
1867 1867 except:
1868 1868 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now