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