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