##// END OF EJS Templates
add parent to Configurable...
MinRK -
Show More
@@ -1,356 +1,376 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for objects that are configurable.
3 A base class for objects that are configurable.
4
4
5 Inheritance diagram:
5 Inheritance diagram:
6
6
7 .. inheritance-diagram:: IPython.config.configurable
7 .. inheritance-diagram:: IPython.config.configurable
8 :parts: 3
8 :parts: 3
9
9
10 Authors:
10 Authors:
11
11
12 * Brian Granger
12 * Brian Granger
13 * Fernando Perez
13 * Fernando Perez
14 * Min RK
14 * Min RK
15 """
15 """
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Copyright (C) 2008-2011 The IPython Development Team
18 # Copyright (C) 2008-2011 The IPython Development Team
19 #
19 #
20 # Distributed under the terms of the BSD License. The full license is in
20 # Distributed under the terms of the BSD License. The full license is in
21 # the file COPYING, distributed as part of this software.
21 # the file COPYING, distributed as part of this software.
22 #-----------------------------------------------------------------------------
22 #-----------------------------------------------------------------------------
23
23
24 #-----------------------------------------------------------------------------
24 #-----------------------------------------------------------------------------
25 # Imports
25 # Imports
26 #-----------------------------------------------------------------------------
26 #-----------------------------------------------------------------------------
27
27
28 import datetime
28 import datetime
29 from copy import deepcopy
29 from copy import deepcopy
30
30
31 from loader import Config
31 from loader import Config
32 from IPython.utils.traitlets import HasTraits, Instance
32 from IPython.utils.traitlets import HasTraits, Instance
33 from IPython.utils.text import indent, wrap_paragraphs
33 from IPython.utils.text import indent, wrap_paragraphs
34
34
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Helper classes for Configurables
37 # Helper classes for Configurables
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40
40
41 class ConfigurableError(Exception):
41 class ConfigurableError(Exception):
42 pass
42 pass
43
43
44
44
45 class MultipleInstanceError(ConfigurableError):
45 class MultipleInstanceError(ConfigurableError):
46 pass
46 pass
47
47
48 #-----------------------------------------------------------------------------
48 #-----------------------------------------------------------------------------
49 # Configurable implementation
49 # Configurable implementation
50 #-----------------------------------------------------------------------------
50 #-----------------------------------------------------------------------------
51
51
52 class Configurable(HasTraits):
52 class Configurable(HasTraits):
53
53
54 config = Instance(Config, (), {})
54 config = Instance(Config, (), {})
55 parent = Instance('IPython.config.configurable.Configurable')
55 created = None
56 created = None
56
57
57 def __init__(self, **kwargs):
58 def __init__(self, **kwargs):
58 """Create a configurable given a config config.
59 """Create a configurable given a config config.
59
60
60 Parameters
61 Parameters
61 ----------
62 ----------
62 config : Config
63 config : Config
63 If this is empty, default values are used. If config is a
64 If this is empty, default values are used. If config is a
64 :class:`Config` instance, it will be used to configure the
65 :class:`Config` instance, it will be used to configure the
65 instance.
66 instance.
67 parent : Configurable instance
68 The parent
66
69
67 Notes
70 Notes
68 -----
71 -----
69 Subclasses of Configurable must call the :meth:`__init__` method of
72 Subclasses of Configurable must call the :meth:`__init__` method of
70 :class:`Configurable` *before* doing anything else and using
73 :class:`Configurable` *before* doing anything else and using
71 :func:`super`::
74 :func:`super`::
72
75
73 class MyConfigurable(Configurable):
76 class MyConfigurable(Configurable):
74 def __init__(self, config=None):
77 def __init__(self, config=None):
75 super(MyConfigurable, self).__init__(config=config)
78 super(MyConfigurable, self).__init__(config=config)
76 # Then any other code you need to finish initialization.
79 # Then any other code you need to finish initialization.
77
80
78 This ensures that instances will be configured properly.
81 This ensures that instances will be configured properly.
79 """
82 """
83 parent = kwargs.pop('parent', None)
84 if parent:
85 # config is implied from parent
86 if 'config' not in kwargs:
87 kwargs['config'] = parent.config
88 self.parent = parent
89
80 config = kwargs.pop('config', None)
90 config = kwargs.pop('config', None)
81 if config is not None:
91 if config is not None:
82 # We used to deepcopy, but for now we are trying to just save
92 # We used to deepcopy, but for now we are trying to just save
83 # by reference. This *could* have side effects as all components
93 # by reference. This *could* have side effects as all components
84 # will share config. In fact, I did find such a side effect in
94 # will share config. In fact, I did find such a side effect in
85 # _config_changed below. If a config attribute value was a mutable type
95 # _config_changed below. If a config attribute value was a mutable type
86 # all instances of a component were getting the same copy, effectively
96 # all instances of a component were getting the same copy, effectively
87 # making that a class attribute.
97 # making that a class attribute.
88 # self.config = deepcopy(config)
98 # self.config = deepcopy(config)
89 self.config = config
99 self.config = config
90 # This should go second so individual keyword arguments override
100 # This should go second so individual keyword arguments override
91 # the values in config.
101 # the values in config.
92 super(Configurable, self).__init__(**kwargs)
102 super(Configurable, self).__init__(**kwargs)
93 self.created = datetime.datetime.now()
103 self.created = datetime.datetime.now()
94
104
95 #-------------------------------------------------------------------------
105 #-------------------------------------------------------------------------
96 # Static trait notifiations
106 # Static trait notifiations
97 #-------------------------------------------------------------------------
107 #-------------------------------------------------------------------------
98
108
99 def _config_changed(self, name, old, new):
109 @classmethod
100 """Update all the class traits having ``config=True`` as metadata.
110 def section_names(cls):
111 """return section names as a list"""
112 return [c.__name__ for c in reversed(cls.__mro__) if
113 issubclass(c, Configurable) and issubclass(cls, c)
114 ]
101
115
102 For any class trait with a ``config`` metadata attribute that is
116 def _load_config(self, cfg, section_names=None, traits=None):
103 ``True``, we update the trait with the value of the corresponding
117 """load traits from a Config object"""
104 config entry.
105 """
106 # Get all traits with a config metadata entry that is True
107 traits = self.traits(config=True)
108
118
109 # We auto-load config section for this class as well as any parent
119 if traits is None:
110 # classes that are Configurable subclasses. This starts with Configurable
120 traits = self.traits(config=True)
111 # and works down the mro loading the config for each section.
121 if section_names is None:
112 section_names = [cls.__name__ for cls in \
122 section_names = self.section_names()
113 reversed(self.__class__.__mro__) if
114 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
115
123
116 for sname in section_names:
124 for sname in section_names:
117 # Don't do a blind getattr as that would cause the config to
125 # Don't do a blind getattr as that would cause the config to
118 # dynamically create the section with name self.__class__.__name__.
126 # dynamically create the section with name self.__class__.__name__.
119 if new._has_section(sname):
127 if cfg._has_section(sname):
120 my_config = new[sname]
128 my_config = cfg[sname]
121 for k, v in traits.iteritems():
129 for k, v in traits.iteritems():
122 # Don't allow traitlets with config=True to start with
123 # uppercase. Otherwise, they are confused with Config
124 # subsections. But, developers shouldn't have uppercase
125 # attributes anyways! (PEP 6)
126 if k[0].upper()==k[0] and not k.startswith('_'):
127 raise ConfigurableError('Configurable traitlets with '
128 'config=True must start with a lowercase so they are '
129 'not confused with Config subsections: %s.%s' % \
130 (self.__class__.__name__, k))
131 try:
130 try:
132 # Here we grab the value from the config
131 # Here we grab the value from the config
133 # If k has the naming convention of a config
132 # If k has the naming convention of a config
134 # section, it will be auto created.
133 # section, it will be auto created.
135 config_value = my_config[k]
134 config_value = my_config[k]
136 except KeyError:
135 except KeyError:
137 pass
136 pass
138 else:
137 else:
139 # print "Setting %s.%s from %s.%s=%r" % \
140 # (self.__class__.__name__,k,sname,k,config_value)
141 # We have to do a deepcopy here if we don't deepcopy the entire
138 # We have to do a deepcopy here if we don't deepcopy the entire
142 # config object. If we don't, a mutable config_value will be
139 # config object. If we don't, a mutable config_value will be
143 # shared by all instances, effectively making it a class attribute.
140 # shared by all instances, effectively making it a class attribute.
144 setattr(self, k, deepcopy(config_value))
141 setattr(self, k, deepcopy(config_value))
145
142
143 def _config_changed(self, name, old, new):
144 """Update all the class traits having ``config=True`` as metadata.
145
146 For any class trait with a ``config`` metadata attribute that is
147 ``True``, we update the trait with the value of the corresponding
148 config entry.
149 """
150 # Get all traits with a config metadata entry that is True
151 traits = self.traits(config=True)
152
153 # We auto-load config section for this class as well as any parent
154 # classes that are Configurable subclasses. This starts with Configurable
155 # and works down the mro loading the config for each section.
156 section_names = self.section_names()
157 self._load_config(new, traits=traits, section_names=section_names)
158
159 # load parent config as well, if we have one
160 parent_section_names = [] if self.parent is None else self.parent.section_names()
161 for parent in parent_section_names:
162 print parent, new._has_section(parent), new[parent]
163 if not new._has_section(parent):
164 continue
165 self._load_config(new[parent], traits=traits, section_names=section_names)
166
146 def update_config(self, config):
167 def update_config(self, config):
147 """Fire the traits events when the config is updated."""
168 """Fire the traits events when the config is updated."""
148 # Save a copy of the current config.
169 # Save a copy of the current config.
149 newconfig = deepcopy(self.config)
170 newconfig = deepcopy(self.config)
150 # Merge the new config into the current one.
171 # Merge the new config into the current one.
151 newconfig.merge(config)
172 newconfig.merge(config)
152 # Save the combined config as self.config, which triggers the traits
173 # Save the combined config as self.config, which triggers the traits
153 # events.
174 # events.
154 self.config = newconfig
175 self.config = newconfig
155
176
156 @classmethod
177 @classmethod
157 def class_get_help(cls, inst=None):
178 def class_get_help(cls, inst=None):
158 """Get the help string for this class in ReST format.
179 """Get the help string for this class in ReST format.
159
180
160 If `inst` is given, it's current trait values will be used in place of
181 If `inst` is given, it's current trait values will be used in place of
161 class defaults.
182 class defaults.
162 """
183 """
163 assert inst is None or isinstance(inst, cls)
184 assert inst is None or isinstance(inst, cls)
164 cls_traits = cls.class_traits(config=True)
165 final_help = []
185 final_help = []
166 final_help.append(u'%s options' % cls.__name__)
186 final_help.append(u'%s options' % cls.__name__)
167 final_help.append(len(final_help[0])*u'-')
187 final_help.append(len(final_help[0])*u'-')
168 for k, v in sorted(cls.class_traits(config=True).iteritems()):
188 for k, v in sorted(cls.class_traits(config=True).iteritems()):
169 help = cls.class_get_trait_help(v, inst)
189 help = cls.class_get_trait_help(v, inst)
170 final_help.append(help)
190 final_help.append(help)
171 return '\n'.join(final_help)
191 return '\n'.join(final_help)
172
192
173 @classmethod
193 @classmethod
174 def class_get_trait_help(cls, trait, inst=None):
194 def class_get_trait_help(cls, trait, inst=None):
175 """Get the help string for a single trait.
195 """Get the help string for a single trait.
176
196
177 If `inst` is given, it's current trait values will be used in place of
197 If `inst` is given, it's current trait values will be used in place of
178 the class default.
198 the class default.
179 """
199 """
180 assert inst is None or isinstance(inst, cls)
200 assert inst is None or isinstance(inst, cls)
181 lines = []
201 lines = []
182 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
202 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
183 lines.append(header)
203 lines.append(header)
184 if inst is not None:
204 if inst is not None:
185 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
205 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
186 else:
206 else:
187 try:
207 try:
188 dvr = repr(trait.get_default_value())
208 dvr = repr(trait.get_default_value())
189 except Exception:
209 except Exception:
190 dvr = None # ignore defaults we can't construct
210 dvr = None # ignore defaults we can't construct
191 if dvr is not None:
211 if dvr is not None:
192 if len(dvr) > 64:
212 if len(dvr) > 64:
193 dvr = dvr[:61]+'...'
213 dvr = dvr[:61]+'...'
194 lines.append(indent('Default: %s' % dvr, 4))
214 lines.append(indent('Default: %s' % dvr, 4))
195 if 'Enum' in trait.__class__.__name__:
215 if 'Enum' in trait.__class__.__name__:
196 # include Enum choices
216 # include Enum choices
197 lines.append(indent('Choices: %r' % (trait.values,)))
217 lines.append(indent('Choices: %r' % (trait.values,)))
198
218
199 help = trait.get_metadata('help')
219 help = trait.get_metadata('help')
200 if help is not None:
220 if help is not None:
201 help = '\n'.join(wrap_paragraphs(help, 76))
221 help = '\n'.join(wrap_paragraphs(help, 76))
202 lines.append(indent(help, 4))
222 lines.append(indent(help, 4))
203 return '\n'.join(lines)
223 return '\n'.join(lines)
204
224
205 @classmethod
225 @classmethod
206 def class_print_help(cls, inst=None):
226 def class_print_help(cls, inst=None):
207 """Get the help string for a single trait and print it."""
227 """Get the help string for a single trait and print it."""
208 print cls.class_get_help(inst)
228 print cls.class_get_help(inst)
209
229
210 @classmethod
230 @classmethod
211 def class_config_section(cls):
231 def class_config_section(cls):
212 """Get the config class config section"""
232 """Get the config class config section"""
213 def c(s):
233 def c(s):
214 """return a commented, wrapped block."""
234 """return a commented, wrapped block."""
215 s = '\n\n'.join(wrap_paragraphs(s, 78))
235 s = '\n\n'.join(wrap_paragraphs(s, 78))
216
236
217 return '# ' + s.replace('\n', '\n# ')
237 return '# ' + s.replace('\n', '\n# ')
218
238
219 # section header
239 # section header
220 breaker = '#' + '-'*78
240 breaker = '#' + '-'*78
221 s = "# %s configuration" % cls.__name__
241 s = "# %s configuration" % cls.__name__
222 lines = [breaker, s, breaker, '']
242 lines = [breaker, s, breaker, '']
223 # get the description trait
243 # get the description trait
224 desc = cls.class_traits().get('description')
244 desc = cls.class_traits().get('description')
225 if desc:
245 if desc:
226 desc = desc.default_value
246 desc = desc.default_value
227 else:
247 else:
228 # no description trait, use __doc__
248 # no description trait, use __doc__
229 desc = getattr(cls, '__doc__', '')
249 desc = getattr(cls, '__doc__', '')
230 if desc:
250 if desc:
231 lines.append(c(desc))
251 lines.append(c(desc))
232 lines.append('')
252 lines.append('')
233
253
234 parents = []
254 parents = []
235 for parent in cls.mro():
255 for parent in cls.mro():
236 # only include parents that are not base classes
256 # only include parents that are not base classes
237 # and are not the class itself
257 # and are not the class itself
238 # and have some configurable traits to inherit
258 # and have some configurable traits to inherit
239 if parent is not cls and issubclass(parent, Configurable) and \
259 if parent is not cls and issubclass(parent, Configurable) and \
240 parent.class_traits(config=True):
260 parent.class_traits(config=True):
241 parents.append(parent)
261 parents.append(parent)
242
262
243 if parents:
263 if parents:
244 pstr = ', '.join([ p.__name__ for p in parents ])
264 pstr = ', '.join([ p.__name__ for p in parents ])
245 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
265 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
246 lines.append('')
266 lines.append('')
247
267
248 for name, trait in cls.class_traits(config=True).iteritems():
268 for name, trait in cls.class_traits(config=True).iteritems():
249 help = trait.get_metadata('help') or ''
269 help = trait.get_metadata('help') or ''
250 lines.append(c(help))
270 lines.append(c(help))
251 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
271 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
252 lines.append('')
272 lines.append('')
253 return '\n'.join(lines)
273 return '\n'.join(lines)
254
274
255
275
256
276
257 class SingletonConfigurable(Configurable):
277 class SingletonConfigurable(Configurable):
258 """A configurable that only allows one instance.
278 """A configurable that only allows one instance.
259
279
260 This class is for classes that should only have one instance of itself
280 This class is for classes that should only have one instance of itself
261 or *any* subclass. To create and retrieve such a class use the
281 or *any* subclass. To create and retrieve such a class use the
262 :meth:`SingletonConfigurable.instance` method.
282 :meth:`SingletonConfigurable.instance` method.
263 """
283 """
264
284
265 _instance = None
285 _instance = None
266
286
267 @classmethod
287 @classmethod
268 def _walk_mro(cls):
288 def _walk_mro(cls):
269 """Walk the cls.mro() for parent classes that are also singletons
289 """Walk the cls.mro() for parent classes that are also singletons
270
290
271 For use in instance()
291 For use in instance()
272 """
292 """
273
293
274 for subclass in cls.mro():
294 for subclass in cls.mro():
275 if issubclass(cls, subclass) and \
295 if issubclass(cls, subclass) and \
276 issubclass(subclass, SingletonConfigurable) and \
296 issubclass(subclass, SingletonConfigurable) and \
277 subclass != SingletonConfigurable:
297 subclass != SingletonConfigurable:
278 yield subclass
298 yield subclass
279
299
280 @classmethod
300 @classmethod
281 def clear_instance(cls):
301 def clear_instance(cls):
282 """unset _instance for this class and singleton parents.
302 """unset _instance for this class and singleton parents.
283 """
303 """
284 if not cls.initialized():
304 if not cls.initialized():
285 return
305 return
286 for subclass in cls._walk_mro():
306 for subclass in cls._walk_mro():
287 if isinstance(subclass._instance, cls):
307 if isinstance(subclass._instance, cls):
288 # only clear instances that are instances
308 # only clear instances that are instances
289 # of the calling class
309 # of the calling class
290 subclass._instance = None
310 subclass._instance = None
291
311
292 @classmethod
312 @classmethod
293 def instance(cls, *args, **kwargs):
313 def instance(cls, *args, **kwargs):
294 """Returns a global instance of this class.
314 """Returns a global instance of this class.
295
315
296 This method create a new instance if none have previously been created
316 This method create a new instance if none have previously been created
297 and returns a previously created instance is one already exists.
317 and returns a previously created instance is one already exists.
298
318
299 The arguments and keyword arguments passed to this method are passed
319 The arguments and keyword arguments passed to this method are passed
300 on to the :meth:`__init__` method of the class upon instantiation.
320 on to the :meth:`__init__` method of the class upon instantiation.
301
321
302 Examples
322 Examples
303 --------
323 --------
304
324
305 Create a singleton class using instance, and retrieve it::
325 Create a singleton class using instance, and retrieve it::
306
326
307 >>> from IPython.config.configurable import SingletonConfigurable
327 >>> from IPython.config.configurable import SingletonConfigurable
308 >>> class Foo(SingletonConfigurable): pass
328 >>> class Foo(SingletonConfigurable): pass
309 >>> foo = Foo.instance()
329 >>> foo = Foo.instance()
310 >>> foo == Foo.instance()
330 >>> foo == Foo.instance()
311 True
331 True
312
332
313 Create a subclass that is retrived using the base class instance::
333 Create a subclass that is retrived using the base class instance::
314
334
315 >>> class Bar(SingletonConfigurable): pass
335 >>> class Bar(SingletonConfigurable): pass
316 >>> class Bam(Bar): pass
336 >>> class Bam(Bar): pass
317 >>> bam = Bam.instance()
337 >>> bam = Bam.instance()
318 >>> bam == Bar.instance()
338 >>> bam == Bar.instance()
319 True
339 True
320 """
340 """
321 # Create and save the instance
341 # Create and save the instance
322 if cls._instance is None:
342 if cls._instance is None:
323 inst = cls(*args, **kwargs)
343 inst = cls(*args, **kwargs)
324 # Now make sure that the instance will also be returned by
344 # Now make sure that the instance will also be returned by
325 # parent classes' _instance attribute.
345 # parent classes' _instance attribute.
326 for subclass in cls._walk_mro():
346 for subclass in cls._walk_mro():
327 subclass._instance = inst
347 subclass._instance = inst
328
348
329 if isinstance(cls._instance, cls):
349 if isinstance(cls._instance, cls):
330 return cls._instance
350 return cls._instance
331 else:
351 else:
332 raise MultipleInstanceError(
352 raise MultipleInstanceError(
333 'Multiple incompatible subclass instances of '
353 'Multiple incompatible subclass instances of '
334 '%s are being created.' % cls.__name__
354 '%s are being created.' % cls.__name__
335 )
355 )
336
356
337 @classmethod
357 @classmethod
338 def initialized(cls):
358 def initialized(cls):
339 """Has an instance been created?"""
359 """Has an instance been created?"""
340 return hasattr(cls, "_instance") and cls._instance is not None
360 return hasattr(cls, "_instance") and cls._instance is not None
341
361
342
362
343 class LoggingConfigurable(Configurable):
363 class LoggingConfigurable(Configurable):
344 """A parent class for Configurables that log.
364 """A parent class for Configurables that log.
345
365
346 Subclasses have a log trait, and the default behavior
366 Subclasses have a log trait, and the default behavior
347 is to get the logger from the currently running Application
367 is to get the logger from the currently running Application
348 via Application.instance().log.
368 via Application.instance().log.
349 """
369 """
350
370
351 log = Instance('logging.Logger')
371 log = Instance('logging.Logger')
352 def _log_default(self):
372 def _log_default(self):
353 from IPython.config.application import Application
373 from IPython.config.application import Application
354 return Application.instance().log
374 return Application.instance().log
355
375
356
376
@@ -1,183 +1,239 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 Tests for IPython.config.configurable
3 Tests for IPython.config.configurable
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez (design help)
8 * Fernando Perez (design help)
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 from unittest import TestCase
22 from unittest import TestCase
23
23
24 from IPython.config.configurable import (
24 from IPython.config.configurable import (
25 Configurable,
25 Configurable,
26 SingletonConfigurable
26 SingletonConfigurable
27 )
27 )
28
28
29 from IPython.utils.traitlets import (
29 from IPython.utils.traitlets import (
30 Integer, Float, Unicode
30 Integer, Float, Unicode
31 )
31 )
32
32
33 from IPython.config.loader import Config
33 from IPython.config.loader import Config
34 from IPython.utils.py3compat import PY3
34 from IPython.utils.py3compat import PY3
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Test cases
37 # Test cases
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40
40
41 class MyConfigurable(Configurable):
41 class MyConfigurable(Configurable):
42 a = Integer(1, config=True, help="The integer a.")
42 a = Integer(1, config=True, help="The integer a.")
43 b = Float(1.0, config=True, help="The integer b.")
43 b = Float(1.0, config=True, help="The integer b.")
44 c = Unicode('no config')
44 c = Unicode('no config')
45
45
46
46
47 mc_help=u"""MyConfigurable options
47 mc_help=u"""MyConfigurable options
48 ----------------------
48 ----------------------
49 --MyConfigurable.a=<Integer>
49 --MyConfigurable.a=<Integer>
50 Default: 1
50 Default: 1
51 The integer a.
51 The integer a.
52 --MyConfigurable.b=<Float>
52 --MyConfigurable.b=<Float>
53 Default: 1.0
53 Default: 1.0
54 The integer b."""
54 The integer b."""
55
55
56 mc_help_inst=u"""MyConfigurable options
56 mc_help_inst=u"""MyConfigurable options
57 ----------------------
57 ----------------------
58 --MyConfigurable.a=<Integer>
58 --MyConfigurable.a=<Integer>
59 Current: 5
59 Current: 5
60 The integer a.
60 The integer a.
61 --MyConfigurable.b=<Float>
61 --MyConfigurable.b=<Float>
62 Current: 4.0
62 Current: 4.0
63 The integer b."""
63 The integer b."""
64
64
65 # On Python 3, the Integer trait is a synonym for Int
65 # On Python 3, the Integer trait is a synonym for Int
66 if PY3:
66 if PY3:
67 mc_help = mc_help.replace(u"<Integer>", u"<Int>")
67 mc_help = mc_help.replace(u"<Integer>", u"<Int>")
68 mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>")
68 mc_help_inst = mc_help_inst.replace(u"<Integer>", u"<Int>")
69
69
70 class Foo(Configurable):
70 class Foo(Configurable):
71 a = Integer(0, config=True, help="The integer a.")
71 a = Integer(0, config=True, help="The integer a.")
72 b = Unicode('nope', config=True)
72 b = Unicode('nope', config=True)
73
73
74
74
75 class Bar(Foo):
75 class Bar(Foo):
76 b = Unicode('gotit', config=False, help="The string b.")
76 b = Unicode('gotit', config=False, help="The string b.")
77 c = Float(config=True, help="The string c.")
77 c = Float(config=True, help="The string c.")
78
78
79
79
80 class TestConfigurable(TestCase):
80 class TestConfigurable(TestCase):
81
81
82 def test_default(self):
82 def test_default(self):
83 c1 = Configurable()
83 c1 = Configurable()
84 c2 = Configurable(config=c1.config)
84 c2 = Configurable(config=c1.config)
85 c3 = Configurable(config=c2.config)
85 c3 = Configurable(config=c2.config)
86 self.assertEqual(c1.config, c2.config)
86 self.assertEqual(c1.config, c2.config)
87 self.assertEqual(c2.config, c3.config)
87 self.assertEqual(c2.config, c3.config)
88
88
89 def test_custom(self):
89 def test_custom(self):
90 config = Config()
90 config = Config()
91 config.foo = 'foo'
91 config.foo = 'foo'
92 config.bar = 'bar'
92 config.bar = 'bar'
93 c1 = Configurable(config=config)
93 c1 = Configurable(config=config)
94 c2 = Configurable(config=c1.config)
94 c2 = Configurable(config=c1.config)
95 c3 = Configurable(config=c2.config)
95 c3 = Configurable(config=c2.config)
96 self.assertEqual(c1.config, config)
96 self.assertEqual(c1.config, config)
97 self.assertEqual(c2.config, config)
97 self.assertEqual(c2.config, config)
98 self.assertEqual(c3.config, config)
98 self.assertEqual(c3.config, config)
99 # Test that copies are not made
99 # Test that copies are not made
100 self.assertTrue(c1.config is config)
100 self.assertTrue(c1.config is config)
101 self.assertTrue(c2.config is config)
101 self.assertTrue(c2.config is config)
102 self.assertTrue(c3.config is config)
102 self.assertTrue(c3.config is config)
103 self.assertTrue(c1.config is c2.config)
103 self.assertTrue(c1.config is c2.config)
104 self.assertTrue(c2.config is c3.config)
104 self.assertTrue(c2.config is c3.config)
105
105
106 def test_inheritance(self):
106 def test_inheritance(self):
107 config = Config()
107 config = Config()
108 config.MyConfigurable.a = 2
108 config.MyConfigurable.a = 2
109 config.MyConfigurable.b = 2.0
109 config.MyConfigurable.b = 2.0
110 c1 = MyConfigurable(config=config)
110 c1 = MyConfigurable(config=config)
111 c2 = MyConfigurable(config=c1.config)
111 c2 = MyConfigurable(config=c1.config)
112 self.assertEqual(c1.a, config.MyConfigurable.a)
112 self.assertEqual(c1.a, config.MyConfigurable.a)
113 self.assertEqual(c1.b, config.MyConfigurable.b)
113 self.assertEqual(c1.b, config.MyConfigurable.b)
114 self.assertEqual(c2.a, config.MyConfigurable.a)
114 self.assertEqual(c2.a, config.MyConfigurable.a)
115 self.assertEqual(c2.b, config.MyConfigurable.b)
115 self.assertEqual(c2.b, config.MyConfigurable.b)
116
116
117 def test_parent(self):
117 def test_parent(self):
118 config = Config()
118 config = Config()
119 config.Foo.a = 10
119 config.Foo.a = 10
120 config.Foo.b = "wow"
120 config.Foo.b = "wow"
121 config.Bar.b = 'later'
121 config.Bar.b = 'later'
122 config.Bar.c = 100.0
122 config.Bar.c = 100.0
123 f = Foo(config=config)
123 f = Foo(config=config)
124 b = Bar(config=f.config)
124 b = Bar(config=f.config)
125 self.assertEqual(f.a, 10)
125 self.assertEqual(f.a, 10)
126 self.assertEqual(f.b, 'wow')
126 self.assertEqual(f.b, 'wow')
127 self.assertEqual(b.b, 'gotit')
127 self.assertEqual(b.b, 'gotit')
128 self.assertEqual(b.c, 100.0)
128 self.assertEqual(b.c, 100.0)
129
129
130 def test_override1(self):
130 def test_override1(self):
131 config = Config()
131 config = Config()
132 config.MyConfigurable.a = 2
132 config.MyConfigurable.a = 2
133 config.MyConfigurable.b = 2.0
133 config.MyConfigurable.b = 2.0
134 c = MyConfigurable(a=3, config=config)
134 c = MyConfigurable(a=3, config=config)
135 self.assertEqual(c.a, 3)
135 self.assertEqual(c.a, 3)
136 self.assertEqual(c.b, config.MyConfigurable.b)
136 self.assertEqual(c.b, config.MyConfigurable.b)
137 self.assertEqual(c.c, 'no config')
137 self.assertEqual(c.c, 'no config')
138
138
139 def test_override2(self):
139 def test_override2(self):
140 config = Config()
140 config = Config()
141 config.Foo.a = 1
141 config.Foo.a = 1
142 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
142 config.Bar.b = 'or' # Up above b is config=False, so this won't do it.
143 config.Bar.c = 10.0
143 config.Bar.c = 10.0
144 c = Bar(config=config)
144 c = Bar(config=config)
145 self.assertEqual(c.a, config.Foo.a)
145 self.assertEqual(c.a, config.Foo.a)
146 self.assertEqual(c.b, 'gotit')
146 self.assertEqual(c.b, 'gotit')
147 self.assertEqual(c.c, config.Bar.c)
147 self.assertEqual(c.c, config.Bar.c)
148 c = Bar(a=2, b='and', c=20.0, config=config)
148 c = Bar(a=2, b='and', c=20.0, config=config)
149 self.assertEqual(c.a, 2)
149 self.assertEqual(c.a, 2)
150 self.assertEqual(c.b, 'and')
150 self.assertEqual(c.b, 'and')
151 self.assertEqual(c.c, 20.0)
151 self.assertEqual(c.c, 20.0)
152
152
153 def test_help(self):
153 def test_help(self):
154 self.assertEqual(MyConfigurable.class_get_help(), mc_help)
154 self.assertEqual(MyConfigurable.class_get_help(), mc_help)
155
155
156 def test_help_inst(self):
156 def test_help_inst(self):
157 inst = MyConfigurable(a=5, b=4)
157 inst = MyConfigurable(a=5, b=4)
158 self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
158 self.assertEqual(MyConfigurable.class_get_help(inst), mc_help_inst)
159
159
160
160
161 class TestSingletonConfigurable(TestCase):
161 class TestSingletonConfigurable(TestCase):
162
162
163 def test_instance(self):
163 def test_instance(self):
164 from IPython.config.configurable import SingletonConfigurable
164 from IPython.config.configurable import SingletonConfigurable
165 class Foo(SingletonConfigurable): pass
165 class Foo(SingletonConfigurable): pass
166 self.assertEqual(Foo.initialized(), False)
166 self.assertEqual(Foo.initialized(), False)
167 foo = Foo.instance()
167 foo = Foo.instance()
168 self.assertEqual(Foo.initialized(), True)
168 self.assertEqual(Foo.initialized(), True)
169 self.assertEqual(foo, Foo.instance())
169 self.assertEqual(foo, Foo.instance())
170 self.assertEqual(SingletonConfigurable._instance, None)
170 self.assertEqual(SingletonConfigurable._instance, None)
171
171
172 def test_inheritance(self):
172 def test_inheritance(self):
173 class Bar(SingletonConfigurable): pass
173 class Bar(SingletonConfigurable): pass
174 class Bam(Bar): pass
174 class Bam(Bar): pass
175 self.assertEqual(Bar.initialized(), False)
175 self.assertEqual(Bar.initialized(), False)
176 self.assertEqual(Bam.initialized(), False)
176 self.assertEqual(Bam.initialized(), False)
177 bam = Bam.instance()
177 bam = Bam.instance()
178 bam == Bar.instance()
178 bam == Bar.instance()
179 self.assertEqual(Bar.initialized(), True)
179 self.assertEqual(Bar.initialized(), True)
180 self.assertEqual(Bam.initialized(), True)
180 self.assertEqual(Bam.initialized(), True)
181 self.assertEqual(bam, Bam._instance)
181 self.assertEqual(bam, Bam._instance)
182 self.assertEqual(bam, Bar._instance)
182 self.assertEqual(bam, Bar._instance)
183 self.assertEqual(SingletonConfigurable._instance, None)
183 self.assertEqual(SingletonConfigurable._instance, None)
184
185
186 class MyParent(Configurable):
187 pass
188
189 class MyParent2(MyParent):
190 pass
191
192 class TestParentConfigurable(TestCase):
193
194 def test_parent_config(self):
195
196 cfg = Config({
197 'MyParent' : {
198 'MyConfigurable' : {
199 'b' : 2.0,
200 }
201 }
202 })
203 parent = MyParent(config=cfg)
204 myc = MyConfigurable(parent=parent)
205 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
206
207 def test_parent_inheritance(self):
208
209 cfg = Config({
210 'MyParent' : {
211 'MyConfigurable' : {
212 'b' : 2.0,
213 }
214 }
215 })
216 parent = MyParent2(config=cfg)
217 myc = MyConfigurable(parent=parent)
218 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
219
220 def test_parent_priority(self):
221
222 cfg = Config({
223 'MyConfigurable' : {
224 'b' : 2.0,
225 },
226 'MyParent' : {
227 'MyConfigurable' : {
228 'b' : 3.0,
229 }
230 },
231 'MyParent2' : {
232 'MyConfigurable' : {
233 'b' : 4.0,
234 }
235 }
236 })
237 parent = MyParent2(config=cfg)
238 myc = MyConfigurable(parent=parent)
239 self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
General Comments 0
You need to be logged in to leave comments. Login now