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