##// END OF EJS Templates
cleanup inheritance line in auto-config files...
MinRK -
Show More
@@ -1,327 +1,328 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 @classmethod
141 @classmethod
142 def class_get_help(cls):
142 def class_get_help(cls):
143 """Get the help string for this class in ReST format."""
143 """Get the help string for this class in ReST format."""
144 cls_traits = cls.class_traits(config=True)
144 cls_traits = cls.class_traits(config=True)
145 final_help = []
145 final_help = []
146 final_help.append(u'%s options' % cls.__name__)
146 final_help.append(u'%s options' % cls.__name__)
147 final_help.append(len(final_help[0])*u'-')
147 final_help.append(len(final_help[0])*u'-')
148 for k,v in cls.class_traits(config=True).iteritems():
148 for k,v in cls.class_traits(config=True).iteritems():
149 help = cls.class_get_trait_help(v)
149 help = cls.class_get_trait_help(v)
150 final_help.append(help)
150 final_help.append(help)
151 return '\n'.join(final_help)
151 return '\n'.join(final_help)
152
152
153 @classmethod
153 @classmethod
154 def class_get_trait_help(cls, trait):
154 def class_get_trait_help(cls, trait):
155 """Get the help string for a single trait."""
155 """Get the help string for a single trait."""
156 lines = []
156 lines = []
157 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
157 header = "--%s.%s=<%s>" % (cls.__name__, trait.name, trait.__class__.__name__)
158 lines.append(header)
158 lines.append(header)
159 try:
159 try:
160 dvr = repr(trait.get_default_value())
160 dvr = repr(trait.get_default_value())
161 except Exception:
161 except Exception:
162 dvr = None # ignore defaults we can't construct
162 dvr = None # ignore defaults we can't construct
163 if dvr is not None:
163 if dvr is not None:
164 if len(dvr) > 64:
164 if len(dvr) > 64:
165 dvr = dvr[:61]+'...'
165 dvr = dvr[:61]+'...'
166 lines.append(indent('Default: %s'%dvr, 4))
166 lines.append(indent('Default: %s'%dvr, 4))
167 if 'Enum' in trait.__class__.__name__:
167 if 'Enum' in trait.__class__.__name__:
168 # include Enum choices
168 # include Enum choices
169 lines.append(indent('Choices: %r'%(trait.values,)))
169 lines.append(indent('Choices: %r'%(trait.values,)))
170
170
171 help = trait.get_metadata('help')
171 help = trait.get_metadata('help')
172 if help is not None:
172 if help is not None:
173 help = '\n'.join(wrap_paragraphs(help, 76))
173 help = '\n'.join(wrap_paragraphs(help, 76))
174 lines.append(indent(help, 4))
174 lines.append(indent(help, 4))
175 return '\n'.join(lines)
175 return '\n'.join(lines)
176
176
177 @classmethod
177 @classmethod
178 def class_print_help(cls):
178 def class_print_help(cls):
179 """Get the help string for a single trait and print it."""
179 """Get the help string for a single trait and print it."""
180 print cls.class_get_help()
180 print cls.class_get_help()
181
181
182 @classmethod
182 @classmethod
183 def class_config_section(cls):
183 def class_config_section(cls):
184 """Get the config class config section"""
184 """Get the config class config section"""
185 def c(s):
185 def c(s):
186 """return a commented, wrapped block."""
186 """return a commented, wrapped block."""
187 s = '\n\n'.join(wrap_paragraphs(s, 78))
187 s = '\n\n'.join(wrap_paragraphs(s, 78))
188
188
189 return '# ' + s.replace('\n', '\n# ')
189 return '# ' + s.replace('\n', '\n# ')
190
190
191 # section header
191 # section header
192 breaker = '#' + '-'*78
192 breaker = '#' + '-'*78
193 s = "# %s configuration"%cls.__name__
193 s = "# %s configuration"%cls.__name__
194 lines = [breaker, s, breaker, '']
194 lines = [breaker, s, breaker, '']
195 # get the description trait
195 # get the description trait
196 desc = cls.class_traits().get('description')
196 desc = cls.class_traits().get('description')
197 if desc:
197 if desc:
198 desc = desc.default_value
198 desc = desc.default_value
199 else:
199 else:
200 # no description trait, use __doc__
200 # no description trait, use __doc__
201 desc = getattr(cls, '__doc__', '')
201 desc = getattr(cls, '__doc__', '')
202 if desc:
202 if desc:
203 lines.append(c(desc))
203 lines.append(c(desc))
204 lines.append('')
204 lines.append('')
205
205
206 parents = []
206 parents = []
207 for parent in cls.mro():
207 for parent in cls.mro():
208 # only include parents that are not base classes
208 # only include parents that are not base classes
209 # and are not the class itself
209 # and are not the class itself
210 if issubclass(parent, Configurable) and \
210 # and have some configurable traits to inherit
211 not parent in (Configurable, SingletonConfigurable, cls):
211 if parent is not cls and issubclass(parent, Configurable) and \
212 parent.class_traits(config=True):
212 parents.append(parent)
213 parents.append(parent)
213
214
214 if parents:
215 if parents:
215 pstr = ', '.join([ p.__name__ for p in parents ])
216 pstr = ', '.join([ p.__name__ for p in parents ])
216 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
217 lines.append(c('%s will inherit config from: %s'%(cls.__name__, pstr)))
217 lines.append('')
218 lines.append('')
218
219
219 for name,trait in cls.class_traits(config=True).iteritems():
220 for name,trait in cls.class_traits(config=True).iteritems():
220 help = trait.get_metadata('help') or ''
221 help = trait.get_metadata('help') or ''
221 lines.append(c(help))
222 lines.append(c(help))
222 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
223 lines.append('# c.%s.%s = %r'%(cls.__name__, name, trait.get_default_value()))
223 lines.append('')
224 lines.append('')
224 return '\n'.join(lines)
225 return '\n'.join(lines)
225
226
226
227
227
228
228 class SingletonConfigurable(Configurable):
229 class SingletonConfigurable(Configurable):
229 """A configurable that only allows one instance.
230 """A configurable that only allows one instance.
230
231
231 This class is for classes that should only have one instance of itself
232 This class is for classes that should only have one instance of itself
232 or *any* subclass. To create and retrieve such a class use the
233 or *any* subclass. To create and retrieve such a class use the
233 :meth:`SingletonConfigurable.instance` method.
234 :meth:`SingletonConfigurable.instance` method.
234 """
235 """
235
236
236 _instance = None
237 _instance = None
237
238
238 @classmethod
239 @classmethod
239 def _walk_mro(cls):
240 def _walk_mro(cls):
240 """Walk the cls.mro() for parent classes that are also singletons
241 """Walk the cls.mro() for parent classes that are also singletons
241
242
242 For use in instance()
243 For use in instance()
243 """
244 """
244
245
245 for subclass in cls.mro():
246 for subclass in cls.mro():
246 if issubclass(cls, subclass) and \
247 if issubclass(cls, subclass) and \
247 issubclass(subclass, SingletonConfigurable) and \
248 issubclass(subclass, SingletonConfigurable) and \
248 subclass != SingletonConfigurable:
249 subclass != SingletonConfigurable:
249 yield subclass
250 yield subclass
250
251
251 @classmethod
252 @classmethod
252 def clear_instance(cls):
253 def clear_instance(cls):
253 """unset _instance for this class and singleton parents.
254 """unset _instance for this class and singleton parents.
254 """
255 """
255 if not cls.initialized():
256 if not cls.initialized():
256 return
257 return
257 for subclass in cls._walk_mro():
258 for subclass in cls._walk_mro():
258 if isinstance(subclass._instance, cls):
259 if isinstance(subclass._instance, cls):
259 # only clear instances that are instances
260 # only clear instances that are instances
260 # of the calling class
261 # of the calling class
261 subclass._instance = None
262 subclass._instance = None
262
263
263 @classmethod
264 @classmethod
264 def instance(cls, *args, **kwargs):
265 def instance(cls, *args, **kwargs):
265 """Returns a global instance of this class.
266 """Returns a global instance of this class.
266
267
267 This method create a new instance if none have previously been created
268 This method create a new instance if none have previously been created
268 and returns a previously created instance is one already exists.
269 and returns a previously created instance is one already exists.
269
270
270 The arguments and keyword arguments passed to this method are passed
271 The arguments and keyword arguments passed to this method are passed
271 on to the :meth:`__init__` method of the class upon instantiation.
272 on to the :meth:`__init__` method of the class upon instantiation.
272
273
273 Examples
274 Examples
274 --------
275 --------
275
276
276 Create a singleton class using instance, and retrieve it::
277 Create a singleton class using instance, and retrieve it::
277
278
278 >>> from IPython.config.configurable import SingletonConfigurable
279 >>> from IPython.config.configurable import SingletonConfigurable
279 >>> class Foo(SingletonConfigurable): pass
280 >>> class Foo(SingletonConfigurable): pass
280 >>> foo = Foo.instance()
281 >>> foo = Foo.instance()
281 >>> foo == Foo.instance()
282 >>> foo == Foo.instance()
282 True
283 True
283
284
284 Create a subclass that is retrived using the base class instance::
285 Create a subclass that is retrived using the base class instance::
285
286
286 >>> class Bar(SingletonConfigurable): pass
287 >>> class Bar(SingletonConfigurable): pass
287 >>> class Bam(Bar): pass
288 >>> class Bam(Bar): pass
288 >>> bam = Bam.instance()
289 >>> bam = Bam.instance()
289 >>> bam == Bar.instance()
290 >>> bam == Bar.instance()
290 True
291 True
291 """
292 """
292 # Create and save the instance
293 # Create and save the instance
293 if cls._instance is None:
294 if cls._instance is None:
294 inst = cls(*args, **kwargs)
295 inst = cls(*args, **kwargs)
295 # Now make sure that the instance will also be returned by
296 # Now make sure that the instance will also be returned by
296 # parent classes' _instance attribute.
297 # parent classes' _instance attribute.
297 for subclass in cls._walk_mro():
298 for subclass in cls._walk_mro():
298 subclass._instance = inst
299 subclass._instance = inst
299
300
300 if isinstance(cls._instance, cls):
301 if isinstance(cls._instance, cls):
301 return cls._instance
302 return cls._instance
302 else:
303 else:
303 raise MultipleInstanceError(
304 raise MultipleInstanceError(
304 'Multiple incompatible subclass instances of '
305 'Multiple incompatible subclass instances of '
305 '%s are being created.' % cls.__name__
306 '%s are being created.' % cls.__name__
306 )
307 )
307
308
308 @classmethod
309 @classmethod
309 def initialized(cls):
310 def initialized(cls):
310 """Has an instance been created?"""
311 """Has an instance been created?"""
311 return hasattr(cls, "_instance") and cls._instance is not None
312 return hasattr(cls, "_instance") and cls._instance is not None
312
313
313
314
314 class LoggingConfigurable(Configurable):
315 class LoggingConfigurable(Configurable):
315 """A parent class for Configurables that log.
316 """A parent class for Configurables that log.
316
317
317 Subclasses have a log trait, and the default behavior
318 Subclasses have a log trait, and the default behavior
318 is to get the logger from the currently running Application
319 is to get the logger from the currently running Application
319 via Application.instance().log.
320 via Application.instance().log.
320 """
321 """
321
322
322 log = Instance('logging.Logger')
323 log = Instance('logging.Logger')
323 def _log_default(self):
324 def _log_default(self):
324 from IPython.config.application import Application
325 from IPython.config.application import Application
325 return Application.instance().log
326 return Application.instance().log
326
327
327
328
General Comments 0
You need to be logged in to leave comments. Login now