##// END OF EJS Templates
add clear_instance() to SingletonConfigurable...
MinRK -
Show More
@@ -1,242 +1,263 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 A base class for objects that are configurable.
5 5
6 6 Authors:
7 7
8 8 * Brian Granger
9 9 * Fernando Perez
10 10 """
11 11
12 12 #-----------------------------------------------------------------------------
13 13 # Copyright (C) 2008-2010 The IPython Development Team
14 14 #
15 15 # Distributed under the terms of the BSD License. The full license is in
16 16 # the file COPYING, distributed as part of this software.
17 17 #-----------------------------------------------------------------------------
18 18
19 19 #-----------------------------------------------------------------------------
20 20 # Imports
21 21 #-----------------------------------------------------------------------------
22 22
23 23 from copy import deepcopy
24 24 import datetime
25 25
26 26 from loader import Config
27 27 from IPython.utils.traitlets import HasTraits, Instance
28 28 from IPython.utils.text import indent
29 29
30 30
31 31 #-----------------------------------------------------------------------------
32 32 # Helper classes for Configurables
33 33 #-----------------------------------------------------------------------------
34 34
35 35
36 36 class ConfigurableError(Exception):
37 37 pass
38 38
39 39
40 40 class MultipleInstanceError(ConfigurableError):
41 41 pass
42 42
43 43 #-----------------------------------------------------------------------------
44 44 # Configurable implementation
45 45 #-----------------------------------------------------------------------------
46 46
47 47 class Configurable(HasTraits):
48 48
49 49 config = Instance(Config,(),{})
50 50 created = None
51 51
52 52 def __init__(self, **kwargs):
53 53 """Create a conigurable given a config config.
54 54
55 55 Parameters
56 56 ----------
57 57 config : Config
58 58 If this is empty, default values are used. If config is a
59 59 :class:`Config` instance, it will be used to configure the
60 60 instance.
61 61
62 62 Notes
63 63 -----
64 64 Subclasses of Configurable must call the :meth:`__init__` method of
65 65 :class:`Configurable` *before* doing anything else and using
66 66 :func:`super`::
67 67
68 68 class MyConfigurable(Configurable):
69 69 def __init__(self, config=None):
70 70 super(MyConfigurable, self).__init__(config)
71 71 # Then any other code you need to finish initialization.
72 72
73 73 This ensures that instances will be configured properly.
74 74 """
75 75 config = kwargs.pop('config', None)
76 76 if config is not None:
77 77 # We used to deepcopy, but for now we are trying to just save
78 78 # by reference. This *could* have side effects as all components
79 79 # will share config. In fact, I did find such a side effect in
80 80 # _config_changed below. If a config attribute value was a mutable type
81 81 # all instances of a component were getting the same copy, effectively
82 82 # making that a class attribute.
83 83 # self.config = deepcopy(config)
84 84 self.config = config
85 85 # This should go second so individual keyword arguments override
86 86 # the values in config.
87 87 super(Configurable, self).__init__(**kwargs)
88 88 self.created = datetime.datetime.now()
89 89
90 90 #-------------------------------------------------------------------------
91 91 # Static trait notifiations
92 92 #-------------------------------------------------------------------------
93 93
94 94 def _config_changed(self, name, old, new):
95 95 """Update all the class traits having ``config=True`` as metadata.
96 96
97 97 For any class trait with a ``config`` metadata attribute that is
98 98 ``True``, we update the trait with the value of the corresponding
99 99 config entry.
100 100 """
101 101 # Get all traits with a config metadata entry that is True
102 102 traits = self.traits(config=True)
103 103
104 104 # We auto-load config section for this class as well as any parent
105 105 # classes that are Configurable subclasses. This starts with Configurable
106 106 # and works down the mro loading the config for each section.
107 107 section_names = [cls.__name__ for cls in \
108 108 reversed(self.__class__.__mro__) if
109 109 issubclass(cls, Configurable) and issubclass(self.__class__, cls)]
110 110
111 111 for sname in section_names:
112 112 # Don't do a blind getattr as that would cause the config to
113 113 # dynamically create the section with name self.__class__.__name__.
114 114 if new._has_section(sname):
115 115 my_config = new[sname]
116 116 for k, v in traits.iteritems():
117 117 # Don't allow traitlets with config=True to start with
118 118 # uppercase. Otherwise, they are confused with Config
119 119 # subsections. But, developers shouldn't have uppercase
120 120 # attributes anyways! (PEP 6)
121 121 if k[0].upper()==k[0] and not k.startswith('_'):
122 122 raise ConfigurableError('Configurable traitlets with '
123 123 'config=True must start with a lowercase so they are '
124 124 'not confused with Config subsections: %s.%s' % \
125 125 (self.__class__.__name__, k))
126 126 try:
127 127 # Here we grab the value from the config
128 128 # If k has the naming convention of a config
129 129 # section, it will be auto created.
130 130 config_value = my_config[k]
131 131 except KeyError:
132 132 pass
133 133 else:
134 134 # print "Setting %s.%s from %s.%s=%r" % \
135 135 # (self.__class__.__name__,k,sname,k,config_value)
136 136 # We have to do a deepcopy here if we don't deepcopy the entire
137 137 # config object. If we don't, a mutable config_value will be
138 138 # shared by all instances, effectively making it a class attribute.
139 139 setattr(self, k, deepcopy(config_value))
140 140
141 141 @classmethod
142 142 def class_get_help(cls):
143 143 """Get the help string for this class in ReST format."""
144 144 cls_traits = cls.class_traits(config=True)
145 145 final_help = []
146 146 final_help.append(u'%s options' % cls.__name__)
147 147 final_help.append(len(final_help[0])*u'-')
148 148 for k,v in cls.class_traits(config=True).iteritems():
149 149 help = cls.class_get_trait_help(v)
150 150 final_help.append(help)
151 151 return '\n'.join(final_help)
152 152
153 153 @classmethod
154 154 def class_get_trait_help(cls, trait):
155 155 """Get the help string for a single """
156 156 lines = []
157 157 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
158 158 try:
159 159 dvr = repr(trait.get_default_value())
160 160 except Exception:
161 161 dvr = None # ignore defaults we can't construct
162 162 if dvr is not None:
163 163 header += ' [default: %s]'%dvr
164 164 lines.append(header)
165 165
166 166 help = trait.get_metadata('help')
167 167 if help is not None:
168 168 lines.append(indent(help.strip(), flatten=True))
169 169 if 'Enum' in trait.__class__.__name__:
170 170 # include Enum choices
171 171 lines.append(indent('Choices: %r'%(trait.values,), flatten=True))
172 172 return '\n'.join(lines)
173 173
174 174 @classmethod
175 175 def class_print_help(cls):
176 176 print cls.class_get_help()
177 177
178 178
179 179 class SingletonConfigurable(Configurable):
180 180 """A configurable that only allows one instance.
181 181
182 182 This class is for classes that should only have one instance of itself
183 183 or *any* subclass. To create and retrieve such a class use the
184 184 :meth:`SingletonConfigurable.instance` method.
185 185 """
186 186
187 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 214 @classmethod
190 215 def instance(cls, *args, **kwargs):
191 216 """Returns a global instance of this class.
192 217
193 218 This method create a new instance if none have previously been created
194 219 and returns a previously created instance is one already exists.
195 220
196 221 The arguments and keyword arguments passed to this method are passed
197 222 on to the :meth:`__init__` method of the class upon instantiation.
198 223
199 224 Examples
200 225 --------
201 226
202 227 Create a singleton class using instance, and retrieve it::
203 228
204 229 >>> from IPython.config.configurable import SingletonConfigurable
205 230 >>> class Foo(SingletonConfigurable): pass
206 231 >>> foo = Foo.instance()
207 232 >>> foo == Foo.instance()
208 233 True
209 234
210 235 Create a subclass that is retrived using the base class instance::
211 236
212 237 >>> class Bar(SingletonConfigurable): pass
213 238 >>> class Bam(Bar): pass
214 239 >>> bam = Bam.instance()
215 240 >>> bam == Bar.instance()
216 241 True
217 242 """
218 243 # Create and save the instance
219 244 if cls._instance is None:
220 245 inst = cls(*args, **kwargs)
221 246 # Now make sure that the instance will also be returned by
222 # the subclasses instance attribute.
223 for subclass in cls.mro():
224 if issubclass(cls, subclass) and \
225 issubclass(subclass, SingletonConfigurable) and \
226 subclass != SingletonConfigurable:
227 subclass._instance = inst
228 else:
229 break
247 # parent classes' _instance attribute.
248 for subclass in cls._walk_mro():
249 subclass._instance = inst
250
230 251 if isinstance(cls._instance, cls):
231 252 return cls._instance
232 253 else:
233 254 raise MultipleInstanceError(
234 255 'Multiple incompatible subclass instances of '
235 256 '%s are being created.' % cls.__name__
236 257 )
237 258
238 259 @classmethod
239 260 def initialized(cls):
240 261 """Has an instance been created?"""
241 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