##// END OF EJS Templates
override null config with parent's config
MinRK -
Show More
@@ -1,376 +1,375 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
68 The parent
68 The parent
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 'config' not in kwargs:
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
115
116 def _load_config(self, cfg, section_names=None, traits=None):
116 def _load_config(self, cfg, section_names=None, traits=None):
117 """load traits from a Config object"""
117 """load traits from a Config object"""
118
118
119 if traits is None:
119 if traits is None:
120 traits = self.traits(config=True)
120 traits = self.traits(config=True)
121 if section_names is None:
121 if section_names is None:
122 section_names = self.section_names()
122 section_names = self.section_names()
123
123
124 for sname in section_names:
124 for sname in section_names:
125 # 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
126 # dynamically create the section with name self.__class__.__name__.
126 # dynamically create the section with name self.__class__.__name__.
127 if cfg._has_section(sname):
127 if cfg._has_section(sname):
128 my_config = cfg[sname]
128 my_config = cfg[sname]
129 for k, v in traits.iteritems():
129 for k, v in traits.iteritems():
130 try:
130 try:
131 # Here we grab the value from the config
131 # Here we grab the value from the config
132 # If k has the naming convention of a config
132 # If k has the naming convention of a config
133 # section, it will be auto created.
133 # section, it will be auto created.
134 config_value = my_config[k]
134 config_value = my_config[k]
135 except KeyError:
135 except KeyError:
136 pass
136 pass
137 else:
137 else:
138 # 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
139 # 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
140 # shared by all instances, effectively making it a class attribute.
140 # shared by all instances, effectively making it a class attribute.
141 setattr(self, k, deepcopy(config_value))
141 setattr(self, k, deepcopy(config_value))
142
142
143 def _config_changed(self, name, old, new):
143 def _config_changed(self, name, old, new):
144 """Update all the class traits having ``config=True`` as metadata.
144 """Update all the class traits having ``config=True`` as metadata.
145
145
146 For any class trait with a ``config`` metadata attribute that is
146 For any class trait with a ``config`` metadata attribute that is
147 ``True``, we update the trait with the value of the corresponding
147 ``True``, we update the trait with the value of the corresponding
148 config entry.
148 config entry.
149 """
149 """
150 # Get all traits with a config metadata entry that is True
150 # Get all traits with a config metadata entry that is True
151 traits = self.traits(config=True)
151 traits = self.traits(config=True)
152
152
153 # We auto-load config section for this class as well as any parent
153 # We auto-load config section for this class as well as any parent
154 # classes that are Configurable subclasses. This starts with Configurable
154 # classes that are Configurable subclasses. This starts with Configurable
155 # and works down the mro loading the config for each section.
155 # and works down the mro loading the config for each section.
156 section_names = self.section_names()
156 section_names = self.section_names()
157 self._load_config(new, traits=traits, section_names=section_names)
157 self._load_config(new, traits=traits, section_names=section_names)
158
158
159 # load parent config as well, if we have one
159 # load parent config as well, if we have one
160 parent_section_names = [] if self.parent is None else self.parent.section_names()
160 parent_section_names = [] if self.parent is None else self.parent.section_names()
161 for parent in 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):
162 if not new._has_section(parent):
164 continue
163 continue
165 self._load_config(new[parent], traits=traits, section_names=section_names)
164 self._load_config(new[parent], traits=traits, section_names=section_names)
166
165
167 def update_config(self, config):
166 def update_config(self, config):
168 """Fire the traits events when the config is updated."""
167 """Fire the traits events when the config is updated."""
169 # Save a copy of the current config.
168 # Save a copy of the current config.
170 newconfig = deepcopy(self.config)
169 newconfig = deepcopy(self.config)
171 # Merge the new config into the current one.
170 # Merge the new config into the current one.
172 newconfig.merge(config)
171 newconfig.merge(config)
173 # Save the combined config as self.config, which triggers the traits
172 # Save the combined config as self.config, which triggers the traits
174 # events.
173 # events.
175 self.config = newconfig
174 self.config = newconfig
176
175
177 @classmethod
176 @classmethod
178 def class_get_help(cls, inst=None):
177 def class_get_help(cls, inst=None):
179 """Get the help string for this class in ReST format.
178 """Get the help string for this class in ReST format.
180
179
181 If `inst` is given, it's current trait values will be used in place of
180 If `inst` is given, it's current trait values will be used in place of
182 class defaults.
181 class defaults.
183 """
182 """
184 assert inst is None or isinstance(inst, cls)
183 assert inst is None or isinstance(inst, cls)
185 final_help = []
184 final_help = []
186 final_help.append(u'%s options' % cls.__name__)
185 final_help.append(u'%s options' % cls.__name__)
187 final_help.append(len(final_help[0])*u'-')
186 final_help.append(len(final_help[0])*u'-')
188 for k, v in sorted(cls.class_traits(config=True).iteritems()):
187 for k, v in sorted(cls.class_traits(config=True).iteritems()):
189 help = cls.class_get_trait_help(v, inst)
188 help = cls.class_get_trait_help(v, inst)
190 final_help.append(help)
189 final_help.append(help)
191 return '\n'.join(final_help)
190 return '\n'.join(final_help)
192
191
193 @classmethod
192 @classmethod
194 def class_get_trait_help(cls, trait, inst=None):
193 def class_get_trait_help(cls, trait, inst=None):
195 """Get the help string for a single trait.
194 """Get the help string for a single trait.
196
195
197 If `inst` is given, it's current trait values will be used in place of
196 If `inst` is given, it's current trait values will be used in place of
198 the class default.
197 the class default.
199 """
198 """
200 assert inst is None or isinstance(inst, cls)
199 assert inst is None or isinstance(inst, cls)
201 lines = []
200 lines = []
202 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
201 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
203 lines.append(header)
202 lines.append(header)
204 if inst is not None:
203 if inst is not None:
205 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
204 lines.append(indent('Current: %r' % getattr(inst, trait.name), 4))
206 else:
205 else:
207 try:
206 try:
208 dvr = repr(trait.get_default_value())
207 dvr = repr(trait.get_default_value())
209 except Exception:
208 except Exception:
210 dvr = None # ignore defaults we can't construct
209 dvr = None # ignore defaults we can't construct
211 if dvr is not None:
210 if dvr is not None:
212 if len(dvr) > 64:
211 if len(dvr) > 64:
213 dvr = dvr[:61]+'...'
212 dvr = dvr[:61]+'...'
214 lines.append(indent('Default: %s' % dvr, 4))
213 lines.append(indent('Default: %s' % dvr, 4))
215 if 'Enum' in trait.__class__.__name__:
214 if 'Enum' in trait.__class__.__name__:
216 # include Enum choices
215 # include Enum choices
217 lines.append(indent('Choices: %r' % (trait.values,)))
216 lines.append(indent('Choices: %r' % (trait.values,)))
218
217
219 help = trait.get_metadata('help')
218 help = trait.get_metadata('help')
220 if help is not None:
219 if help is not None:
221 help = '\n'.join(wrap_paragraphs(help, 76))
220 help = '\n'.join(wrap_paragraphs(help, 76))
222 lines.append(indent(help, 4))
221 lines.append(indent(help, 4))
223 return '\n'.join(lines)
222 return '\n'.join(lines)
224
223
225 @classmethod
224 @classmethod
226 def class_print_help(cls, inst=None):
225 def class_print_help(cls, inst=None):
227 """Get the help string for a single trait and print it."""
226 """Get the help string for a single trait and print it."""
228 print cls.class_get_help(inst)
227 print cls.class_get_help(inst)
229
228
230 @classmethod
229 @classmethod
231 def class_config_section(cls):
230 def class_config_section(cls):
232 """Get the config class config section"""
231 """Get the config class config section"""
233 def c(s):
232 def c(s):
234 """return a commented, wrapped block."""
233 """return a commented, wrapped block."""
235 s = '\n\n'.join(wrap_paragraphs(s, 78))
234 s = '\n\n'.join(wrap_paragraphs(s, 78))
236
235
237 return '# ' + s.replace('\n', '\n# ')
236 return '# ' + s.replace('\n', '\n# ')
238
237
239 # section header
238 # section header
240 breaker = '#' + '-'*78
239 breaker = '#' + '-'*78
241 s = "# %s configuration" % cls.__name__
240 s = "# %s configuration" % cls.__name__
242 lines = [breaker, s, breaker, '']
241 lines = [breaker, s, breaker, '']
243 # get the description trait
242 # get the description trait
244 desc = cls.class_traits().get('description')
243 desc = cls.class_traits().get('description')
245 if desc:
244 if desc:
246 desc = desc.default_value
245 desc = desc.default_value
247 else:
246 else:
248 # no description trait, use __doc__
247 # no description trait, use __doc__
249 desc = getattr(cls, '__doc__', '')
248 desc = getattr(cls, '__doc__', '')
250 if desc:
249 if desc:
251 lines.append(c(desc))
250 lines.append(c(desc))
252 lines.append('')
251 lines.append('')
253
252
254 parents = []
253 parents = []
255 for parent in cls.mro():
254 for parent in cls.mro():
256 # only include parents that are not base classes
255 # only include parents that are not base classes
257 # and are not the class itself
256 # and are not the class itself
258 # and have some configurable traits to inherit
257 # and have some configurable traits to inherit
259 if parent is not cls and issubclass(parent, Configurable) and \
258 if parent is not cls and issubclass(parent, Configurable) and \
260 parent.class_traits(config=True):
259 parent.class_traits(config=True):
261 parents.append(parent)
260 parents.append(parent)
262
261
263 if parents:
262 if parents:
264 pstr = ', '.join([ p.__name__ for p in parents ])
263 pstr = ', '.join([ p.__name__ for p in parents ])
265 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
264 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
266 lines.append('')
265 lines.append('')
267
266
268 for name, trait in cls.class_traits(config=True).iteritems():
267 for name, trait in cls.class_traits(config=True).iteritems():
269 help = trait.get_metadata('help') or ''
268 help = trait.get_metadata('help') or ''
270 lines.append(c(help))
269 lines.append(c(help))
271 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
270 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
272 lines.append('')
271 lines.append('')
273 return '\n'.join(lines)
272 return '\n'.join(lines)
274
273
275
274
276
275
277 class SingletonConfigurable(Configurable):
276 class SingletonConfigurable(Configurable):
278 """A configurable that only allows one instance.
277 """A configurable that only allows one instance.
279
278
280 This class is for classes that should only have one instance of itself
279 This class is for classes that should only have one instance of itself
281 or *any* subclass. To create and retrieve such a class use the
280 or *any* subclass. To create and retrieve such a class use the
282 :meth:`SingletonConfigurable.instance` method.
281 :meth:`SingletonConfigurable.instance` method.
283 """
282 """
284
283
285 _instance = None
284 _instance = None
286
285
287 @classmethod
286 @classmethod
288 def _walk_mro(cls):
287 def _walk_mro(cls):
289 """Walk the cls.mro() for parent classes that are also singletons
288 """Walk the cls.mro() for parent classes that are also singletons
290
289
291 For use in instance()
290 For use in instance()
292 """
291 """
293
292
294 for subclass in cls.mro():
293 for subclass in cls.mro():
295 if issubclass(cls, subclass) and \
294 if issubclass(cls, subclass) and \
296 issubclass(subclass, SingletonConfigurable) and \
295 issubclass(subclass, SingletonConfigurable) and \
297 subclass != SingletonConfigurable:
296 subclass != SingletonConfigurable:
298 yield subclass
297 yield subclass
299
298
300 @classmethod
299 @classmethod
301 def clear_instance(cls):
300 def clear_instance(cls):
302 """unset _instance for this class and singleton parents.
301 """unset _instance for this class and singleton parents.
303 """
302 """
304 if not cls.initialized():
303 if not cls.initialized():
305 return
304 return
306 for subclass in cls._walk_mro():
305 for subclass in cls._walk_mro():
307 if isinstance(subclass._instance, cls):
306 if isinstance(subclass._instance, cls):
308 # only clear instances that are instances
307 # only clear instances that are instances
309 # of the calling class
308 # of the calling class
310 subclass._instance = None
309 subclass._instance = None
311
310
312 @classmethod
311 @classmethod
313 def instance(cls, *args, **kwargs):
312 def instance(cls, *args, **kwargs):
314 """Returns a global instance of this class.
313 """Returns a global instance of this class.
315
314
316 This method create a new instance if none have previously been created
315 This method create a new instance if none have previously been created
317 and returns a previously created instance is one already exists.
316 and returns a previously created instance is one already exists.
318
317
319 The arguments and keyword arguments passed to this method are passed
318 The arguments and keyword arguments passed to this method are passed
320 on to the :meth:`__init__` method of the class upon instantiation.
319 on to the :meth:`__init__` method of the class upon instantiation.
321
320
322 Examples
321 Examples
323 --------
322 --------
324
323
325 Create a singleton class using instance, and retrieve it::
324 Create a singleton class using instance, and retrieve it::
326
325
327 >>> from IPython.config.configurable import SingletonConfigurable
326 >>> from IPython.config.configurable import SingletonConfigurable
328 >>> class Foo(SingletonConfigurable): pass
327 >>> class Foo(SingletonConfigurable): pass
329 >>> foo = Foo.instance()
328 >>> foo = Foo.instance()
330 >>> foo == Foo.instance()
329 >>> foo == Foo.instance()
331 True
330 True
332
331
333 Create a subclass that is retrived using the base class instance::
332 Create a subclass that is retrived using the base class instance::
334
333
335 >>> class Bar(SingletonConfigurable): pass
334 >>> class Bar(SingletonConfigurable): pass
336 >>> class Bam(Bar): pass
335 >>> class Bam(Bar): pass
337 >>> bam = Bam.instance()
336 >>> bam = Bam.instance()
338 >>> bam == Bar.instance()
337 >>> bam == Bar.instance()
339 True
338 True
340 """
339 """
341 # Create and save the instance
340 # Create and save the instance
342 if cls._instance is None:
341 if cls._instance is None:
343 inst = cls(*args, **kwargs)
342 inst = cls(*args, **kwargs)
344 # Now make sure that the instance will also be returned by
343 # Now make sure that the instance will also be returned by
345 # parent classes' _instance attribute.
344 # parent classes' _instance attribute.
346 for subclass in cls._walk_mro():
345 for subclass in cls._walk_mro():
347 subclass._instance = inst
346 subclass._instance = inst
348
347
349 if isinstance(cls._instance, cls):
348 if isinstance(cls._instance, cls):
350 return cls._instance
349 return cls._instance
351 else:
350 else:
352 raise MultipleInstanceError(
351 raise MultipleInstanceError(
353 'Multiple incompatible subclass instances of '
352 'Multiple incompatible subclass instances of '
354 '%s are being created.' % cls.__name__
353 '%s are being created.' % cls.__name__
355 )
354 )
356
355
357 @classmethod
356 @classmethod
358 def initialized(cls):
357 def initialized(cls):
359 """Has an instance been created?"""
358 """Has an instance been created?"""
360 return hasattr(cls, "_instance") and cls._instance is not None
359 return hasattr(cls, "_instance") and cls._instance is not None
361
360
362
361
363 class LoggingConfigurable(Configurable):
362 class LoggingConfigurable(Configurable):
364 """A parent class for Configurables that log.
363 """A parent class for Configurables that log.
365
364
366 Subclasses have a log trait, and the default behavior
365 Subclasses have a log trait, and the default behavior
367 is to get the logger from the currently running Application
366 is to get the logger from the currently running Application
368 via Application.instance().log.
367 via Application.instance().log.
369 """
368 """
370
369
371 log = Instance('logging.Logger')
370 log = Instance('logging.Logger')
372 def _log_default(self):
371 def _log_default(self):
373 from IPython.config.application import Application
372 from IPython.config.application import Application
374 return Application.instance().log
373 return Application.instance().log
375
374
376
375
General Comments 0
You need to be logged in to leave comments. Login now