##// END OF EJS Templates
include default value in help output...
MinRK -
Show More
@@ -1,241 +1,286 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 A base class for a configurable application.
3 A base class for a configurable application.
4
4
5 Authors:
5 Authors:
6
6
7 * Brian Granger
7 * Brian Granger
8 """
8 """
9
9
10 #-----------------------------------------------------------------------------
10 #-----------------------------------------------------------------------------
11 # Copyright (C) 2008-2011 The IPython Development Team
11 # Copyright (C) 2008-2011 The IPython Development Team
12 #
12 #
13 # Distributed under the terms of the BSD License. The full license is in
13 # Distributed under the terms of the BSD License. The full license is in
14 # the file COPYING, distributed as part of this software.
14 # the file COPYING, distributed as part of this software.
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16
16
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18 # Imports
18 # Imports
19 #-----------------------------------------------------------------------------
19 #-----------------------------------------------------------------------------
20
20
21 from copy import deepcopy
21 from copy import deepcopy
22 import logging
22 import logging
23 import sys
23 import sys
24
24
25 from IPython.config.configurable import SingletonConfigurable
25 from IPython.config.configurable import SingletonConfigurable
26 from IPython.config.loader import (
26 from IPython.config.loader import (
27 KeyValueConfigLoader, PyFileConfigLoader, Config
27 KeyValueConfigLoader, PyFileConfigLoader, Config
28 )
28 )
29
29
30 from IPython.utils.traitlets import (
30 from IPython.utils.traitlets import (
31 Unicode, List, Int, Enum, Dict
31 Unicode, List, Int, Enum, Dict
32 )
32 )
33 from IPython.utils.text import indent
33 from IPython.utils.text import indent
34
34
35 #-----------------------------------------------------------------------------
35 #-----------------------------------------------------------------------------
36 # Descriptions for
36 # Descriptions for the various sections
37 #-----------------------------------------------------------------------------
37 #-----------------------------------------------------------------------------
38
38
39 flag_description = """
39 flag_description = """
40 Flags are command-line arguments passed as '--<flag>'.
40 Flags are command-line arguments passed as '--<flag>'.
41 These take no parameters, unlike regular key-value arguments.
41 These take no parameters, unlike regular key-value arguments.
42 They are typically used for setting boolean flags, or enabling
42 They are typically used for setting boolean flags, or enabling
43 modes that involve setting multiple options together.
43 modes that involve setting multiple options together.
44 """.strip() # trim newlines of front and back
44 """.strip() # trim newlines of front and back
45
45
46 alias_description = """
46 alias_description = """
47 These are commonly set parameters, given abbreviated aliases for convenience.
47 These are commonly set parameters, given abbreviated aliases for convenience.
48 They are set in the same `name=value` way as class parameters, where
48 They are set in the same `name=value` way as class parameters, where
49 <name> is replaced by the real parameter for which it is an alias.
49 <name> is replaced by the real parameter for which it is an alias.
50 """.strip() # trim newlines of front and back
50 """.strip() # trim newlines of front and back
51
51
52 keyvalue_description = """
52 keyvalue_description = """
53 Parameters are set from command-line arguments of the form:
53 Parameters are set from command-line arguments of the form:
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
54 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
55 This line is evaluated in Python, so simple expressions are allowed, e.g.
56 `C.a='range(3)'` For setting C.a=[0,1,2]
56 `C.a='range(3)'` For setting C.a=[0,1,2]
57 """.strip() # trim newlines of front and back
57 """.strip() # trim newlines of front and back
58
58
59 #-----------------------------------------------------------------------------
59 #-----------------------------------------------------------------------------
60 # Application class
60 # Application class
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62
62
63
63
64 class ApplicationError(Exception):
64 class ApplicationError(Exception):
65 pass
65 pass
66
66
67
67
68 class Application(SingletonConfigurable):
68 class Application(SingletonConfigurable):
69 """A singleton application with full configuration support."""
69 """A singleton application with full configuration support."""
70
70
71 # The name of the application, will usually match the name of the command
71 # The name of the application, will usually match the name of the command
72 # line application
72 # line application
73 name = Unicode(u'application')
73 name = Unicode(u'application')
74
74
75 # The description of the application that is printed at the beginning
75 # The description of the application that is printed at the beginning
76 # of the help.
76 # of the help.
77 description = Unicode(u'This is an application.')
77 description = Unicode(u'This is an application.')
78 # default section descriptions
78 # default section descriptions
79 flag_description = Unicode(flag_description)
79 flag_description = Unicode(flag_description)
80 alias_description = Unicode(alias_description)
80 alias_description = Unicode(alias_description)
81 keyvalue_description = Unicode(keyvalue_description)
81 keyvalue_description = Unicode(keyvalue_description)
82
82
83
83
84 # A sequence of Configurable subclasses whose config=True attributes will
84 # A sequence of Configurable subclasses whose config=True attributes will
85 # be exposed at the command line.
85 # be exposed at the command line.
86 classes = List([])
86 classes = List([])
87
87
88 # The version string of this application.
88 # The version string of this application.
89 version = Unicode(u'0.0')
89 version = Unicode(u'0.0')
90
90
91 # The log level for the application
91 # The log level for the application
92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
92 log_level = Enum((0,10,20,30,40,50), default_value=logging.WARN,
93 config=True,
93 config=True,
94 help="Set the log level (0,10,20,30,40,50).")
94 help="Set the log level.")
95
95
96 # the alias map for configurables
96 # the alias map for configurables
97 aliases = Dict(dict(log_level='Application.log_level'))
97 aliases = Dict(dict(log_level='Application.log_level'))
98
98
99 # flags for loading Configurables or store_const style flags
99 # flags for loading Configurables or store_const style flags
100 # flags are loaded from this dict by '--key' flags
100 # flags are loaded from this dict by '--key' flags
101 # this must be a dict of two-tuples, the first element being the Config/dict
101 # this must be a dict of two-tuples, the first element being the Config/dict
102 # and the second being the help string for the flag
102 # and the second being the help string for the flag
103 flags = Dict()
103 flags = Dict()
104
104
105
105
106 def __init__(self, **kwargs):
106 def __init__(self, **kwargs):
107 SingletonConfigurable.__init__(self, **kwargs)
107 SingletonConfigurable.__init__(self, **kwargs)
108 # Add my class to self.classes so my attributes appear in command line
108 # Add my class to self.classes so my attributes appear in command line
109 # options.
109 # options.
110 self.classes.insert(0, self.__class__)
110 self.classes.insert(0, self.__class__)
111
111
112 # ensure self.flags dict is valid
112 # ensure self.flags dict is valid
113 for key,value in self.flags.iteritems():
113 for key,value in self.flags.iteritems():
114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
114 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
115 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
116 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
117 self.init_logging()
117 self.init_logging()
118
118
119 def _config_changed(self, name, old, new):
119 def _config_changed(self, name, old, new):
120 SingletonConfigurable._config_changed(self, name, old, new)
120 SingletonConfigurable._config_changed(self, name, old, new)
121 self.log.debug('Config changed:')
121 self.log.debug('Config changed:')
122 self.log.debug(repr(new))
122 self.log.debug(repr(new))
123
123
124 def init_logging(self):
124 def init_logging(self):
125 """Start logging for this application.
125 """Start logging for this application.
126
126
127 The default is to log to stdout using a StreaHandler. The log level
127 The default is to log to stdout using a StreaHandler. The log level
128 starts at loggin.WARN, but this can be adjusted by setting the
128 starts at loggin.WARN, but this can be adjusted by setting the
129 ``log_level`` attribute.
129 ``log_level`` attribute.
130 """
130 """
131 self.log = logging.getLogger(self.__class__.__name__)
131 self.log = logging.getLogger(self.__class__.__name__)
132 self.log.setLevel(self.log_level)
132 self.log.setLevel(self.log_level)
133 self._log_handler = logging.StreamHandler()
133 self._log_handler = logging.StreamHandler()
134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
134 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
135 self._log_handler.setFormatter(self._log_formatter)
135 self._log_handler.setFormatter(self._log_formatter)
136 self.log.addHandler(self._log_handler)
136 self.log.addHandler(self._log_handler)
137
137
138 def _log_level_changed(self, name, old, new):
138 def _log_level_changed(self, name, old, new):
139 """Adjust the log level when log_level is set."""
139 """Adjust the log level when log_level is set."""
140 self.log.setLevel(new)
140 self.log.setLevel(new)
141
141
142 def print_alias_help(self):
142 def print_alias_help(self):
143 """print the alias part of the help"""
143 """print the alias part of the help"""
144 if not self.aliases:
144 if not self.aliases:
145 return
145 return
146
146
147 print "Aliases"
147 lines = ['Aliases']
148 print "-------"
148 lines.append('_'*len(lines[0]))
149 print self.alias_description
149 lines.append(self.alias_description)
150 print
150 lines.append('')
151
151
152 classdict = {}
152 classdict = {}
153 for c in self.classes:
153 for c in self.classes:
154 classdict[c.__name__] = c
154 classdict[c.__name__] = c
155
155
156 for alias, longname in self.aliases.iteritems():
156 for alias, longname in self.aliases.iteritems():
157 classname, traitname = longname.split('.',1)
157 classname, traitname = longname.split('.',1)
158 cls = classdict[classname]
158 cls = classdict[classname]
159
159
160 trait = cls.class_traits(config=True)[traitname]
160 trait = cls.class_traits(config=True)[traitname]
161 help = trait.get_metadata('help')
161 help = cls.class_get_trait_help(trait)
162 print alias, "(%s)"%longname, ':', trait.__class__.__name__
162 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
163 if help:
163 lines.append(help)
164 print indent(help, flatten=True)
164 # header = "%s (%s) : %s"%(alias, longname, trait.__class__.__name__)
165 print
165 # lines.append(header)
166 # help = cls.class_get_trait_help(trait)
167 # if help:
168 # lines.append(indent(help, flatten=True))
169 lines.append('')
170 print '\n'.join(lines)
166
171
167 def print_flag_help(self):
172 def print_flag_help(self):
168 """print the flag part of the help"""
173 """print the flag part of the help"""
169 if not self.flags:
174 if not self.flags:
170 return
175 return
171
176
172 print "Flags"
177 lines = ['Flags']
173 print "-----"
178 lines.append('_'*len(lines[0]))
174 print self.flag_description
179 lines.append(self.flag_description)
175 print
180 lines.append('')
176
181
177 for m, (cfg,help) in self.flags.iteritems():
182 for m, (cfg,help) in self.flags.iteritems():
178 print '--'+m
183 lines.append('--'+m)
179 print indent(help, flatten=True)
184 lines.append(indent(help, flatten=True))
180 print
185 lines.append('')
186 print '\n'.join(lines)
181
187
182 def print_help(self):
188 def print_help(self):
183 """Print the help for each Configurable class in self.classes."""
189 """Print the help for each Configurable class in self.classes."""
184 self.print_flag_help()
190 self.print_flag_help()
185 self.print_alias_help()
191 self.print_alias_help()
192
186 if self.classes:
193 if self.classes:
187 print "Class parameters"
194 print "Class parameters"
188 print "----------------"
195 print "----------------"
189 print self.keyvalue_description
196 print self.keyvalue_description
190 print
197 print
191
198
192 for cls in self.classes:
199 for cls in self.classes:
193 cls.class_print_help()
200 cls.class_print_help()
194 print
201 print
195
202
196 def print_description(self):
203 def print_description(self):
197 """Print the application description."""
204 """Print the application description."""
198 print self.description
205 print self.description
199 print
206 print
200
207
201 def print_version(self):
208 def print_version(self):
202 """Print the version string."""
209 """Print the version string."""
203 print self.version
210 print self.version
204
211
205 def update_config(self, config):
212 def update_config(self, config):
206 """Fire the traits events when the config is updated."""
213 """Fire the traits events when the config is updated."""
207 # Save a copy of the current config.
214 # Save a copy of the current config.
208 newconfig = deepcopy(self.config)
215 newconfig = deepcopy(self.config)
209 # Merge the new config into the current one.
216 # Merge the new config into the current one.
210 newconfig._merge(config)
217 newconfig._merge(config)
211 # Save the combined config as self.config, which triggers the traits
218 # Save the combined config as self.config, which triggers the traits
212 # events.
219 # events.
213 self.config = newconfig
220 self.config = newconfig
214
221
215 def parse_command_line(self, argv=None):
222 def parse_command_line(self, argv=None):
216 """Parse the command line arguments."""
223 """Parse the command line arguments."""
217 argv = sys.argv[1:] if argv is None else argv
224 argv = sys.argv[1:] if argv is None else argv
218
225
219 if '-h' in argv or '--help' in argv:
226 if '-h' in argv or '--help' in argv:
220 self.print_description()
227 self.print_description()
221 self.print_help()
228 self.print_help()
222 self.exit(1)
229 self.exit(1)
223
230
224 if '--version' in argv:
231 if '--version' in argv:
225 self.print_version()
232 self.print_version()
226 self.exit(1)
233 self.exit(1)
227
234
228 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
235 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
229 flags=self.flags)
236 flags=self.flags)
230 config = loader.load_config()
237 config = loader.load_config()
231 self.update_config(config)
238 self.update_config(config)
232
239
233 def load_config_file(self, filename, path=None):
240 def load_config_file(self, filename, path=None):
234 """Load a .py based config file by filename and path."""
241 """Load a .py based config file by filename and path."""
235 loader = PyFileConfigLoader(filename, path=path)
242 loader = PyFileConfigLoader(filename, path=path)
236 config = loader.load_config()
243 config = loader.load_config()
237 self.update_config(config)
244 self.update_config(config)
238
245
239 def exit(self, exit_status=0):
246 def exit(self, exit_status=0):
240 self.log.debug("Exiting application: %s" % self.name)
247 self.log.debug("Exiting application: %s" % self.name)
241 sys.exit(exit_status)
248 sys.exit(exit_status)
249
250 #-----------------------------------------------------------------------------
251 # utility functions, for convenience
252 #-----------------------------------------------------------------------------
253
254 def boolean_flag(name, configurable, set_help='', unset_help=''):
255 """helper for building basic --trait, --no-trait flags
256
257 Parameters
258 ----------
259
260 name : str
261 The name of the flag.
262 configurable : str
263 The 'Class.trait' string of the trait to be set/unset with the flag
264 set_help : unicode
265 help string for --name flag
266 unset_help : unicode
267 help string for --no-name flag
268
269 Returns
270 -------
271
272 cfg : dict
273 A dict with two keys: 'name', and 'no-name', for setting and unsetting
274 the trait, respectively.
275 """
276 # default helpstrings
277 set_help = set_help or "set %s=True"%configurable
278 unset_help = unset_help or "set %s=False"%configurable
279
280 cls,trait = configurable.split('.')
281
282 setter = Config()
283 setter[cls][trait] = True
284 unsetter = Config()
285 unsetter[cls][trait] = False
286 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
@@ -1,224 +1,242 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_traits.items():
148 for k,v in cls.class_traits(config=True).iteritems():
149 help = v.get_metadata('help')
149 help = cls.class_get_trait_help(v)
150 header = "%s.%s : %s" % (cls.__name__, k, v.__class__.__name__)
150 final_help.append(help)
151 final_help.append(header)
152 if help is not None:
153 final_help.append(indent(help, flatten=True))
154 return '\n'.join(final_help)
151 return '\n'.join(final_help)
152
153 @classmethod
154 def class_get_trait_help(cls, trait):
155 """Get the help string for a single """
156 lines = []
157 header = "%s.%s : %s" % (cls.__name__, trait.name, trait.__class__.__name__)
158 try:
159 dvr = repr(trait.get_default_value())
160 except Exception:
161 dvr = None # ignore defaults we can't construct
162 if dvr is not None:
163 header += ' [default: %s]'%dvr
164 lines.append(header)
165
166 help = trait.get_metadata('help')
167 if help is not None:
168 lines.append(indent(help, flatten=True))
169 if 'Enum' in trait.__class__.__name__:
170 # include Enum choices
171 lines.append(indent('Choices: %r'%(trait.values,), flatten=True))
172 return '\n'.join(lines)
155
173
156 @classmethod
174 @classmethod
157 def class_print_help(cls):
175 def class_print_help(cls):
158 print cls.class_get_help()
176 print cls.class_get_help()
159
177
160
178
161 class SingletonConfigurable(Configurable):
179 class SingletonConfigurable(Configurable):
162 """A configurable that only allows one instance.
180 """A configurable that only allows one instance.
163
181
164 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
165 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
166 :meth:`SingletonConfigurable.instance` method.
184 :meth:`SingletonConfigurable.instance` method.
167 """
185 """
168
186
169 _instance = None
187 _instance = None
170
188
171 @classmethod
189 @classmethod
172 def instance(cls, *args, **kwargs):
190 def instance(cls, *args, **kwargs):
173 """Returns a global instance of this class.
191 """Returns a global instance of this class.
174
192
175 This method create a new instance if none have previously been created
193 This method create a new instance if none have previously been created
176 and returns a previously created instance is one already exists.
194 and returns a previously created instance is one already exists.
177
195
178 The arguments and keyword arguments passed to this method are passed
196 The arguments and keyword arguments passed to this method are passed
179 on to the :meth:`__init__` method of the class upon instantiation.
197 on to the :meth:`__init__` method of the class upon instantiation.
180
198
181 Examples
199 Examples
182 --------
200 --------
183
201
184 Create a singleton class using instance, and retrieve it::
202 Create a singleton class using instance, and retrieve it::
185
203
186 >>> from IPython.config.configurable import SingletonConfigurable
204 >>> from IPython.config.configurable import SingletonConfigurable
187 >>> class Foo(SingletonConfigurable): pass
205 >>> class Foo(SingletonConfigurable): pass
188 >>> foo = Foo.instance()
206 >>> foo = Foo.instance()
189 >>> foo == Foo.instance()
207 >>> foo == Foo.instance()
190 True
208 True
191
209
192 Create a subclass that is retrived using the base class instance::
210 Create a subclass that is retrived using the base class instance::
193
211
194 >>> class Bar(SingletonConfigurable): pass
212 >>> class Bar(SingletonConfigurable): pass
195 >>> class Bam(Bar): pass
213 >>> class Bam(Bar): pass
196 >>> bam = Bam.instance()
214 >>> bam = Bam.instance()
197 >>> bam == Bar.instance()
215 >>> bam == Bar.instance()
198 True
216 True
199 """
217 """
200 # Create and save the instance
218 # Create and save the instance
201 if cls._instance is None:
219 if cls._instance is None:
202 inst = cls(*args, **kwargs)
220 inst = cls(*args, **kwargs)
203 # Now make sure that the instance will also be returned by
221 # Now make sure that the instance will also be returned by
204 # the subclasses instance attribute.
222 # the subclasses instance attribute.
205 for subclass in cls.mro():
223 for subclass in cls.mro():
206 if issubclass(cls, subclass) and \
224 if issubclass(cls, subclass) and \
207 issubclass(subclass, SingletonConfigurable) and \
225 issubclass(subclass, SingletonConfigurable) and \
208 subclass != SingletonConfigurable:
226 subclass != SingletonConfigurable:
209 subclass._instance = inst
227 subclass._instance = inst
210 else:
228 else:
211 break
229 break
212 if isinstance(cls._instance, cls):
230 if isinstance(cls._instance, cls):
213 return cls._instance
231 return cls._instance
214 else:
232 else:
215 raise MultipleInstanceError(
233 raise MultipleInstanceError(
216 'Multiple incompatible subclass instances of '
234 'Multiple incompatible subclass instances of '
217 '%s are being created.' % cls.__name__
235 '%s are being created.' % cls.__name__
218 )
236 )
219
237
220 @classmethod
238 @classmethod
221 def initialized(cls):
239 def initialized(cls):
222 """Has an instance been created?"""
240 """Has an instance been created?"""
223 return hasattr(cls, "_instance") and cls._instance is not None
241 return hasattr(cls, "_instance") and cls._instance is not None
224
242
General Comments 0
You need to be logged in to leave comments. Login now