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