##// END OF EJS Templates
add clear_instance() to SingletonConfigurable...
MinRK -
Show More
@@ -1,242 +1,263 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 # encoding: utf-8
2 # encoding: utf-8
3 """
3 """
4 A base class for objects that are configurable.
4 A base class for objects that are configurable.
5
5
6 Authors:
6 Authors:
7
7
8 * Brian Granger
8 * Brian Granger
9 * Fernando Perez
9 * Fernando Perez
10 """
10 """
11
11
12 #-----------------------------------------------------------------------------
12 #-----------------------------------------------------------------------------
13 # Copyright (C) 2008-2010 The IPython Development Team
13 # Copyright (C) 2008-2010 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 from copy import deepcopy
23 from copy import deepcopy
24 import datetime
24 import datetime
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
28 from IPython.utils.text import indent
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 conigurable given a config config.
53 """Create a conigurable 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 """
155 """Get the help string for a single """
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 try:
158 try:
159 dvr = repr(trait.get_default_value())
159 dvr = repr(trait.get_default_value())
160 except Exception:
160 except Exception:
161 dvr = None # ignore defaults we can't construct
161 dvr = None # ignore defaults we can't construct
162 if dvr is not None:
162 if dvr is not None:
163 header += ' [default: %s]'%dvr
163 header += ' [default: %s]'%dvr
164 lines.append(header)
164 lines.append(header)
165
165
166 help = trait.get_metadata('help')
166 help = trait.get_metadata('help')
167 if help is not None:
167 if help is not None:
168 lines.append(indent(help.strip(), flatten=True))
168 lines.append(indent(help.strip(), flatten=True))
169 if 'Enum' in trait.__class__.__name__:
169 if 'Enum' in trait.__class__.__name__:
170 # include Enum choices
170 # include Enum choices
171 lines.append(indent('Choices: %r'%(trait.values,), flatten=True))
171 lines.append(indent('Choices: %r'%(trait.values,), flatten=True))
172 return '\n'.join(lines)
172 return '\n'.join(lines)
173
173
174 @classmethod
174 @classmethod
175 def class_print_help(cls):
175 def class_print_help(cls):
176 print cls.class_get_help()
176 print cls.class_get_help()
177
177
178
178
179 class SingletonConfigurable(Configurable):
179 class SingletonConfigurable(Configurable):
180 """A configurable that only allows one instance.
180 """A configurable that only allows one instance.
181
181
182 This class is for classes that should only have one instance of itself
182 This class is for classes that should only have one instance of itself
183 or *any* subclass. To create and retrieve such a class use the
183 or *any* subclass. To create and retrieve such a class use the
184 :meth:`SingletonConfigurable.instance` method.
184 :meth:`SingletonConfigurable.instance` method.
185 """
185 """
186
186
187 _instance = None
187 _instance = None
188
188
189 @classmethod
190 def _walk_mro(cls):
191 """Walk the cls.mro() for parent classes that are also singletons
192
193 For use in instance()
194 """
195
196 for subclass in cls.mro():
197 if issubclass(cls, subclass) and \
198 issubclass(subclass, SingletonConfigurable) and \
199 subclass != SingletonConfigurable:
200 yield subclass
201
202 @classmethod
203 def clear_instance(cls):
204 """unset _instance for this class and singleton parents.
205 """
206 if not cls.initialized():
207 return
208 for subclass in cls._walk_mro():
209 if isinstance(subclass._instance, cls):
210 # only clear instances that are instances
211 # of the calling class
212 subclass._instance = None
213
189 @classmethod
214 @classmethod
190 def instance(cls, *args, **kwargs):
215 def instance(cls, *args, **kwargs):
191 """Returns a global instance of this class.
216 """Returns a global instance of this class.
192
217
193 This method create a new instance if none have previously been created
218 This method create a new instance if none have previously been created
194 and returns a previously created instance is one already exists.
219 and returns a previously created instance is one already exists.
195
220
196 The arguments and keyword arguments passed to this method are passed
221 The arguments and keyword arguments passed to this method are passed
197 on to the :meth:`__init__` method of the class upon instantiation.
222 on to the :meth:`__init__` method of the class upon instantiation.
198
223
199 Examples
224 Examples
200 --------
225 --------
201
226
202 Create a singleton class using instance, and retrieve it::
227 Create a singleton class using instance, and retrieve it::
203
228
204 >>> from IPython.config.configurable import SingletonConfigurable
229 >>> from IPython.config.configurable import SingletonConfigurable
205 >>> class Foo(SingletonConfigurable): pass
230 >>> class Foo(SingletonConfigurable): pass
206 >>> foo = Foo.instance()
231 >>> foo = Foo.instance()
207 >>> foo == Foo.instance()
232 >>> foo == Foo.instance()
208 True
233 True
209
234
210 Create a subclass that is retrived using the base class instance::
235 Create a subclass that is retrived using the base class instance::
211
236
212 >>> class Bar(SingletonConfigurable): pass
237 >>> class Bar(SingletonConfigurable): pass
213 >>> class Bam(Bar): pass
238 >>> class Bam(Bar): pass
214 >>> bam = Bam.instance()
239 >>> bam = Bam.instance()
215 >>> bam == Bar.instance()
240 >>> bam == Bar.instance()
216 True
241 True
217 """
242 """
218 # Create and save the instance
243 # Create and save the instance
219 if cls._instance is None:
244 if cls._instance is None:
220 inst = cls(*args, **kwargs)
245 inst = cls(*args, **kwargs)
221 # Now make sure that the instance will also be returned by
246 # Now make sure that the instance will also be returned by
222 # the subclasses instance attribute.
247 # parent classes' _instance attribute.
223 for subclass in cls.mro():
248 for subclass in cls._walk_mro():
224 if issubclass(cls, subclass) and \
249 subclass._instance = inst
225 issubclass(subclass, SingletonConfigurable) and \
250
226 subclass != SingletonConfigurable:
227 subclass._instance = inst
228 else:
229 break
230 if isinstance(cls._instance, cls):
251 if isinstance(cls._instance, cls):
231 return cls._instance
252 return cls._instance
232 else:
253 else:
233 raise MultipleInstanceError(
254 raise MultipleInstanceError(
234 'Multiple incompatible subclass instances of '
255 'Multiple incompatible subclass instances of '
235 '%s are being created.' % cls.__name__
256 '%s are being created.' % cls.__name__
236 )
257 )
237
258
238 @classmethod
259 @classmethod
239 def initialized(cls):
260 def initialized(cls):
240 """Has an instance been created?"""
261 """Has an instance been created?"""
241 return hasattr(cls, "_instance") and cls._instance is not None
262 return hasattr(cls, "_instance") and cls._instance is not None
242
263
General Comments 0
You need to be logged in to leave comments. Login now