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