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