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