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