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