##// END OF EJS Templates
allow multi-level Config parentage...
MinRK -
Show More
@@ -1,375 +1,382 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 parent = Instance('IPython.config.configurable.Configurable')
56 created = None
56 created = None
57
57
58 def __init__(self, **kwargs):
58 def __init__(self, **kwargs):
59 """Create a configurable given a config config.
59 """Create a configurable given a config config.
60
60
61 Parameters
61 Parameters
62 ----------
62 ----------
63 config : Config
63 config : Config
64 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
65 :class:`Config` instance, it will be used to configure the
65 :class:`Config` instance, it will be used to configure the
66 instance.
66 instance.
67 parent : Configurable instance
67 parent : Configurable instance, optional
68 The parent
68 The parent Configurable instance of this object.
69
69
70 Notes
70 Notes
71 -----
71 -----
72 Subclasses of Configurable must call the :meth:`__init__` method of
72 Subclasses of Configurable must call the :meth:`__init__` method of
73 :class:`Configurable` *before* doing anything else and using
73 :class:`Configurable` *before* doing anything else and using
74 :func:`super`::
74 :func:`super`::
75
75
76 class MyConfigurable(Configurable):
76 class MyConfigurable(Configurable):
77 def __init__(self, config=None):
77 def __init__(self, config=None):
78 super(MyConfigurable, self).__init__(config=config)
78 super(MyConfigurable, self).__init__(config=config)
79 # Then any other code you need to finish initialization.
79 # Then any other code you need to finish initialization.
80
80
81 This ensures that instances will be configured properly.
81 This ensures that instances will be configured properly.
82 """
82 """
83 parent = kwargs.pop('parent', None)
83 parent = kwargs.pop('parent', None)
84 if parent:
84 if parent:
85 # config is implied from parent
85 # config is implied from parent
86 if kwargs.get('config', None) is None:
86 if kwargs.get('config', None) is None:
87 kwargs['config'] = parent.config
87 kwargs['config'] = parent.config
88 self.parent = parent
88 self.parent = parent
89
89
90 config = kwargs.pop('config', None)
90 config = kwargs.pop('config', None)
91 if config is not None:
91 if config is not None:
92 # 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
93 # by reference. This *could* have side effects as all components
93 # by reference. This *could* have side effects as all components
94 # 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
95 # _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
96 # all instances of a component were getting the same copy, effectively
96 # all instances of a component were getting the same copy, effectively
97 # making that a class attribute.
97 # making that a class attribute.
98 # self.config = deepcopy(config)
98 # self.config = deepcopy(config)
99 self.config = config
99 self.config = config
100 # This should go second so individual keyword arguments override
100 # This should go second so individual keyword arguments override
101 # the values in config.
101 # the values in config.
102 super(Configurable, self).__init__(**kwargs)
102 super(Configurable, self).__init__(**kwargs)
103 self.created = datetime.datetime.now()
103 self.created = datetime.datetime.now()
104
104
105 #-------------------------------------------------------------------------
105 #-------------------------------------------------------------------------
106 # Static trait notifiations
106 # Static trait notifiations
107 #-------------------------------------------------------------------------
107 #-------------------------------------------------------------------------
108
108
109 @classmethod
109 @classmethod
110 def section_names(cls):
110 def section_names(cls):
111 """return section names as a list"""
111 """return section names as a list"""
112 return [c.__name__ for c in reversed(cls.__mro__) if
112 return [c.__name__ for c in reversed(cls.__mro__) if
113 issubclass(c, Configurable) and issubclass(cls, c)
113 issubclass(c, Configurable) and issubclass(cls, c)
114 ]
114 ]
115
116 def _find_my_config(self, cfg):
117 """extract my config from a global Config object
118
119 will construct a Config object of only the config values that apply to me
120 based on my mro(), as well as those of my parent(s) if they exist.
121
122 If I am Bar and my parent is Foo, and their parent is Tim,
123 this will return merge following config sections, in this order::
124
125 [Bar, Foo.bar, Tim.Foo.Bar]
126
127 With the last item being the highest priority.
128 """
129 cfgs = [cfg]
130 if self.parent:
131 cfgs.append(self.parent._find_my_config(cfg))
132 my_config = Config()
133 for c in cfgs:
134 for sname in self.section_names():
135 # Don't do a blind getattr as that would cause the config to
136 # dynamically create the section with name Class.__name__.
137 if c._has_section(sname):
138 my_config.merge(c[sname])
139 return my_config
115
140
116 def _load_config(self, cfg, section_names=None, traits=None):
141 def _load_config(self, cfg, section_names=None, traits=None):
117 """load traits from a Config object"""
142 """load traits from a Config object"""
118
143
119 if traits is None:
144 if traits is None:
120 traits = self.traits(config=True)
145 traits = self.traits(config=True)
121 if section_names is None:
146 if section_names is None:
122 section_names = self.section_names()
147 section_names = self.section_names()
123
148
124 for sname in section_names:
149 my_config = self._find_my_config(cfg)
125 # Don't do a blind getattr as that would cause the config to
150 for name, config_value in my_config.iteritems():
126 # dynamically create the section with name self.__class__.__name__.
151 if name in traits:
127 if cfg._has_section(sname):
152 # We have to do a deepcopy here if we don't deepcopy the entire
128 my_config = cfg[sname]
153 # config object. If we don't, a mutable config_value will be
129 for k, v in traits.iteritems():
154 # shared by all instances, effectively making it a class attribute.
130 try:
155 setattr(self, name, deepcopy(config_value))
131 # Here we grab the value from the config
132 # If k has the naming convention of a config
133 # section, it will be auto created.
134 config_value = my_config[k]
135 except KeyError:
136 pass
137 else:
138 # We have to do a deepcopy here if we don't deepcopy the entire
139 # config object. If we don't, a mutable config_value will be
140 # shared by all instances, effectively making it a class attribute.
141 setattr(self, k, deepcopy(config_value))
142
156
143 def _config_changed(self, name, old, new):
157 def _config_changed(self, name, old, new):
144 """Update all the class traits having ``config=True`` as metadata.
158 """Update all the class traits having ``config=True`` as metadata.
145
159
146 For any class trait with a ``config`` metadata attribute that is
160 For any class trait with a ``config`` metadata attribute that is
147 ``True``, we update the trait with the value of the corresponding
161 ``True``, we update the trait with the value of the corresponding
148 config entry.
162 config entry.
149 """
163 """
150 # Get all traits with a config metadata entry that is True
164 # Get all traits with a config metadata entry that is True
151 traits = self.traits(config=True)
165 traits = self.traits(config=True)
152
166
153 # We auto-load config section for this class as well as any parent
167 # We auto-load config section for this class as well as any parent
154 # classes that are Configurable subclasses. This starts with Configurable
168 # classes that are Configurable subclasses. This starts with Configurable
155 # and works down the mro loading the config for each section.
169 # and works down the mro loading the config for each section.
156 section_names = self.section_names()
170 section_names = self.section_names()
157 self._load_config(new, traits=traits, section_names=section_names)
171 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 if not new._has_section(parent):
163 continue
164 self._load_config(new[parent], traits=traits, section_names=section_names)
165
172
166 def update_config(self, config):
173 def update_config(self, config):
167 """Fire the traits events when the config is updated."""
174 """Fire the traits events when the config is updated."""
168 # Save a copy of the current config.
175 # Save a copy of the current config.
169 newconfig = deepcopy(self.config)
176 newconfig = deepcopy(self.config)
170 # Merge the new config into the current one.
177 # Merge the new config into the current one.
171 newconfig.merge(config)
178 newconfig.merge(config)
172 # Save the combined config as self.config, which triggers the traits
179 # Save the combined config as self.config, which triggers the traits
173 # events.
180 # events.
174 self.config = newconfig
181 self.config = newconfig
175
182
176 @classmethod
183 @classmethod
177 def class_get_help(cls, inst=None):
184 def class_get_help(cls, inst=None):
178 """Get the help string for this class in ReST format.
185 """Get the help string for this class in ReST format.
179
186
180 If `inst` is given, it's current trait values will be used in place of
187 If `inst` is given, it's current trait values will be used in place of
181 class defaults.
188 class defaults.
182 """
189 """
183 assert inst is None or isinstance(inst, cls)
190 assert inst is None or isinstance(inst, cls)
184 final_help = []
191 final_help = []
185 final_help.append(u'%s options' % cls.__name__)
192 final_help.append(u'%s options' % cls.__name__)
186 final_help.append(len(final_help[0])*u'-')
193 final_help.append(len(final_help[0])*u'-')
187 for k, v in sorted(cls.class_traits(config=True).iteritems()):
194 for k, v in sorted(cls.class_traits(config=True).iteritems()):
188 help = cls.class_get_trait_help(v, inst)
195 help = cls.class_get_trait_help(v, inst)
189 final_help.append(help)
196 final_help.append(help)
190 return '\n'.join(final_help)
197 return '\n'.join(final_help)
191
198
192 @classmethod
199 @classmethod
193 def class_get_trait_help(cls, trait, inst=None):
200 def class_get_trait_help(cls, trait, inst=None):
194 """Get the help string for a single trait.
201 """Get the help string for a single trait.
195
202
196 If `inst` is given, it's current trait values will be used in place of
203 If `inst` is given, it's current trait values will be used in place of
197 the class default.
204 the class default.
198 """
205 """
199 assert inst is None or isinstance(inst, cls)
206 assert inst is None or isinstance(inst, cls)
200 lines = []
207 lines = []
201 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
208 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
202 lines.append(header)
209 lines.append(header)
203 if inst is not None:
210 if inst is not None:
204 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
211 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
205 else:
212 else:
206 try:
213 try:
207 dvr = repr(trait.get_default_value())
214 dvr = repr(trait.get_default_value())
208 except Exception:
215 except Exception:
209 dvr = None # ignore defaults we can't construct
216 dvr = None # ignore defaults we can't construct
210 if dvr is not None:
217 if dvr is not None:
211 if len(dvr) > 64:
218 if len(dvr) > 64:
212 dvr = dvr[:61]+'...'
219 dvr = dvr[:61]+'...'
213 lines.append(indent('Default: %s' % dvr, 4))
220 lines.append(indent('Default: %s' % dvr, 4))
214 if 'Enum' in trait.__class__.__name__:
221 if 'Enum' in trait.__class__.__name__:
215 # include Enum choices
222 # include Enum choices
216 lines.append(indent('Choices: %r' % (trait.values,)))
223 lines.append(indent('Choices: %r' % (trait.values,)))
217
224
218 help = trait.get_metadata('help')
225 help = trait.get_metadata('help')
219 if help is not None:
226 if help is not None:
220 help = '\n'.join(wrap_paragraphs(help, 76))
227 help = '\n'.join(wrap_paragraphs(help, 76))
221 lines.append(indent(help, 4))
228 lines.append(indent(help, 4))
222 return '\n'.join(lines)
229 return '\n'.join(lines)
223
230
224 @classmethod
231 @classmethod
225 def class_print_help(cls, inst=None):
232 def class_print_help(cls, inst=None):
226 """Get the help string for a single trait and print it."""
233 """Get the help string for a single trait and print it."""
227 print cls.class_get_help(inst)
234 print cls.class_get_help(inst)
228
235
229 @classmethod
236 @classmethod
230 def class_config_section(cls):
237 def class_config_section(cls):
231 """Get the config class config section"""
238 """Get the config class config section"""
232 def c(s):
239 def c(s):
233 """return a commented, wrapped block."""
240 """return a commented, wrapped block."""
234 s = '\n\n'.join(wrap_paragraphs(s, 78))
241 s = '\n\n'.join(wrap_paragraphs(s, 78))
235
242
236 return '# ' + s.replace('\n', '\n# ')
243 return '# ' + s.replace('\n', '\n# ')
237
244
238 # section header
245 # section header
239 breaker = '#' + '-'*78
246 breaker = '#' + '-'*78
240 s = "# %s configuration" % cls.__name__
247 s = "# %s configuration" % cls.__name__
241 lines = [breaker, s, breaker, '']
248 lines = [breaker, s, breaker, '']
242 # get the description trait
249 # get the description trait
243 desc = cls.class_traits().get('description')
250 desc = cls.class_traits().get('description')
244 if desc:
251 if desc:
245 desc = desc.default_value
252 desc = desc.default_value
246 else:
253 else:
247 # no description trait, use __doc__
254 # no description trait, use __doc__
248 desc = getattr(cls, '__doc__', '')
255 desc = getattr(cls, '__doc__', '')
249 if desc:
256 if desc:
250 lines.append(c(desc))
257 lines.append(c(desc))
251 lines.append('')
258 lines.append('')
252
259
253 parents = []
260 parents = []
254 for parent in cls.mro():
261 for parent in cls.mro():
255 # only include parents that are not base classes
262 # only include parents that are not base classes
256 # and are not the class itself
263 # and are not the class itself
257 # and have some configurable traits to inherit
264 # and have some configurable traits to inherit
258 if parent is not cls and issubclass(parent, Configurable) and \
265 if parent is not cls and issubclass(parent, Configurable) and \
259 parent.class_traits(config=True):
266 parent.class_traits(config=True):
260 parents.append(parent)
267 parents.append(parent)
261
268
262 if parents:
269 if parents:
263 pstr = ', '.join([ p.__name__ for p in parents ])
270 pstr = ', '.join([ p.__name__ for p in parents ])
264 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
271 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
265 lines.append('')
272 lines.append('')
266
273
267 for name, trait in cls.class_traits(config=True).iteritems():
274 for name, trait in cls.class_traits(config=True).iteritems():
268 help = trait.get_metadata('help') or ''
275 help = trait.get_metadata('help') or ''
269 lines.append(c(help))
276 lines.append(c(help))
270 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
277 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
271 lines.append('')
278 lines.append('')
272 return '\n'.join(lines)
279 return '\n'.join(lines)
273
280
274
281
275
282
276 class SingletonConfigurable(Configurable):
283 class SingletonConfigurable(Configurable):
277 """A configurable that only allows one instance.
284 """A configurable that only allows one instance.
278
285
279 This class is for classes that should only have one instance of itself
286 This class is for classes that should only have one instance of itself
280 or *any* subclass. To create and retrieve such a class use the
287 or *any* subclass. To create and retrieve such a class use the
281 :meth:`SingletonConfigurable.instance` method.
288 :meth:`SingletonConfigurable.instance` method.
282 """
289 """
283
290
284 _instance = None
291 _instance = None
285
292
286 @classmethod
293 @classmethod
287 def _walk_mro(cls):
294 def _walk_mro(cls):
288 """Walk the cls.mro() for parent classes that are also singletons
295 """Walk the cls.mro() for parent classes that are also singletons
289
296
290 For use in instance()
297 For use in instance()
291 """
298 """
292
299
293 for subclass in cls.mro():
300 for subclass in cls.mro():
294 if issubclass(cls, subclass) and \
301 if issubclass(cls, subclass) and \
295 issubclass(subclass, SingletonConfigurable) and \
302 issubclass(subclass, SingletonConfigurable) and \
296 subclass != SingletonConfigurable:
303 subclass != SingletonConfigurable:
297 yield subclass
304 yield subclass
298
305
299 @classmethod
306 @classmethod
300 def clear_instance(cls):
307 def clear_instance(cls):
301 """unset _instance for this class and singleton parents.
308 """unset _instance for this class and singleton parents.
302 """
309 """
303 if not cls.initialized():
310 if not cls.initialized():
304 return
311 return
305 for subclass in cls._walk_mro():
312 for subclass in cls._walk_mro():
306 if isinstance(subclass._instance, cls):
313 if isinstance(subclass._instance, cls):
307 # only clear instances that are instances
314 # only clear instances that are instances
308 # of the calling class
315 # of the calling class
309 subclass._instance = None
316 subclass._instance = None
310
317
311 @classmethod
318 @classmethod
312 def instance(cls, *args, **kwargs):
319 def instance(cls, *args, **kwargs):
313 """Returns a global instance of this class.
320 """Returns a global instance of this class.
314
321
315 This method create a new instance if none have previously been created
322 This method create a new instance if none have previously been created
316 and returns a previously created instance is one already exists.
323 and returns a previously created instance is one already exists.
317
324
318 The arguments and keyword arguments passed to this method are passed
325 The arguments and keyword arguments passed to this method are passed
319 on to the :meth:`__init__` method of the class upon instantiation.
326 on to the :meth:`__init__` method of the class upon instantiation.
320
327
321 Examples
328 Examples
322 --------
329 --------
323
330
324 Create a singleton class using instance, and retrieve it::
331 Create a singleton class using instance, and retrieve it::
325
332
326 >>> from IPython.config.configurable import SingletonConfigurable
333 >>> from IPython.config.configurable import SingletonConfigurable
327 >>> class Foo(SingletonConfigurable): pass
334 >>> class Foo(SingletonConfigurable): pass
328 >>> foo = Foo.instance()
335 >>> foo = Foo.instance()
329 >>> foo == Foo.instance()
336 >>> foo == Foo.instance()
330 True
337 True
331
338
332 Create a subclass that is retrived using the base class instance::
339 Create a subclass that is retrived using the base class instance::
333
340
334 >>> class Bar(SingletonConfigurable): pass
341 >>> class Bar(SingletonConfigurable): pass
335 >>> class Bam(Bar): pass
342 >>> class Bam(Bar): pass
336 >>> bam = Bam.instance()
343 >>> bam = Bam.instance()
337 >>> bam == Bar.instance()
344 >>> bam == Bar.instance()
338 True
345 True
339 """
346 """
340 # Create and save the instance
347 # Create and save the instance
341 if cls._instance is None:
348 if cls._instance is None:
342 inst = cls(*args, **kwargs)
349 inst = cls(*args, **kwargs)
343 # Now make sure that the instance will also be returned by
350 # Now make sure that the instance will also be returned by
344 # parent classes' _instance attribute.
351 # parent classes' _instance attribute.
345 for subclass in cls._walk_mro():
352 for subclass in cls._walk_mro():
346 subclass._instance = inst
353 subclass._instance = inst
347
354
348 if isinstance(cls._instance, cls):
355 if isinstance(cls._instance, cls):
349 return cls._instance
356 return cls._instance
350 else:
357 else:
351 raise MultipleInstanceError(
358 raise MultipleInstanceError(
352 'Multiple incompatible subclass instances of '
359 'Multiple incompatible subclass instances of '
353 '%s are being created.' % cls.__name__
360 '%s are being created.' % cls.__name__
354 )
361 )
355
362
356 @classmethod
363 @classmethod
357 def initialized(cls):
364 def initialized(cls):
358 """Has an instance been created?"""
365 """Has an instance been created?"""
359 return hasattr(cls, "_instance") and cls._instance is not None
366 return hasattr(cls, "_instance") and cls._instance is not None
360
367
361
368
362 class LoggingConfigurable(Configurable):
369 class LoggingConfigurable(Configurable):
363 """A parent class for Configurables that log.
370 """A parent class for Configurables that log.
364
371
365 Subclasses have a log trait, and the default behavior
372 Subclasses have a log trait, and the default behavior
366 is to get the logger from the currently running Application
373 is to get the logger from the currently running Application
367 via Application.instance().log.
374 via Application.instance().log.
368 """
375 """
369
376
370 log = Instance('logging.Logger')
377 log = Instance('logging.Logger')
371 def _log_default(self):
378 def _log_default(self):
372 from IPython.config.application import Application
379 from IPython.config.application import Application
373 return Application.instance().log
380 return Application.instance().log
374
381
375
382
@@ -1,239 +1,284 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
184
185
185
186 class MyParent(Configurable):
186 class MyParent(Configurable):
187 pass
187 pass
188
188
189 class MyParent2(MyParent):
189 class MyParent2(MyParent):
190 pass
190 pass
191
191
192 class TestParentConfigurable(TestCase):
192 class TestParentConfigurable(TestCase):
193
193
194 def test_parent_config(self):
194 def test_parent_config(self):
195
196 cfg = Config({
195 cfg = Config({
197 'MyParent' : {
196 'MyParent' : {
198 'MyConfigurable' : {
197 'MyConfigurable' : {
199 'b' : 2.0,
198 'b' : 2.0,
200 }
199 }
201 }
200 }
202 })
201 })
203 parent = MyParent(config=cfg)
202 parent = MyParent(config=cfg)
204 myc = MyConfigurable(parent=parent)
203 myc = MyConfigurable(parent=parent)
205 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
204 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
206
205
207 def test_parent_inheritance(self):
206 def test_parent_inheritance(self):
208
209 cfg = Config({
207 cfg = Config({
210 'MyParent' : {
208 'MyParent' : {
211 'MyConfigurable' : {
209 'MyConfigurable' : {
212 'b' : 2.0,
210 'b' : 2.0,
213 }
211 }
214 }
212 }
215 })
213 })
216 parent = MyParent2(config=cfg)
214 parent = MyParent2(config=cfg)
217 myc = MyConfigurable(parent=parent)
215 myc = MyConfigurable(parent=parent)
218 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
216 self.assertEqual(myc.b, parent.config.MyParent.MyConfigurable.b)
219
217
218 def test_multi_parent(self):
219 cfg = Config({
220 'MyParent2' : {
221 'MyParent' : {
222 'MyConfigurable' : {
223 'b' : 2.0,
224 }
225 },
226 # this one shouldn't count
227 'MyConfigurable' : {
228 'b' : 3.0,
229 },
230 }
231 })
232 parent2 = MyParent2(config=cfg)
233 parent = MyParent(parent=parent2)
234 myc = MyConfigurable(parent=parent)
235 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
236
220 def test_parent_priority(self):
237 def test_parent_priority(self):
221
222 cfg = Config({
238 cfg = Config({
223 'MyConfigurable' : {
239 'MyConfigurable' : {
224 'b' : 2.0,
240 'b' : 2.0,
225 },
241 },
226 'MyParent' : {
242 'MyParent' : {
227 'MyConfigurable' : {
243 'MyConfigurable' : {
228 'b' : 3.0,
244 'b' : 3.0,
229 }
245 }
230 },
246 },
231 'MyParent2' : {
247 'MyParent2' : {
232 'MyConfigurable' : {
248 'MyConfigurable' : {
233 'b' : 4.0,
249 'b' : 4.0,
234 }
250 }
235 }
251 }
236 })
252 })
237 parent = MyParent2(config=cfg)
253 parent = MyParent2(config=cfg)
238 myc = MyConfigurable(parent=parent)
254 myc = MyConfigurable(parent=parent)
239 self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
255 self.assertEqual(myc.b, parent.config.MyParent2.MyConfigurable.b)
256
257 def test_multi_parent_priority(self):
258 cfg = Config({
259 'MyConfigurable' : {
260 'b' : 2.0,
261 },
262 'MyParent' : {
263 'MyConfigurable' : {
264 'b' : 3.0,
265 }
266 },
267 'MyParent2' : {
268 'MyConfigurable' : {
269 'b' : 4.0,
270 }
271 },
272 'MyParent2' : {
273 'MyParent' : {
274 'MyConfigurable' : {
275 'b' : 5.0,
276 }
277 }
278 }
279 })
280 parent2 = MyParent2(config=cfg)
281 parent = MyParent2(parent=parent2)
282 myc = MyConfigurable(parent=parent)
283 self.assertEqual(myc.b, parent.config.MyParent2.MyParent.MyConfigurable.b)
284
General Comments 0
You need to be logged in to leave comments. Login now