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