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