##// END OF EJS Templates
fix handling of unicode in KV loader...
MinRK -
Show More
@@ -1,541 +1,555 b''
1 1 """A simple configuration system.
2 2
3 3 Authors
4 4 -------
5 5 * Brian Granger
6 6 * Fernando Perez
7 7 * Min RK
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2008-2011 The IPython Development Team
12 12 #
13 13 # Distributed under the terms of the BSD License. The full license is in
14 14 # the file COPYING, distributed as part of this software.
15 15 #-----------------------------------------------------------------------------
16 16
17 17 #-----------------------------------------------------------------------------
18 18 # Imports
19 19 #-----------------------------------------------------------------------------
20 20
21 21 import __builtin__
22 22 import re
23 23 import sys
24 24
25 25 from IPython.external import argparse
26 26 from IPython.utils.path import filefind, get_ipython_dir
27 27
28 28 #-----------------------------------------------------------------------------
29 29 # Exceptions
30 30 #-----------------------------------------------------------------------------
31 31
32 32
33 33 class ConfigError(Exception):
34 34 pass
35 35
36 36
37 37 class ConfigLoaderError(ConfigError):
38 38 pass
39 39
40 40 class ArgumentError(ConfigLoaderError):
41 41 pass
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Argparse fix
45 45 #-----------------------------------------------------------------------------
46 46
47 47 # Unfortunately argparse by default prints help messages to stderr instead of
48 48 # stdout. This makes it annoying to capture long help screens at the command
49 49 # line, since one must know how to pipe stderr, which many users don't know how
50 50 # to do. So we override the print_help method with one that defaults to
51 51 # stdout and use our class instead.
52 52
53 53 class ArgumentParser(argparse.ArgumentParser):
54 54 """Simple argparse subclass that prints help to stdout by default."""
55 55
56 56 def print_help(self, file=None):
57 57 if file is None:
58 58 file = sys.stdout
59 59 return super(ArgumentParser, self).print_help(file)
60 60
61 61 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
62 62
63 63 #-----------------------------------------------------------------------------
64 64 # Config class for holding config information
65 65 #-----------------------------------------------------------------------------
66 66
67 67
68 68 class Config(dict):
69 69 """An attribute based dict that can do smart merges."""
70 70
71 71 def __init__(self, *args, **kwds):
72 72 dict.__init__(self, *args, **kwds)
73 73 # This sets self.__dict__ = self, but it has to be done this way
74 74 # because we are also overriding __setattr__.
75 75 dict.__setattr__(self, '__dict__', self)
76 76
77 77 def _merge(self, other):
78 78 to_update = {}
79 79 for k, v in other.iteritems():
80 80 if not self.has_key(k):
81 81 to_update[k] = v
82 82 else: # I have this key
83 83 if isinstance(v, Config):
84 84 # Recursively merge common sub Configs
85 85 self[k]._merge(v)
86 86 else:
87 87 # Plain updates for non-Configs
88 88 to_update[k] = v
89 89
90 90 self.update(to_update)
91 91
92 92 def _is_section_key(self, key):
93 93 if key[0].upper()==key[0] and not key.startswith('_'):
94 94 return True
95 95 else:
96 96 return False
97 97
98 98 def __contains__(self, key):
99 99 if self._is_section_key(key):
100 100 return True
101 101 else:
102 102 return super(Config, self).__contains__(key)
103 103 # .has_key is deprecated for dictionaries.
104 104 has_key = __contains__
105 105
106 106 def _has_section(self, key):
107 107 if self._is_section_key(key):
108 108 if super(Config, self).__contains__(key):
109 109 return True
110 110 return False
111 111
112 112 def copy(self):
113 113 return type(self)(dict.copy(self))
114 114
115 115 def __copy__(self):
116 116 return self.copy()
117 117
118 118 def __deepcopy__(self, memo):
119 119 import copy
120 120 return type(self)(copy.deepcopy(self.items()))
121 121
122 122 def __getitem__(self, key):
123 123 # We cannot use directly self._is_section_key, because it triggers
124 124 # infinite recursion on top of PyPy. Instead, we manually fish the
125 125 # bound method.
126 126 is_section_key = self.__class__._is_section_key.__get__(self)
127 127
128 128 # Because we use this for an exec namespace, we need to delegate
129 129 # the lookup of names in __builtin__ to itself. This means
130 130 # that you can't have section or attribute names that are
131 131 # builtins.
132 132 try:
133 133 return getattr(__builtin__, key)
134 134 except AttributeError:
135 135 pass
136 136 if is_section_key(key):
137 137 try:
138 138 return dict.__getitem__(self, key)
139 139 except KeyError:
140 140 c = Config()
141 141 dict.__setitem__(self, key, c)
142 142 return c
143 143 else:
144 144 return dict.__getitem__(self, key)
145 145
146 146 def __setitem__(self, key, value):
147 147 # Don't allow names in __builtin__ to be modified.
148 148 if hasattr(__builtin__, key):
149 149 raise ConfigError('Config variable names cannot have the same name '
150 150 'as a Python builtin: %s' % key)
151 151 if self._is_section_key(key):
152 152 if not isinstance(value, Config):
153 153 raise ValueError('values whose keys begin with an uppercase '
154 154 'char must be Config instances: %r, %r' % (key, value))
155 155 else:
156 156 dict.__setitem__(self, key, value)
157 157
158 158 def __getattr__(self, key):
159 159 try:
160 160 return self.__getitem__(key)
161 161 except KeyError, e:
162 162 raise AttributeError(e)
163 163
164 164 def __setattr__(self, key, value):
165 165 try:
166 166 self.__setitem__(key, value)
167 167 except KeyError, e:
168 168 raise AttributeError(e)
169 169
170 170 def __delattr__(self, key):
171 171 try:
172 172 dict.__delitem__(self, key)
173 173 except KeyError, e:
174 174 raise AttributeError(e)
175 175
176 176
177 177 #-----------------------------------------------------------------------------
178 178 # Config loading classes
179 179 #-----------------------------------------------------------------------------
180 180
181 181
182 182 class ConfigLoader(object):
183 183 """A object for loading configurations from just about anywhere.
184 184
185 185 The resulting configuration is packaged as a :class:`Struct`.
186 186
187 187 Notes
188 188 -----
189 189 A :class:`ConfigLoader` does one thing: load a config from a source
190 190 (file, command line arguments) and returns the data as a :class:`Struct`.
191 191 There are lots of things that :class:`ConfigLoader` does not do. It does
192 192 not implement complex logic for finding config files. It does not handle
193 193 default values or merge multiple configs. These things need to be
194 194 handled elsewhere.
195 195 """
196 196
197 197 def __init__(self):
198 198 """A base class for config loaders.
199 199
200 200 Examples
201 201 --------
202 202
203 203 >>> cl = ConfigLoader()
204 204 >>> config = cl.load_config()
205 205 >>> config
206 206 {}
207 207 """
208 208 self.clear()
209 209
210 210 def clear(self):
211 211 self.config = Config()
212 212
213 213 def load_config(self):
214 214 """Load a config from somewhere, return a :class:`Config` instance.
215 215
216 216 Usually, this will cause self.config to be set and then returned.
217 217 However, in most cases, :meth:`ConfigLoader.clear` should be called
218 218 to erase any previous state.
219 219 """
220 220 self.clear()
221 221 return self.config
222 222
223 223
224 224 class FileConfigLoader(ConfigLoader):
225 225 """A base class for file based configurations.
226 226
227 227 As we add more file based config loaders, the common logic should go
228 228 here.
229 229 """
230 230 pass
231 231
232 232
233 233 class PyFileConfigLoader(FileConfigLoader):
234 234 """A config loader for pure python files.
235 235
236 236 This calls execfile on a plain python file and looks for attributes
237 237 that are all caps. These attribute are added to the config Struct.
238 238 """
239 239
240 240 def __init__(self, filename, path=None):
241 241 """Build a config loader for a filename and path.
242 242
243 243 Parameters
244 244 ----------
245 245 filename : str
246 246 The file name of the config file.
247 247 path : str, list, tuple
248 248 The path to search for the config file on, or a sequence of
249 249 paths to try in order.
250 250 """
251 251 super(PyFileConfigLoader, self).__init__()
252 252 self.filename = filename
253 253 self.path = path
254 254 self.full_filename = ''
255 255 self.data = None
256 256
257 257 def load_config(self):
258 258 """Load the config from a file and return it as a Struct."""
259 259 self.clear()
260 260 self._find_file()
261 261 self._read_file_as_dict()
262 262 self._convert_to_config()
263 263 return self.config
264 264
265 265 def _find_file(self):
266 266 """Try to find the file by searching the paths."""
267 267 self.full_filename = filefind(self.filename, self.path)
268 268
269 269 def _read_file_as_dict(self):
270 270 """Load the config file into self.config, with recursive loading."""
271 271 # This closure is made available in the namespace that is used
272 272 # to exec the config file. It allows users to call
273 273 # load_subconfig('myconfig.py') to load config files recursively.
274 274 # It needs to be a closure because it has references to self.path
275 275 # and self.config. The sub-config is loaded with the same path
276 276 # as the parent, but it uses an empty config which is then merged
277 277 # with the parents.
278 278
279 279 # If a profile is specified, the config file will be loaded
280 280 # from that profile
281 281
282 282 def load_subconfig(fname, profile=None):
283 283 # import here to prevent circular imports
284 284 from IPython.core.profiledir import ProfileDir, ProfileDirError
285 285 if profile is not None:
286 286 try:
287 287 profile_dir = ProfileDir.find_profile_dir_by_name(
288 288 get_ipython_dir(),
289 289 profile,
290 290 )
291 291 except ProfileDirError:
292 292 return
293 293 path = profile_dir.location
294 294 else:
295 295 path = self.path
296 296 loader = PyFileConfigLoader(fname, path)
297 297 try:
298 298 sub_config = loader.load_config()
299 299 except IOError:
300 300 # Pass silently if the sub config is not there. This happens
301 301 # when a user s using a profile, but not the default config.
302 302 pass
303 303 else:
304 304 self.config._merge(sub_config)
305 305
306 306 # Again, this needs to be a closure and should be used in config
307 307 # files to get the config being loaded.
308 308 def get_config():
309 309 return self.config
310 310
311 311 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
312 312 fs_encoding = sys.getfilesystemencoding() or 'ascii'
313 313 conf_filename = self.full_filename.encode(fs_encoding)
314 314 execfile(conf_filename, namespace)
315 315
316 316 def _convert_to_config(self):
317 317 if self.data is None:
318 318 ConfigLoaderError('self.data does not exist')
319 319
320 320
321 321 class CommandLineConfigLoader(ConfigLoader):
322 322 """A config loader for command line arguments.
323 323
324 324 As we add more command line based loaders, the common logic should go
325 325 here.
326 326 """
327 327
328 328 kv_pattern = re.compile(r'[A-Za-z]\w*(\.\w+)*\=.*')
329 329 flag_pattern = re.compile(r'\-\-\w+(\-\w)*')
330 330
331 331 class KeyValueConfigLoader(CommandLineConfigLoader):
332 332 """A config loader that loads key value pairs from the command line.
333 333
334 334 This allows command line options to be gives in the following form::
335 335
336 336 ipython Global.profile="foo" InteractiveShell.autocall=False
337 337 """
338 338
339 339 def __init__(self, argv=None, aliases=None, flags=None):
340 340 """Create a key value pair config loader.
341 341
342 342 Parameters
343 343 ----------
344 344 argv : list
345 345 A list that has the form of sys.argv[1:] which has unicode
346 346 elements of the form u"key=value". If this is None (default),
347 347 then sys.argv[1:] will be used.
348 348 aliases : dict
349 349 A dict of aliases for configurable traits.
350 350 Keys are the short aliases, Values are the resolved trait.
351 351 Of the form: `{'alias' : 'Configurable.trait'}`
352 352 flags : dict
353 353 A dict of flags, keyed by str name. Vaues can be Config objects,
354 354 dicts, or "key=value" strings. If Config or dict, when the flag
355 355 is triggered, The flag is loaded as `self.config.update(m)`.
356 356
357 357 Returns
358 358 -------
359 359 config : Config
360 360 The resulting Config object.
361 361
362 362 Examples
363 363 --------
364 364
365 365 >>> from IPython.config.loader import KeyValueConfigLoader
366 366 >>> cl = KeyValueConfigLoader()
367 367 >>> cl.load_config(["foo='bar'","A.name='brian'","B.number=0"])
368 368 {'A': {'name': 'brian'}, 'B': {'number': 0}, 'foo': 'bar'}
369 369 """
370 370 self.clear()
371 371 if argv is None:
372 372 argv = sys.argv[1:]
373 373 self.argv = argv
374 374 self.aliases = aliases or {}
375 375 self.flags = flags or {}
376 376
377 377
378 378 def clear(self):
379 379 super(KeyValueConfigLoader, self).clear()
380 380 self.extra_args = []
381 381
382
382
383 def _decode_argv(self, argv, enc=None):
384 """decode argv if bytes, using stin.encoding, falling back on default enc"""
385 uargv = []
386 if enc is None:
387 enc = sys.stdin.encoding or sys.getdefaultencoding()
388 for arg in argv:
389 if not isinstance(arg, unicode):
390 # only decode if not already decoded
391 arg = arg.decode(enc)
392 uargv.append(arg)
393 return uargv
394
395
383 396 def load_config(self, argv=None, aliases=None, flags=None):
384 397 """Parse the configuration and generate the Config object.
385 398
386 399 After loading, any arguments that are not key-value or
387 400 flags will be stored in self.extra_args - a list of
388 401 unparsed command-line arguments. This is used for
389 402 arguments such as input files or subcommands.
390 403
391 404 Parameters
392 405 ----------
393 406 argv : list, optional
394 407 A list that has the form of sys.argv[1:] which has unicode
395 408 elements of the form u"key=value". If this is None (default),
396 409 then self.argv will be used.
397 410 aliases : dict
398 411 A dict of aliases for configurable traits.
399 412 Keys are the short aliases, Values are the resolved trait.
400 413 Of the form: `{'alias' : 'Configurable.trait'}`
401 414 flags : dict
402 415 A dict of flags, keyed by str name. Values can be Config objects
403 416 or dicts. When the flag is triggered, The config is loaded as
404 417 `self.config.update(cfg)`.
405 418 """
406 419 from IPython.config.configurable import Configurable
407 420
408 421 self.clear()
409 422 if argv is None:
410 423 argv = self.argv
411 424 if aliases is None:
412 425 aliases = self.aliases
413 426 if flags is None:
414 427 flags = self.flags
415 428
416 for item in argv:
429 for item in self._decode_argv(argv):
417 430 if kv_pattern.match(item):
418 431 lhs,rhs = item.split('=',1)
419 432 # Substitute longnames for aliases.
420 433 if lhs in aliases:
421 434 lhs = aliases[lhs]
422 435 exec_str = 'self.config.' + lhs + '=' + rhs
423 436 try:
424 437 # Try to see if regular Python syntax will work. This
425 438 # won't handle strings as the quote marks are removed
426 439 # by the system shell.
427 440 exec exec_str in locals(), globals()
428 441 except (NameError, SyntaxError):
429 442 # This case happens if the rhs is a string but without
430 # the quote marks. We add the quote marks and see if
443 # the quote marks. Use repr, to get quote marks, and
444 # 'u' prefix and see if
431 445 # it succeeds. If it still fails, we let it raise.
432 exec_str = 'self.config.' + lhs + '="' + rhs + '"'
446 exec_str = u'self.config.' + lhs + '=' + repr(rhs)
433 447 exec exec_str in locals(), globals()
434 448 elif flag_pattern.match(item):
435 449 # trim leading '--'
436 450 m = item[2:]
437 451 cfg,_ = flags.get(m, (None,None))
438 452 if cfg is None:
439 453 raise ArgumentError("Unrecognized flag: %r"%item)
440 454 elif isinstance(cfg, (dict, Config)):
441 455 # don't clobber whole config sections, update
442 456 # each section from config:
443 457 for sec,c in cfg.iteritems():
444 458 self.config[sec].update(c)
445 459 else:
446 460 raise ValueError("Invalid flag: %r"%flag)
447 461 elif item.startswith('-'):
448 462 # this shouldn't ever be valid
449 463 raise ArgumentError("Invalid argument: %r"%item)
450 464 else:
451 465 # keep all args that aren't valid in a list,
452 466 # in case our parent knows what to do with them.
453 467 self.extra_args.append(item)
454 468 return self.config
455 469
456 470 class ArgParseConfigLoader(CommandLineConfigLoader):
457 471 """A loader that uses the argparse module to load from the command line."""
458 472
459 473 def __init__(self, argv=None, *parser_args, **parser_kw):
460 474 """Create a config loader for use with argparse.
461 475
462 476 Parameters
463 477 ----------
464 478
465 479 argv : optional, list
466 480 If given, used to read command-line arguments from, otherwise
467 481 sys.argv[1:] is used.
468 482
469 483 parser_args : tuple
470 484 A tuple of positional arguments that will be passed to the
471 485 constructor of :class:`argparse.ArgumentParser`.
472 486
473 487 parser_kw : dict
474 488 A tuple of keyword arguments that will be passed to the
475 489 constructor of :class:`argparse.ArgumentParser`.
476 490
477 491 Returns
478 492 -------
479 493 config : Config
480 494 The resulting Config object.
481 495 """
482 496 super(CommandLineConfigLoader, self).__init__()
483 497 if argv == None:
484 498 argv = sys.argv[1:]
485 499 self.argv = argv
486 500 self.parser_args = parser_args
487 501 self.version = parser_kw.pop("version", None)
488 502 kwargs = dict(argument_default=argparse.SUPPRESS)
489 503 kwargs.update(parser_kw)
490 504 self.parser_kw = kwargs
491 505
492 506 def load_config(self, argv=None):
493 507 """Parse command line arguments and return as a Config object.
494 508
495 509 Parameters
496 510 ----------
497 511
498 512 args : optional, list
499 513 If given, a list with the structure of sys.argv[1:] to parse
500 514 arguments from. If not given, the instance's self.argv attribute
501 515 (given at construction time) is used."""
502 516 self.clear()
503 517 if argv is None:
504 518 argv = self.argv
505 519 self._create_parser()
506 520 self._parse_args(argv)
507 521 self._convert_to_config()
508 522 return self.config
509 523
510 524 def get_extra_args(self):
511 525 if hasattr(self, 'extra_args'):
512 526 return self.extra_args
513 527 else:
514 528 return []
515 529
516 530 def _create_parser(self):
517 531 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
518 532 self._add_arguments()
519 533
520 534 def _add_arguments(self):
521 535 raise NotImplementedError("subclasses must implement _add_arguments")
522 536
523 537 def _parse_args(self, args):
524 538 """self.parser->self.parsed_data"""
525 539 # decode sys.argv to support unicode command-line options
526 540 uargs = []
527 541 for a in args:
528 542 if isinstance(a, str):
529 543 # don't decode if we already got unicode
530 544 a = a.decode(sys.stdin.encoding or
531 545 sys.getdefaultencoding())
532 546 uargs.append(a)
533 547 self.parsed_data, self.extra_args = self.parser.parse_known_args(uargs)
534 548
535 549 def _convert_to_config(self):
536 550 """self.parsed_data->self.config"""
537 551 for k, v in vars(self.parsed_data).iteritems():
538 552 exec_str = 'self.config.' + k + '= v'
539 553 exec exec_str in locals(), globals()
540 554
541 555
@@ -1,197 +1,217 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 Tests for IPython.config.loader
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez (design help)
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2009 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 import os
24 import sys
24 25 from tempfile import mkstemp
25 26 from unittest import TestCase
26 27
28 from nose import SkipTest
29
27 30 from IPython.utils.traitlets import Int, Unicode
28 31 from IPython.config.configurable import Configurable
29 32 from IPython.config.loader import (
30 33 Config,
31 34 PyFileConfigLoader,
32 35 KeyValueConfigLoader,
33 36 ArgParseConfigLoader,
34 37 ConfigError
35 38 )
36 39
37 40 #-----------------------------------------------------------------------------
38 41 # Actual tests
39 42 #-----------------------------------------------------------------------------
40 43
41 44
42 45 pyfile = """
43 46 c = get_config()
44 47 c.a=10
45 48 c.b=20
46 49 c.Foo.Bar.value=10
47 50 c.Foo.Bam.value=range(10)
48 51 c.D.C.value='hi there'
49 52 """
50 53
51 54 class TestPyFileCL(TestCase):
52 55
53 56 def test_basic(self):
54 57 fd, fname = mkstemp('.py')
55 58 f = os.fdopen(fd, 'w')
56 59 f.write(pyfile)
57 60 f.close()
58 61 # Unlink the file
59 62 cl = PyFileConfigLoader(fname)
60 63 config = cl.load_config()
61 64 self.assertEquals(config.a, 10)
62 65 self.assertEquals(config.b, 20)
63 66 self.assertEquals(config.Foo.Bar.value, 10)
64 67 self.assertEquals(config.Foo.Bam.value, range(10))
65 68 self.assertEquals(config.D.C.value, 'hi there')
66 69
67 70 class MyLoader1(ArgParseConfigLoader):
68 71 def _add_arguments(self):
69 72 p = self.parser
70 73 p.add_argument('-f', '--foo', dest='Global.foo', type=str)
71 74 p.add_argument('-b', dest='MyClass.bar', type=int)
72 75 p.add_argument('-n', dest='n', action='store_true')
73 76 p.add_argument('Global.bam', type=str)
74 77
75 78 class MyLoader2(ArgParseConfigLoader):
76 79 def _add_arguments(self):
77 80 subparsers = self.parser.add_subparsers(dest='subparser_name')
78 81 subparser1 = subparsers.add_parser('1')
79 82 subparser1.add_argument('-x',dest='Global.x')
80 83 subparser2 = subparsers.add_parser('2')
81 84 subparser2.add_argument('y')
82 85
83 86 class TestArgParseCL(TestCase):
84 87
85 88 def test_basic(self):
86 89 cl = MyLoader1()
87 90 config = cl.load_config('-f hi -b 10 -n wow'.split())
88 91 self.assertEquals(config.Global.foo, 'hi')
89 92 self.assertEquals(config.MyClass.bar, 10)
90 93 self.assertEquals(config.n, True)
91 94 self.assertEquals(config.Global.bam, 'wow')
92 95 config = cl.load_config(['wow'])
93 96 self.assertEquals(config.keys(), ['Global'])
94 97 self.assertEquals(config.Global.keys(), ['bam'])
95 98 self.assertEquals(config.Global.bam, 'wow')
96 99
97 100 def test_add_arguments(self):
98 101 cl = MyLoader2()
99 102 config = cl.load_config('2 frobble'.split())
100 103 self.assertEquals(config.subparser_name, '2')
101 104 self.assertEquals(config.y, 'frobble')
102 105 config = cl.load_config('1 -x frobble'.split())
103 106 self.assertEquals(config.subparser_name, '1')
104 107 self.assertEquals(config.Global.x, 'frobble')
105 108
106 109 def test_argv(self):
107 110 cl = MyLoader1(argv='-f hi -b 10 -n wow'.split())
108 111 config = cl.load_config()
109 112 self.assertEquals(config.Global.foo, 'hi')
110 113 self.assertEquals(config.MyClass.bar, 10)
111 114 self.assertEquals(config.n, True)
112 115 self.assertEquals(config.Global.bam, 'wow')
113 116
114 117
115 118 class TestKeyValueCL(TestCase):
116 119
117 120 def test_basic(self):
118 121 cl = KeyValueConfigLoader()
119 122 argv = [s.strip('c.') for s in pyfile.split('\n')[2:-1]]
120 123 config = cl.load_config(argv)
121 124 self.assertEquals(config.a, 10)
122 125 self.assertEquals(config.b, 20)
123 126 self.assertEquals(config.Foo.Bar.value, 10)
124 127 self.assertEquals(config.Foo.Bam.value, range(10))
125 128 self.assertEquals(config.D.C.value, 'hi there')
126 129
127 130 def test_extra_args(self):
128 131 cl = KeyValueConfigLoader()
129 132 config = cl.load_config(['a=5', 'b', 'c=10', 'd'])
130 133 self.assertEquals(cl.extra_args, ['b', 'd'])
131 134 self.assertEquals(config.a, 5)
132 135 self.assertEquals(config.c, 10)
136
137 def test_unicode_args(self):
138 cl = KeyValueConfigLoader()
139 argv = [u'a=épsîlön']
140 config = cl.load_config(argv)
141 self.assertEquals(config.a, u'épsîlön')
142
143 def test_unicode_bytes_args(self):
144 uarg = u'a=é'
145 try:
146 barg = uarg.encode(sys.stdin.encoding)
147 except (TypeError, UnicodeEncodeError):
148 raise SkipTest("sys.stdin.encoding can't handle 'é'")
149
150 cl = KeyValueConfigLoader()
151 config = cl.load_config([barg])
152 self.assertEquals(config.a, u'é')
133 153
134 154
135 155 class TestConfig(TestCase):
136 156
137 157 def test_setget(self):
138 158 c = Config()
139 159 c.a = 10
140 160 self.assertEquals(c.a, 10)
141 161 self.assertEquals(c.has_key('b'), False)
142 162
143 163 def test_auto_section(self):
144 164 c = Config()
145 165 self.assertEquals(c.has_key('A'), True)
146 166 self.assertEquals(c._has_section('A'), False)
147 167 A = c.A
148 168 A.foo = 'hi there'
149 169 self.assertEquals(c._has_section('A'), True)
150 170 self.assertEquals(c.A.foo, 'hi there')
151 171 del c.A
152 172 self.assertEquals(len(c.A.keys()),0)
153 173
154 174 def test_merge_doesnt_exist(self):
155 175 c1 = Config()
156 176 c2 = Config()
157 177 c2.bar = 10
158 178 c2.Foo.bar = 10
159 179 c1._merge(c2)
160 180 self.assertEquals(c1.Foo.bar, 10)
161 181 self.assertEquals(c1.bar, 10)
162 182 c2.Bar.bar = 10
163 183 c1._merge(c2)
164 184 self.assertEquals(c1.Bar.bar, 10)
165 185
166 186 def test_merge_exists(self):
167 187 c1 = Config()
168 188 c2 = Config()
169 189 c1.Foo.bar = 10
170 190 c1.Foo.bam = 30
171 191 c2.Foo.bar = 20
172 192 c2.Foo.wow = 40
173 193 c1._merge(c2)
174 194 self.assertEquals(c1.Foo.bam, 30)
175 195 self.assertEquals(c1.Foo.bar, 20)
176 196 self.assertEquals(c1.Foo.wow, 40)
177 197 c2.Foo.Bam.bam = 10
178 198 c1._merge(c2)
179 199 self.assertEquals(c1.Foo.Bam.bam, 10)
180 200
181 201 def test_deepcopy(self):
182 202 c1 = Config()
183 203 c1.Foo.bar = 10
184 204 c1.Foo.bam = 30
185 205 c1.a = 'asdf'
186 206 c1.b = range(10)
187 207 import copy
188 208 c2 = copy.deepcopy(c1)
189 209 self.assertEquals(c1, c2)
190 210 self.assert_(c1 is not c2)
191 211 self.assert_(c1.Foo is not c2.Foo)
192 212
193 213 def test_builtin(self):
194 214 c1 = Config()
195 215 exec 'foo = True' in c1
196 216 self.assertEquals(c1.foo, True)
197 217 self.assertRaises(ConfigError, setattr, c1, 'ValueError', 10)
@@ -1,1397 +1,1397 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A lightweight Traits like module.
5 5
6 6 This is designed to provide a lightweight, simple, pure Python version of
7 7 many of the capabilities of enthought.traits. This includes:
8 8
9 9 * Validation
10 10 * Type specification with defaults
11 11 * Static and dynamic notification
12 12 * Basic predefined types
13 13 * An API that is similar to enthought.traits
14 14
15 15 We don't support:
16 16
17 17 * Delegation
18 18 * Automatic GUI generation
19 19 * A full set of trait types. Most importantly, we don't provide container
20 20 traits (list, dict, tuple) that can trigger notifications if their
21 21 contents change.
22 22 * API compatibility with enthought.traits
23 23
24 24 There are also some important difference in our design:
25 25
26 26 * enthought.traits does not validate default values. We do.
27 27
28 28 We choose to create this module because we need these capabilities, but
29 29 we need them to be pure Python so they work in all Python implementations,
30 30 including Jython and IronPython.
31 31
32 32 Authors:
33 33
34 34 * Brian Granger
35 35 * Enthought, Inc. Some of the code in this file comes from enthought.traits
36 36 and is licensed under the BSD license. Also, many of the ideas also come
37 37 from enthought.traits even though our implementation is very different.
38 38 """
39 39
40 40 #-----------------------------------------------------------------------------
41 41 # Copyright (C) 2008-2009 The IPython Development Team
42 42 #
43 43 # Distributed under the terms of the BSD License. The full license is in
44 44 # the file COPYING, distributed as part of this software.
45 45 #-----------------------------------------------------------------------------
46 46
47 47 #-----------------------------------------------------------------------------
48 48 # Imports
49 49 #-----------------------------------------------------------------------------
50 50
51 51
52 52 import inspect
53 53 import re
54 54 import sys
55 55 import types
56 56 from types import (
57 57 InstanceType, ClassType, FunctionType,
58 58 ListType, TupleType
59 59 )
60 60 from .importstring import import_item
61 61
62 62 ClassTypes = (ClassType, type)
63 63
64 64 SequenceTypes = (ListType, TupleType, set, frozenset)
65 65
66 66 #-----------------------------------------------------------------------------
67 67 # Basic classes
68 68 #-----------------------------------------------------------------------------
69 69
70 70
71 71 class NoDefaultSpecified ( object ): pass
72 72 NoDefaultSpecified = NoDefaultSpecified()
73 73
74 74
75 75 class Undefined ( object ): pass
76 76 Undefined = Undefined()
77 77
78 78 class TraitError(Exception):
79 79 pass
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Utilities
83 83 #-----------------------------------------------------------------------------
84 84
85 85
86 86 def class_of ( object ):
87 87 """ Returns a string containing the class name of an object with the
88 88 correct indefinite article ('a' or 'an') preceding it (e.g., 'an Image',
89 89 'a PlotValue').
90 90 """
91 91 if isinstance( object, basestring ):
92 92 return add_article( object )
93 93
94 94 return add_article( object.__class__.__name__ )
95 95
96 96
97 97 def add_article ( name ):
98 98 """ Returns a string containing the correct indefinite article ('a' or 'an')
99 99 prefixed to the specified string.
100 100 """
101 101 if name[:1].lower() in 'aeiou':
102 102 return 'an ' + name
103 103
104 104 return 'a ' + name
105 105
106 106
107 107 def repr_type(obj):
108 108 """ Return a string representation of a value and its type for readable
109 109 error messages.
110 110 """
111 111 the_type = type(obj)
112 112 if the_type is InstanceType:
113 113 # Old-style class.
114 114 the_type = obj.__class__
115 115 msg = '%r %r' % (obj, the_type)
116 116 return msg
117 117
118 118
119 119 def parse_notifier_name(name):
120 120 """Convert the name argument to a list of names.
121 121
122 122 Examples
123 123 --------
124 124
125 125 >>> parse_notifier_name('a')
126 126 ['a']
127 127 >>> parse_notifier_name(['a','b'])
128 128 ['a', 'b']
129 129 >>> parse_notifier_name(None)
130 130 ['anytrait']
131 131 """
132 132 if isinstance(name, str):
133 133 return [name]
134 134 elif name is None:
135 135 return ['anytrait']
136 136 elif isinstance(name, (list, tuple)):
137 137 for n in name:
138 138 assert isinstance(n, str), "names must be strings"
139 139 return name
140 140
141 141
142 142 class _SimpleTest:
143 143 def __init__ ( self, value ): self.value = value
144 144 def __call__ ( self, test ):
145 145 return test == self.value
146 146 def __repr__(self):
147 147 return "<SimpleTest(%r)" % self.value
148 148 def __str__(self):
149 149 return self.__repr__()
150 150
151 151
152 152 def getmembers(object, predicate=None):
153 153 """A safe version of inspect.getmembers that handles missing attributes.
154 154
155 155 This is useful when there are descriptor based attributes that for
156 156 some reason raise AttributeError even though they exist. This happens
157 157 in zope.inteface with the __provides__ attribute.
158 158 """
159 159 results = []
160 160 for key in dir(object):
161 161 try:
162 162 value = getattr(object, key)
163 163 except AttributeError:
164 164 pass
165 165 else:
166 166 if not predicate or predicate(value):
167 167 results.append((key, value))
168 168 results.sort()
169 169 return results
170 170
171 171
172 172 #-----------------------------------------------------------------------------
173 173 # Base TraitType for all traits
174 174 #-----------------------------------------------------------------------------
175 175
176 176
177 177 class TraitType(object):
178 178 """A base class for all trait descriptors.
179 179
180 180 Notes
181 181 -----
182 182 Our implementation of traits is based on Python's descriptor
183 183 prototol. This class is the base class for all such descriptors. The
184 184 only magic we use is a custom metaclass for the main :class:`HasTraits`
185 185 class that does the following:
186 186
187 187 1. Sets the :attr:`name` attribute of every :class:`TraitType`
188 188 instance in the class dict to the name of the attribute.
189 189 2. Sets the :attr:`this_class` attribute of every :class:`TraitType`
190 190 instance in the class dict to the *class* that declared the trait.
191 191 This is used by the :class:`This` trait to allow subclasses to
192 192 accept superclasses for :class:`This` values.
193 193 """
194 194
195 195
196 196 metadata = {}
197 197 default_value = Undefined
198 198 info_text = 'any value'
199 199
200 200 def __init__(self, default_value=NoDefaultSpecified, **metadata):
201 201 """Create a TraitType.
202 202 """
203 203 if default_value is not NoDefaultSpecified:
204 204 self.default_value = default_value
205 205
206 206 if len(metadata) > 0:
207 207 if len(self.metadata) > 0:
208 208 self._metadata = self.metadata.copy()
209 209 self._metadata.update(metadata)
210 210 else:
211 211 self._metadata = metadata
212 212 else:
213 213 self._metadata = self.metadata
214 214
215 215 self.init()
216 216
217 217 def init(self):
218 218 pass
219 219
220 220 def get_default_value(self):
221 221 """Create a new instance of the default value."""
222 222 return self.default_value
223 223
224 224 def instance_init(self, obj):
225 225 """This is called by :meth:`HasTraits.__new__` to finish init'ing.
226 226
227 227 Some stages of initialization must be delayed until the parent
228 228 :class:`HasTraits` instance has been created. This method is
229 229 called in :meth:`HasTraits.__new__` after the instance has been
230 230 created.
231 231
232 232 This method trigger the creation and validation of default values
233 233 and also things like the resolution of str given class names in
234 234 :class:`Type` and :class`Instance`.
235 235
236 236 Parameters
237 237 ----------
238 238 obj : :class:`HasTraits` instance
239 239 The parent :class:`HasTraits` instance that has just been
240 240 created.
241 241 """
242 242 self.set_default_value(obj)
243 243
244 244 def set_default_value(self, obj):
245 245 """Set the default value on a per instance basis.
246 246
247 247 This method is called by :meth:`instance_init` to create and
248 248 validate the default value. The creation and validation of
249 249 default values must be delayed until the parent :class:`HasTraits`
250 250 class has been instantiated.
251 251 """
252 252 # Check for a deferred initializer defined in the same class as the
253 253 # trait declaration or above.
254 254 mro = type(obj).mro()
255 255 meth_name = '_%s_default' % self.name
256 256 for cls in mro[:mro.index(self.this_class)+1]:
257 257 if meth_name in cls.__dict__:
258 258 break
259 259 else:
260 260 # We didn't find one. Do static initialization.
261 261 dv = self.get_default_value()
262 262 newdv = self._validate(obj, dv)
263 263 obj._trait_values[self.name] = newdv
264 264 return
265 265 # Complete the dynamic initialization.
266 266 obj._trait_dyn_inits[self.name] = cls.__dict__[meth_name]
267 267
268 268 def __get__(self, obj, cls=None):
269 269 """Get the value of the trait by self.name for the instance.
270 270
271 271 Default values are instantiated when :meth:`HasTraits.__new__`
272 272 is called. Thus by the time this method gets called either the
273 273 default value or a user defined value (they called :meth:`__set__`)
274 274 is in the :class:`HasTraits` instance.
275 275 """
276 276 if obj is None:
277 277 return self
278 278 else:
279 279 try:
280 280 value = obj._trait_values[self.name]
281 281 except KeyError:
282 282 # Check for a dynamic initializer.
283 283 if self.name in obj._trait_dyn_inits:
284 284 value = obj._trait_dyn_inits[self.name](obj)
285 285 # FIXME: Do we really validate here?
286 286 value = self._validate(obj, value)
287 287 obj._trait_values[self.name] = value
288 288 return value
289 289 else:
290 290 raise TraitError('Unexpected error in TraitType: '
291 291 'both default value and dynamic initializer are '
292 292 'absent.')
293 293 except Exception:
294 294 # HasTraits should call set_default_value to populate
295 295 # this. So this should never be reached.
296 296 raise TraitError('Unexpected error in TraitType: '
297 297 'default value not set properly')
298 298 else:
299 299 return value
300 300
301 301 def __set__(self, obj, value):
302 302 new_value = self._validate(obj, value)
303 303 old_value = self.__get__(obj)
304 304 if old_value != new_value:
305 305 obj._trait_values[self.name] = new_value
306 306 obj._notify_trait(self.name, old_value, new_value)
307 307
308 308 def _validate(self, obj, value):
309 309 if hasattr(self, 'validate'):
310 310 return self.validate(obj, value)
311 311 elif hasattr(self, 'is_valid_for'):
312 312 valid = self.is_valid_for(value)
313 313 if valid:
314 314 return value
315 315 else:
316 316 raise TraitError('invalid value for type: %r' % value)
317 317 elif hasattr(self, 'value_for'):
318 318 return self.value_for(value)
319 319 else:
320 320 return value
321 321
322 322 def info(self):
323 323 return self.info_text
324 324
325 325 def error(self, obj, value):
326 326 if obj is not None:
327 327 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
328 328 % (self.name, class_of(obj),
329 329 self.info(), repr_type(value))
330 330 else:
331 331 e = "The '%s' trait must be %s, but a value of %r was specified." \
332 332 % (self.name, self.info(), repr_type(value))
333 333 raise TraitError(e)
334 334
335 335 def get_metadata(self, key):
336 336 return getattr(self, '_metadata', {}).get(key, None)
337 337
338 338 def set_metadata(self, key, value):
339 339 getattr(self, '_metadata', {})[key] = value
340 340
341 341
342 342 #-----------------------------------------------------------------------------
343 343 # The HasTraits implementation
344 344 #-----------------------------------------------------------------------------
345 345
346 346
347 347 class MetaHasTraits(type):
348 348 """A metaclass for HasTraits.
349 349
350 350 This metaclass makes sure that any TraitType class attributes are
351 351 instantiated and sets their name attribute.
352 352 """
353 353
354 354 def __new__(mcls, name, bases, classdict):
355 355 """Create the HasTraits class.
356 356
357 357 This instantiates all TraitTypes in the class dict and sets their
358 358 :attr:`name` attribute.
359 359 """
360 360 # print "MetaHasTraitlets (mcls, name): ", mcls, name
361 361 # print "MetaHasTraitlets (bases): ", bases
362 362 # print "MetaHasTraitlets (classdict): ", classdict
363 363 for k,v in classdict.iteritems():
364 364 if isinstance(v, TraitType):
365 365 v.name = k
366 366 elif inspect.isclass(v):
367 367 if issubclass(v, TraitType):
368 368 vinst = v()
369 369 vinst.name = k
370 370 classdict[k] = vinst
371 371 return super(MetaHasTraits, mcls).__new__(mcls, name, bases, classdict)
372 372
373 373 def __init__(cls, name, bases, classdict):
374 374 """Finish initializing the HasTraits class.
375 375
376 376 This sets the :attr:`this_class` attribute of each TraitType in the
377 377 class dict to the newly created class ``cls``.
378 378 """
379 379 for k, v in classdict.iteritems():
380 380 if isinstance(v, TraitType):
381 381 v.this_class = cls
382 382 super(MetaHasTraits, cls).__init__(name, bases, classdict)
383 383
384 384 class HasTraits(object):
385 385
386 386 __metaclass__ = MetaHasTraits
387 387
388 388 def __new__(cls, **kw):
389 389 # This is needed because in Python 2.6 object.__new__ only accepts
390 390 # the cls argument.
391 391 new_meth = super(HasTraits, cls).__new__
392 392 if new_meth is object.__new__:
393 393 inst = new_meth(cls)
394 394 else:
395 395 inst = new_meth(cls, **kw)
396 396 inst._trait_values = {}
397 397 inst._trait_notifiers = {}
398 398 inst._trait_dyn_inits = {}
399 399 # Here we tell all the TraitType instances to set their default
400 400 # values on the instance.
401 401 for key in dir(cls):
402 402 # Some descriptors raise AttributeError like zope.interface's
403 403 # __provides__ attributes even though they exist. This causes
404 404 # AttributeErrors even though they are listed in dir(cls).
405 405 try:
406 406 value = getattr(cls, key)
407 407 except AttributeError:
408 408 pass
409 409 else:
410 410 if isinstance(value, TraitType):
411 411 value.instance_init(inst)
412 412
413 413 return inst
414 414
415 415 def __init__(self, **kw):
416 416 # Allow trait values to be set using keyword arguments.
417 417 # We need to use setattr for this to trigger validation and
418 418 # notifications.
419 419 for key, value in kw.iteritems():
420 420 setattr(self, key, value)
421 421
422 422 def _notify_trait(self, name, old_value, new_value):
423 423
424 424 # First dynamic ones
425 425 callables = self._trait_notifiers.get(name,[])
426 426 more_callables = self._trait_notifiers.get('anytrait',[])
427 427 callables.extend(more_callables)
428 428
429 429 # Now static ones
430 430 try:
431 431 cb = getattr(self, '_%s_changed' % name)
432 432 except:
433 433 pass
434 434 else:
435 435 callables.append(cb)
436 436
437 437 # Call them all now
438 438 for c in callables:
439 439 # Traits catches and logs errors here. I allow them to raise
440 440 if callable(c):
441 441 argspec = inspect.getargspec(c)
442 442 nargs = len(argspec[0])
443 443 # Bound methods have an additional 'self' argument
444 444 # I don't know how to treat unbound methods, but they
445 445 # can't really be used for callbacks.
446 446 if isinstance(c, types.MethodType):
447 447 offset = -1
448 448 else:
449 449 offset = 0
450 450 if nargs + offset == 0:
451 451 c()
452 452 elif nargs + offset == 1:
453 453 c(name)
454 454 elif nargs + offset == 2:
455 455 c(name, new_value)
456 456 elif nargs + offset == 3:
457 457 c(name, old_value, new_value)
458 458 else:
459 459 raise TraitError('a trait changed callback '
460 460 'must have 0-3 arguments.')
461 461 else:
462 462 raise TraitError('a trait changed callback '
463 463 'must be callable.')
464 464
465 465
466 466 def _add_notifiers(self, handler, name):
467 467 if not self._trait_notifiers.has_key(name):
468 468 nlist = []
469 469 self._trait_notifiers[name] = nlist
470 470 else:
471 471 nlist = self._trait_notifiers[name]
472 472 if handler not in nlist:
473 473 nlist.append(handler)
474 474
475 475 def _remove_notifiers(self, handler, name):
476 476 if self._trait_notifiers.has_key(name):
477 477 nlist = self._trait_notifiers[name]
478 478 try:
479 479 index = nlist.index(handler)
480 480 except ValueError:
481 481 pass
482 482 else:
483 483 del nlist[index]
484 484
485 485 def on_trait_change(self, handler, name=None, remove=False):
486 486 """Setup a handler to be called when a trait changes.
487 487
488 488 This is used to setup dynamic notifications of trait changes.
489 489
490 490 Static handlers can be created by creating methods on a HasTraits
491 491 subclass with the naming convention '_[traitname]_changed'. Thus,
492 492 to create static handler for the trait 'a', create the method
493 493 _a_changed(self, name, old, new) (fewer arguments can be used, see
494 494 below).
495 495
496 496 Parameters
497 497 ----------
498 498 handler : callable
499 499 A callable that is called when a trait changes. Its
500 500 signature can be handler(), handler(name), handler(name, new)
501 501 or handler(name, old, new).
502 502 name : list, str, None
503 503 If None, the handler will apply to all traits. If a list
504 504 of str, handler will apply to all names in the list. If a
505 505 str, the handler will apply just to that name.
506 506 remove : bool
507 507 If False (the default), then install the handler. If True
508 508 then unintall it.
509 509 """
510 510 if remove:
511 511 names = parse_notifier_name(name)
512 512 for n in names:
513 513 self._remove_notifiers(handler, n)
514 514 else:
515 515 names = parse_notifier_name(name)
516 516 for n in names:
517 517 self._add_notifiers(handler, n)
518 518
519 519 @classmethod
520 520 def class_trait_names(cls, **metadata):
521 521 """Get a list of all the names of this classes traits.
522 522
523 523 This method is just like the :meth:`trait_names` method, but is unbound.
524 524 """
525 525 return cls.class_traits(**metadata).keys()
526 526
527 527 @classmethod
528 528 def class_traits(cls, **metadata):
529 529 """Get a list of all the traits of this class.
530 530
531 531 This method is just like the :meth:`traits` method, but is unbound.
532 532
533 533 The TraitTypes returned don't know anything about the values
534 534 that the various HasTrait's instances are holding.
535 535
536 536 This follows the same algorithm as traits does and does not allow
537 537 for any simple way of specifying merely that a metadata name
538 538 exists, but has any value. This is because get_metadata returns
539 539 None if a metadata key doesn't exist.
540 540 """
541 541 traits = dict([memb for memb in getmembers(cls) if \
542 542 isinstance(memb[1], TraitType)])
543 543
544 544 if len(metadata) == 0:
545 545 return traits
546 546
547 547 for meta_name, meta_eval in metadata.items():
548 548 if type(meta_eval) is not FunctionType:
549 549 metadata[meta_name] = _SimpleTest(meta_eval)
550 550
551 551 result = {}
552 552 for name, trait in traits.items():
553 553 for meta_name, meta_eval in metadata.items():
554 554 if not meta_eval(trait.get_metadata(meta_name)):
555 555 break
556 556 else:
557 557 result[name] = trait
558 558
559 559 return result
560 560
561 561 def trait_names(self, **metadata):
562 562 """Get a list of all the names of this classes traits."""
563 563 return self.traits(**metadata).keys()
564 564
565 565 def traits(self, **metadata):
566 566 """Get a list of all the traits of this class.
567 567
568 568 The TraitTypes returned don't know anything about the values
569 569 that the various HasTrait's instances are holding.
570 570
571 571 This follows the same algorithm as traits does and does not allow
572 572 for any simple way of specifying merely that a metadata name
573 573 exists, but has any value. This is because get_metadata returns
574 574 None if a metadata key doesn't exist.
575 575 """
576 576 traits = dict([memb for memb in getmembers(self.__class__) if \
577 577 isinstance(memb[1], TraitType)])
578 578
579 579 if len(metadata) == 0:
580 580 return traits
581 581
582 582 for meta_name, meta_eval in metadata.items():
583 583 if type(meta_eval) is not FunctionType:
584 584 metadata[meta_name] = _SimpleTest(meta_eval)
585 585
586 586 result = {}
587 587 for name, trait in traits.items():
588 588 for meta_name, meta_eval in metadata.items():
589 589 if not meta_eval(trait.get_metadata(meta_name)):
590 590 break
591 591 else:
592 592 result[name] = trait
593 593
594 594 return result
595 595
596 596 def trait_metadata(self, traitname, key):
597 597 """Get metadata values for trait by key."""
598 598 try:
599 599 trait = getattr(self.__class__, traitname)
600 600 except AttributeError:
601 601 raise TraitError("Class %s does not have a trait named %s" %
602 602 (self.__class__.__name__, traitname))
603 603 else:
604 604 return trait.get_metadata(key)
605 605
606 606 #-----------------------------------------------------------------------------
607 607 # Actual TraitTypes implementations/subclasses
608 608 #-----------------------------------------------------------------------------
609 609
610 610 #-----------------------------------------------------------------------------
611 611 # TraitTypes subclasses for handling classes and instances of classes
612 612 #-----------------------------------------------------------------------------
613 613
614 614
615 615 class ClassBasedTraitType(TraitType):
616 616 """A trait with error reporting for Type, Instance and This."""
617 617
618 618 def error(self, obj, value):
619 619 kind = type(value)
620 620 if kind is InstanceType:
621 621 msg = 'class %s' % value.__class__.__name__
622 622 else:
623 623 msg = '%s (i.e. %s)' % ( str( kind )[1:-1], repr( value ) )
624 624
625 625 if obj is not None:
626 626 e = "The '%s' trait of %s instance must be %s, but a value of %s was specified." \
627 627 % (self.name, class_of(obj),
628 628 self.info(), msg)
629 629 else:
630 630 e = "The '%s' trait must be %s, but a value of %r was specified." \
631 631 % (self.name, self.info(), msg)
632 632
633 633 raise TraitError(e)
634 634
635 635
636 636 class Type(ClassBasedTraitType):
637 637 """A trait whose value must be a subclass of a specified class."""
638 638
639 639 def __init__ (self, default_value=None, klass=None, allow_none=True, **metadata ):
640 640 """Construct a Type trait
641 641
642 642 A Type trait specifies that its values must be subclasses of
643 643 a particular class.
644 644
645 645 If only ``default_value`` is given, it is used for the ``klass`` as
646 646 well.
647 647
648 648 Parameters
649 649 ----------
650 650 default_value : class, str or None
651 651 The default value must be a subclass of klass. If an str,
652 652 the str must be a fully specified class name, like 'foo.bar.Bah'.
653 653 The string is resolved into real class, when the parent
654 654 :class:`HasTraits` class is instantiated.
655 655 klass : class, str, None
656 656 Values of this trait must be a subclass of klass. The klass
657 657 may be specified in a string like: 'foo.bar.MyClass'.
658 658 The string is resolved into real class, when the parent
659 659 :class:`HasTraits` class is instantiated.
660 660 allow_none : boolean
661 661 Indicates whether None is allowed as an assignable value. Even if
662 662 ``False``, the default value may be ``None``.
663 663 """
664 664 if default_value is None:
665 665 if klass is None:
666 666 klass = object
667 667 elif klass is None:
668 668 klass = default_value
669 669
670 670 if not (inspect.isclass(klass) or isinstance(klass, basestring)):
671 671 raise TraitError("A Type trait must specify a class.")
672 672
673 673 self.klass = klass
674 674 self._allow_none = allow_none
675 675
676 676 super(Type, self).__init__(default_value, **metadata)
677 677
678 678 def validate(self, obj, value):
679 679 """Validates that the value is a valid object instance."""
680 680 try:
681 681 if issubclass(value, self.klass):
682 682 return value
683 683 except:
684 684 if (value is None) and (self._allow_none):
685 685 return value
686 686
687 687 self.error(obj, value)
688 688
689 689 def info(self):
690 690 """ Returns a description of the trait."""
691 691 if isinstance(self.klass, basestring):
692 692 klass = self.klass
693 693 else:
694 694 klass = self.klass.__name__
695 695 result = 'a subclass of ' + klass
696 696 if self._allow_none:
697 697 return result + ' or None'
698 698 return result
699 699
700 700 def instance_init(self, obj):
701 701 self._resolve_classes()
702 702 super(Type, self).instance_init(obj)
703 703
704 704 def _resolve_classes(self):
705 705 if isinstance(self.klass, basestring):
706 706 self.klass = import_item(self.klass)
707 707 if isinstance(self.default_value, basestring):
708 708 self.default_value = import_item(self.default_value)
709 709
710 710 def get_default_value(self):
711 711 return self.default_value
712 712
713 713
714 714 class DefaultValueGenerator(object):
715 715 """A class for generating new default value instances."""
716 716
717 717 def __init__(self, *args, **kw):
718 718 self.args = args
719 719 self.kw = kw
720 720
721 721 def generate(self, klass):
722 722 return klass(*self.args, **self.kw)
723 723
724 724
725 725 class Instance(ClassBasedTraitType):
726 726 """A trait whose value must be an instance of a specified class.
727 727
728 728 The value can also be an instance of a subclass of the specified class.
729 729 """
730 730
731 731 def __init__(self, klass=None, args=None, kw=None,
732 732 allow_none=True, **metadata ):
733 733 """Construct an Instance trait.
734 734
735 735 This trait allows values that are instances of a particular
736 736 class or its sublclasses. Our implementation is quite different
737 737 from that of enthough.traits as we don't allow instances to be used
738 738 for klass and we handle the ``args`` and ``kw`` arguments differently.
739 739
740 740 Parameters
741 741 ----------
742 742 klass : class, str
743 743 The class that forms the basis for the trait. Class names
744 744 can also be specified as strings, like 'foo.bar.Bar'.
745 745 args : tuple
746 746 Positional arguments for generating the default value.
747 747 kw : dict
748 748 Keyword arguments for generating the default value.
749 749 allow_none : bool
750 750 Indicates whether None is allowed as a value.
751 751
752 752 Default Value
753 753 -------------
754 754 If both ``args`` and ``kw`` are None, then the default value is None.
755 755 If ``args`` is a tuple and ``kw`` is a dict, then the default is
756 756 created as ``klass(*args, **kw)``. If either ``args`` or ``kw`` is
757 757 not (but not both), None is replace by ``()`` or ``{}``.
758 758 """
759 759
760 760 self._allow_none = allow_none
761 761
762 762 if (klass is None) or (not (inspect.isclass(klass) or isinstance(klass, basestring))):
763 763 raise TraitError('The klass argument must be a class'
764 764 ' you gave: %r' % klass)
765 765 self.klass = klass
766 766
767 767 # self.klass is a class, so handle default_value
768 768 if args is None and kw is None:
769 769 default_value = None
770 770 else:
771 771 if args is None:
772 772 # kw is not None
773 773 args = ()
774 774 elif kw is None:
775 775 # args is not None
776 776 kw = {}
777 777
778 778 if not isinstance(kw, dict):
779 779 raise TraitError("The 'kw' argument must be a dict or None.")
780 780 if not isinstance(args, tuple):
781 781 raise TraitError("The 'args' argument must be a tuple or None.")
782 782
783 783 default_value = DefaultValueGenerator(*args, **kw)
784 784
785 785 super(Instance, self).__init__(default_value, **metadata)
786 786
787 787 def validate(self, obj, value):
788 788 if value is None:
789 789 if self._allow_none:
790 790 return value
791 791 self.error(obj, value)
792 792
793 793 if isinstance(value, self.klass):
794 794 return value
795 795 else:
796 796 self.error(obj, value)
797 797
798 798 def info(self):
799 799 if isinstance(self.klass, basestring):
800 800 klass = self.klass
801 801 else:
802 802 klass = self.klass.__name__
803 803 result = class_of(klass)
804 804 if self._allow_none:
805 805 return result + ' or None'
806 806
807 807 return result
808 808
809 809 def instance_init(self, obj):
810 810 self._resolve_classes()
811 811 super(Instance, self).instance_init(obj)
812 812
813 813 def _resolve_classes(self):
814 814 if isinstance(self.klass, basestring):
815 815 self.klass = import_item(self.klass)
816 816
817 817 def get_default_value(self):
818 818 """Instantiate a default value instance.
819 819
820 820 This is called when the containing HasTraits classes'
821 821 :meth:`__new__` method is called to ensure that a unique instance
822 822 is created for each HasTraits instance.
823 823 """
824 824 dv = self.default_value
825 825 if isinstance(dv, DefaultValueGenerator):
826 826 return dv.generate(self.klass)
827 827 else:
828 828 return dv
829 829
830 830
831 831 class This(ClassBasedTraitType):
832 832 """A trait for instances of the class containing this trait.
833 833
834 834 Because how how and when class bodies are executed, the ``This``
835 835 trait can only have a default value of None. This, and because we
836 836 always validate default values, ``allow_none`` is *always* true.
837 837 """
838 838
839 839 info_text = 'an instance of the same type as the receiver or None'
840 840
841 841 def __init__(self, **metadata):
842 842 super(This, self).__init__(None, **metadata)
843 843
844 844 def validate(self, obj, value):
845 845 # What if value is a superclass of obj.__class__? This is
846 846 # complicated if it was the superclass that defined the This
847 847 # trait.
848 848 if isinstance(value, self.this_class) or (value is None):
849 849 return value
850 850 else:
851 851 self.error(obj, value)
852 852
853 853
854 854 #-----------------------------------------------------------------------------
855 855 # Basic TraitTypes implementations/subclasses
856 856 #-----------------------------------------------------------------------------
857 857
858 858
859 859 class Any(TraitType):
860 860 default_value = None
861 861 info_text = 'any value'
862 862
863 863
864 864 class Int(TraitType):
865 865 """A integer trait."""
866 866
867 867 default_value = 0
868 868 info_text = 'an integer'
869 869
870 870 def validate(self, obj, value):
871 871 if isinstance(value, int):
872 872 return value
873 873 self.error(obj, value)
874 874
875 875 class CInt(Int):
876 876 """A casting version of the int trait."""
877 877
878 878 def validate(self, obj, value):
879 879 try:
880 880 return int(value)
881 881 except:
882 882 self.error(obj, value)
883 883
884 884
885 885 class Long(TraitType):
886 886 """A long integer trait."""
887 887
888 888 default_value = 0L
889 889 info_text = 'a long'
890 890
891 891 def validate(self, obj, value):
892 892 if isinstance(value, long):
893 893 return value
894 894 if isinstance(value, int):
895 895 return long(value)
896 896 self.error(obj, value)
897 897
898 898
899 899 class CLong(Long):
900 900 """A casting version of the long integer trait."""
901 901
902 902 def validate(self, obj, value):
903 903 try:
904 904 return long(value)
905 905 except:
906 906 self.error(obj, value)
907 907
908 908
909 909 class Float(TraitType):
910 910 """A float trait."""
911 911
912 912 default_value = 0.0
913 913 info_text = 'a float'
914 914
915 915 def validate(self, obj, value):
916 916 if isinstance(value, float):
917 917 return value
918 918 if isinstance(value, int):
919 919 return float(value)
920 920 self.error(obj, value)
921 921
922 922
923 923 class CFloat(Float):
924 924 """A casting version of the float trait."""
925 925
926 926 def validate(self, obj, value):
927 927 try:
928 928 return float(value)
929 929 except:
930 930 self.error(obj, value)
931 931
932 932 class Complex(TraitType):
933 933 """A trait for complex numbers."""
934 934
935 935 default_value = 0.0 + 0.0j
936 936 info_text = 'a complex number'
937 937
938 938 def validate(self, obj, value):
939 939 if isinstance(value, complex):
940 940 return value
941 941 if isinstance(value, (float, int)):
942 942 return complex(value)
943 943 self.error(obj, value)
944 944
945 945
946 946 class CComplex(Complex):
947 947 """A casting version of the complex number trait."""
948 948
949 949 def validate (self, obj, value):
950 950 try:
951 951 return complex(value)
952 952 except:
953 953 self.error(obj, value)
954 954
955 955 # We should always be explicit about whether we're using bytes or unicode, both
956 956 # for Python 3 conversion and for reliable unicode behaviour on Python 2. So
957 957 # we don't have a Str type.
958 958 class Bytes(TraitType):
959 959 """A trait for strings."""
960 960
961 961 default_value = ''
962 962 info_text = 'a string'
963 963
964 964 def validate(self, obj, value):
965 965 if isinstance(value, bytes):
966 966 return value
967 967 self.error(obj, value)
968 968
969 969
970 970 class CBytes(Bytes):
971 971 """A casting version of the string trait."""
972 972
973 973 def validate(self, obj, value):
974 974 try:
975 975 return bytes(value)
976 976 except:
977 977 self.error(obj, value)
978 978
979 979
980 980 class Unicode(TraitType):
981 981 """A trait for unicode strings."""
982 982
983 983 default_value = u''
984 984 info_text = 'a unicode string'
985 985
986 986 def validate(self, obj, value):
987 987 if isinstance(value, unicode):
988 988 return value
989 989 if isinstance(value, bytes):
990 990 return unicode(value)
991 991 self.error(obj, value)
992 992
993 993
994 994 class CUnicode(Unicode):
995 995 """A casting version of the unicode trait."""
996 996
997 997 def validate(self, obj, value):
998 998 try:
999 999 return unicode(value)
1000 1000 except:
1001 1001 self.error(obj, value)
1002 1002
1003 1003
1004 1004 class ObjectName(TraitType):
1005 1005 """A string holding a valid object name in this version of Python.
1006 1006
1007 1007 This does not check that the name exists in any scope."""
1008 1008 info_text = "a valid object identifier in Python"
1009 1009
1010 1010 if sys.version_info[0] < 3:
1011 1011 # Python 2:
1012 1012 _name_re = re.compile(r"[a-zA-Z_][a-zA-Z0-9_]*$")
1013 1013 def isidentifier(self, s):
1014 1014 return bool(self._name_re.match(s))
1015 1015
1016 1016 def coerce_str(self, obj, value):
1017 1017 "In Python 2, coerce ascii-only unicode to str"
1018 1018 if isinstance(value, unicode):
1019 1019 try:
1020 1020 return str(value)
1021 1021 except UnicodeEncodeError:
1022 1022 self.error(obj, value)
1023 1023 return value
1024 1024
1025 1025 else:
1026 1026 # Python 3:
1027 1027 isidentifier = staticmethod(lambda s: s.isidentifier())
1028 1028 coerce_str = staticmethod(lambda _,s: s)
1029 1029
1030 1030 def validate(self, obj, value):
1031 1031 value = self.coerce_str(obj, value)
1032 1032
1033 1033 if isinstance(value, str) and self.isidentifier(value):
1034 1034 return value
1035 1035 self.error(obj, value)
1036 1036
1037 1037 class DottedObjectName(ObjectName):
1038 1038 """A string holding a valid dotted object name in Python, such as A.b3._c"""
1039 1039 def validate(self, obj, value):
1040 1040 value = self.coerce_str(obj, value)
1041 1041
1042 1042 if isinstance(value, str) and all(self.isidentifier(x) \
1043 1043 for x in value.split('.')):
1044 1044 return value
1045 1045 self.error(obj, value)
1046 1046
1047 1047
1048 1048 class Bool(TraitType):
1049 1049 """A boolean (True, False) trait."""
1050 1050
1051 1051 default_value = False
1052 1052 info_text = 'a boolean'
1053 1053
1054 1054 def validate(self, obj, value):
1055 1055 if isinstance(value, bool):
1056 1056 return value
1057 1057 self.error(obj, value)
1058 1058
1059 1059
1060 1060 class CBool(Bool):
1061 1061 """A casting version of the boolean trait."""
1062 1062
1063 1063 def validate(self, obj, value):
1064 1064 try:
1065 1065 return bool(value)
1066 1066 except:
1067 1067 self.error(obj, value)
1068 1068
1069 1069
1070 1070 class Enum(TraitType):
1071 1071 """An enum that whose value must be in a given sequence."""
1072 1072
1073 1073 def __init__(self, values, default_value=None, allow_none=True, **metadata):
1074 1074 self.values = values
1075 1075 self._allow_none = allow_none
1076 1076 super(Enum, self).__init__(default_value, **metadata)
1077 1077
1078 1078 def validate(self, obj, value):
1079 1079 if value is None:
1080 1080 if self._allow_none:
1081 1081 return value
1082 1082
1083 1083 if value in self.values:
1084 1084 return value
1085 1085 self.error(obj, value)
1086 1086
1087 1087 def info(self):
1088 1088 """ Returns a description of the trait."""
1089 1089 result = 'any of ' + repr(self.values)
1090 1090 if self._allow_none:
1091 1091 return result + ' or None'
1092 1092 return result
1093 1093
1094 1094 class CaselessStrEnum(Enum):
1095 1095 """An enum of strings that are caseless in validate."""
1096 1096
1097 1097 def validate(self, obj, value):
1098 1098 if value is None:
1099 1099 if self._allow_none:
1100 1100 return value
1101 1101
1102 if not isinstance(value, str):
1102 if not isinstance(value, basestring):
1103 1103 self.error(obj, value)
1104 1104
1105 1105 for v in self.values:
1106 1106 if v.lower() == value.lower():
1107 1107 return v
1108 1108 self.error(obj, value)
1109 1109
1110 1110 class Container(Instance):
1111 1111 """An instance of a container (list, set, etc.)
1112 1112
1113 1113 To be subclassed by overriding klass.
1114 1114 """
1115 1115 klass = None
1116 1116 _valid_defaults = SequenceTypes
1117 1117 _trait = None
1118 1118
1119 1119 def __init__(self, trait=None, default_value=None, allow_none=True,
1120 1120 **metadata):
1121 1121 """Create a container trait type from a list, set, or tuple.
1122 1122
1123 1123 The default value is created by doing ``List(default_value)``,
1124 1124 which creates a copy of the ``default_value``.
1125 1125
1126 1126 ``trait`` can be specified, which restricts the type of elements
1127 1127 in the container to that TraitType.
1128 1128
1129 1129 If only one arg is given and it is not a Trait, it is taken as
1130 1130 ``default_value``:
1131 1131
1132 1132 ``c = List([1,2,3])``
1133 1133
1134 1134 Parameters
1135 1135 ----------
1136 1136
1137 1137 trait : TraitType [ optional ]
1138 1138 the type for restricting the contents of the Container. If unspecified,
1139 1139 types are not checked.
1140 1140
1141 1141 default_value : SequenceType [ optional ]
1142 1142 The default value for the Trait. Must be list/tuple/set, and
1143 1143 will be cast to the container type.
1144 1144
1145 1145 allow_none : Bool [ default True ]
1146 1146 Whether to allow the value to be None
1147 1147
1148 1148 **metadata : any
1149 1149 further keys for extensions to the Trait (e.g. config)
1150 1150
1151 1151 """
1152 1152 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1153 1153
1154 1154 # allow List([values]):
1155 1155 if default_value is None and not istrait(trait):
1156 1156 default_value = trait
1157 1157 trait = None
1158 1158
1159 1159 if default_value is None:
1160 1160 args = ()
1161 1161 elif isinstance(default_value, self._valid_defaults):
1162 1162 args = (default_value,)
1163 1163 else:
1164 1164 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1165 1165
1166 1166 if istrait(trait):
1167 1167 self._trait = trait()
1168 1168 self._trait.name = 'element'
1169 1169 elif trait is not None:
1170 1170 raise TypeError("`trait` must be a Trait or None, got %s"%repr_type(trait))
1171 1171
1172 1172 super(Container,self).__init__(klass=self.klass, args=args,
1173 1173 allow_none=allow_none, **metadata)
1174 1174
1175 1175 def element_error(self, obj, element, validator):
1176 1176 e = "Element of the '%s' trait of %s instance must be %s, but a value of %s was specified." \
1177 1177 % (self.name, class_of(obj), validator.info(), repr_type(element))
1178 1178 raise TraitError(e)
1179 1179
1180 1180 def validate(self, obj, value):
1181 1181 value = super(Container, self).validate(obj, value)
1182 1182 if value is None:
1183 1183 return value
1184 1184
1185 1185 value = self.validate_elements(obj, value)
1186 1186
1187 1187 return value
1188 1188
1189 1189 def validate_elements(self, obj, value):
1190 1190 validated = []
1191 1191 if self._trait is None or isinstance(self._trait, Any):
1192 1192 return value
1193 1193 for v in value:
1194 1194 try:
1195 1195 v = self._trait.validate(obj, v)
1196 1196 except TraitError:
1197 1197 self.element_error(obj, v, self._trait)
1198 1198 else:
1199 1199 validated.append(v)
1200 1200 return self.klass(validated)
1201 1201
1202 1202
1203 1203 class List(Container):
1204 1204 """An instance of a Python list."""
1205 1205 klass = list
1206 1206
1207 1207 def __init__(self, trait=None, default_value=None, minlen=0, maxlen=sys.maxint,
1208 1208 allow_none=True, **metadata):
1209 1209 """Create a List trait type from a list, set, or tuple.
1210 1210
1211 1211 The default value is created by doing ``List(default_value)``,
1212 1212 which creates a copy of the ``default_value``.
1213 1213
1214 1214 ``trait`` can be specified, which restricts the type of elements
1215 1215 in the container to that TraitType.
1216 1216
1217 1217 If only one arg is given and it is not a Trait, it is taken as
1218 1218 ``default_value``:
1219 1219
1220 1220 ``c = List([1,2,3])``
1221 1221
1222 1222 Parameters
1223 1223 ----------
1224 1224
1225 1225 trait : TraitType [ optional ]
1226 1226 the type for restricting the contents of the Container. If unspecified,
1227 1227 types are not checked.
1228 1228
1229 1229 default_value : SequenceType [ optional ]
1230 1230 The default value for the Trait. Must be list/tuple/set, and
1231 1231 will be cast to the container type.
1232 1232
1233 1233 minlen : Int [ default 0 ]
1234 1234 The minimum length of the input list
1235 1235
1236 1236 maxlen : Int [ default sys.maxint ]
1237 1237 The maximum length of the input list
1238 1238
1239 1239 allow_none : Bool [ default True ]
1240 1240 Whether to allow the value to be None
1241 1241
1242 1242 **metadata : any
1243 1243 further keys for extensions to the Trait (e.g. config)
1244 1244
1245 1245 """
1246 1246 self._minlen = minlen
1247 1247 self._maxlen = maxlen
1248 1248 super(List, self).__init__(trait=trait, default_value=default_value,
1249 1249 allow_none=allow_none, **metadata)
1250 1250
1251 1251 def length_error(self, obj, value):
1252 1252 e = "The '%s' trait of %s instance must be of length %i <= L <= %i, but a value of %s was specified." \
1253 1253 % (self.name, class_of(obj), self._minlen, self._maxlen, value)
1254 1254 raise TraitError(e)
1255 1255
1256 1256 def validate_elements(self, obj, value):
1257 1257 length = len(value)
1258 1258 if length < self._minlen or length > self._maxlen:
1259 1259 self.length_error(obj, value)
1260 1260
1261 1261 return super(List, self).validate_elements(obj, value)
1262 1262
1263 1263
1264 1264 class Set(Container):
1265 1265 """An instance of a Python set."""
1266 1266 klass = set
1267 1267
1268 1268 class Tuple(Container):
1269 1269 """An instance of a Python tuple."""
1270 1270 klass = tuple
1271 1271
1272 1272 def __init__(self, *traits, **metadata):
1273 1273 """Tuple(*traits, default_value=None, allow_none=True, **medatata)
1274 1274
1275 1275 Create a tuple from a list, set, or tuple.
1276 1276
1277 1277 Create a fixed-type tuple with Traits:
1278 1278
1279 1279 ``t = Tuple(Int, Str, CStr)``
1280 1280
1281 1281 would be length 3, with Int,Str,CStr for each element.
1282 1282
1283 1283 If only one arg is given and it is not a Trait, it is taken as
1284 1284 default_value:
1285 1285
1286 1286 ``t = Tuple((1,2,3))``
1287 1287
1288 1288 Otherwise, ``default_value`` *must* be specified by keyword.
1289 1289
1290 1290 Parameters
1291 1291 ----------
1292 1292
1293 1293 *traits : TraitTypes [ optional ]
1294 1294 the tsype for restricting the contents of the Tuple. If unspecified,
1295 1295 types are not checked. If specified, then each positional argument
1296 1296 corresponds to an element of the tuple. Tuples defined with traits
1297 1297 are of fixed length.
1298 1298
1299 1299 default_value : SequenceType [ optional ]
1300 1300 The default value for the Tuple. Must be list/tuple/set, and
1301 1301 will be cast to a tuple. If `traits` are specified, the
1302 1302 `default_value` must conform to the shape and type they specify.
1303 1303
1304 1304 allow_none : Bool [ default True ]
1305 1305 Whether to allow the value to be None
1306 1306
1307 1307 **metadata : any
1308 1308 further keys for extensions to the Trait (e.g. config)
1309 1309
1310 1310 """
1311 1311 default_value = metadata.pop('default_value', None)
1312 1312 allow_none = metadata.pop('allow_none', True)
1313 1313
1314 1314 istrait = lambda t: isinstance(t, type) and issubclass(t, TraitType)
1315 1315
1316 1316 # allow Tuple((values,)):
1317 1317 if len(traits) == 1 and default_value is None and not istrait(traits[0]):
1318 1318 default_value = traits[0]
1319 1319 traits = ()
1320 1320
1321 1321 if default_value is None:
1322 1322 args = ()
1323 1323 elif isinstance(default_value, self._valid_defaults):
1324 1324 args = (default_value,)
1325 1325 else:
1326 1326 raise TypeError('default value of %s was %s' %(self.__class__.__name__, default_value))
1327 1327
1328 1328 self._traits = []
1329 1329 for trait in traits:
1330 1330 t = trait()
1331 1331 t.name = 'element'
1332 1332 self._traits.append(t)
1333 1333
1334 1334 if self._traits and default_value is None:
1335 1335 # don't allow default to be an empty container if length is specified
1336 1336 args = None
1337 1337 super(Container,self).__init__(klass=self.klass, args=args,
1338 1338 allow_none=allow_none, **metadata)
1339 1339
1340 1340 def validate_elements(self, obj, value):
1341 1341 if not self._traits:
1342 1342 # nothing to validate
1343 1343 return value
1344 1344 if len(value) != len(self._traits):
1345 1345 e = "The '%s' trait of %s instance requires %i elements, but a value of %s was specified." \
1346 1346 % (self.name, class_of(obj), len(self._traits), repr_type(value))
1347 1347 raise TraitError(e)
1348 1348
1349 1349 validated = []
1350 1350 for t,v in zip(self._traits, value):
1351 1351 try:
1352 1352 v = t.validate(obj, v)
1353 1353 except TraitError:
1354 1354 self.element_error(obj, v, t)
1355 1355 else:
1356 1356 validated.append(v)
1357 1357 return tuple(validated)
1358 1358
1359 1359
1360 1360 class Dict(Instance):
1361 1361 """An instance of a Python dict."""
1362 1362
1363 1363 def __init__(self, default_value=None, allow_none=True, **metadata):
1364 1364 """Create a dict trait type from a dict.
1365 1365
1366 1366 The default value is created by doing ``dict(default_value)``,
1367 1367 which creates a copy of the ``default_value``.
1368 1368 """
1369 1369 if default_value is None:
1370 1370 args = ((),)
1371 1371 elif isinstance(default_value, dict):
1372 1372 args = (default_value,)
1373 1373 elif isinstance(default_value, SequenceTypes):
1374 1374 args = (default_value,)
1375 1375 else:
1376 1376 raise TypeError('default value of Dict was %s' % default_value)
1377 1377
1378 1378 super(Dict,self).__init__(klass=dict, args=args,
1379 1379 allow_none=allow_none, **metadata)
1380 1380
1381 1381 class TCPAddress(TraitType):
1382 1382 """A trait for an (ip, port) tuple.
1383 1383
1384 1384 This allows for both IPv4 IP addresses as well as hostnames.
1385 1385 """
1386 1386
1387 1387 default_value = ('127.0.0.1', 0)
1388 1388 info_text = 'an (ip, port) tuple'
1389 1389
1390 1390 def validate(self, obj, value):
1391 1391 if isinstance(value, tuple):
1392 1392 if len(value) == 2:
1393 1393 if isinstance(value[0], basestring) and isinstance(value[1], int):
1394 1394 port = value[1]
1395 1395 if port >= 0 and port <= 65535:
1396 1396 return value
1397 1397 self.error(obj, value)
General Comments 0
You need to be logged in to leave comments. Login now