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