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