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