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