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