##// END OF EJS Templates
fix setting log_level by name in Application...
MinRK -
Show More
@@ -1,394 +1,393 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 * Min RK
8 * Min RK
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2011 The IPython Development Team
12 # Copyright (C) 2008-2011 The IPython Development Team
13 #
13 #
14 # Distributed under the terms of the BSD License. The full license is in
14 # Distributed under the terms of the BSD License. The full license is in
15 # the file COPYING, distributed as part of this software.
15 # the file COPYING, distributed as part of this software.
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 #-----------------------------------------------------------------------------
18 #-----------------------------------------------------------------------------
19 # Imports
19 # Imports
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21
21
22 import logging
22 import logging
23 import os
23 import os
24 import re
24 import re
25 import sys
25 import sys
26 from copy import deepcopy
26 from copy import deepcopy
27
27
28 from IPython.config.configurable import SingletonConfigurable
28 from IPython.config.configurable import SingletonConfigurable
29 from IPython.config.loader import (
29 from IPython.config.loader import (
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
30 KeyValueConfigLoader, PyFileConfigLoader, Config, ArgumentError
31 )
31 )
32
32
33 from IPython.utils.traitlets import (
33 from IPython.utils.traitlets import (
34 Unicode, List, Int, Enum, Dict, Instance
34 Unicode, List, Int, Enum, Dict, Instance
35 )
35 )
36 from IPython.utils.importstring import import_item
36 from IPython.utils.importstring import import_item
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
37 from IPython.utils.text import indent, wrap_paragraphs, dedent
38
38
39 #-----------------------------------------------------------------------------
39 #-----------------------------------------------------------------------------
40 # function for re-wrapping a helpstring
40 # function for re-wrapping a helpstring
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Descriptions for the various sections
44 # Descriptions for the various sections
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 flag_description = """
47 flag_description = """
48 Flags are command-line arguments passed as '--<flag>'.
48 Flags are command-line arguments passed as '--<flag>'.
49 These take no parameters, unlike regular key-value arguments.
49 These take no parameters, unlike regular key-value arguments.
50 They are typically used for setting boolean flags, or enabling
50 They are typically used for setting boolean flags, or enabling
51 modes that involve setting multiple options together.
51 modes that involve setting multiple options together.
52
52
53 Flags *always* begin with '--', never just one '-'.
53 Flags *always* begin with '--', never just one '-'.
54 """.strip() # trim newlines of front and back
54 """.strip() # trim newlines of front and back
55
55
56 alias_description = """
56 alias_description = """
57 These are commonly set parameters, given abbreviated aliases for convenience.
57 These are commonly set parameters, given abbreviated aliases for convenience.
58 They are set in the same `name=value` way as class parameters, where
58 They are set in the same `name=value` way as class parameters, where
59 <name> is replaced by the real parameter for which it is an alias.
59 <name> is replaced by the real parameter for which it is an alias.
60 """.strip() # trim newlines of front and back
60 """.strip() # trim newlines of front and back
61
61
62 keyvalue_description = """
62 keyvalue_description = """
63 Parameters are set from command-line arguments of the form:
63 Parameters are set from command-line arguments of the form:
64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
64 `Class.trait=value`. Parameters will *never* be prefixed with '-'.
65 This line is evaluated in Python, so simple expressions are allowed, e.g.
65 This line is evaluated in Python, so simple expressions are allowed, e.g.
66 `C.a='range(3)'` For setting C.a=[0,1,2]
66 `C.a='range(3)'` For setting C.a=[0,1,2]
67 """.strip() # trim newlines of front and back
67 """.strip() # trim newlines of front and back
68
68
69 #-----------------------------------------------------------------------------
69 #-----------------------------------------------------------------------------
70 # Application class
70 # Application class
71 #-----------------------------------------------------------------------------
71 #-----------------------------------------------------------------------------
72
72
73
73
74 class ApplicationError(Exception):
74 class ApplicationError(Exception):
75 pass
75 pass
76
76
77
77
78 class Application(SingletonConfigurable):
78 class Application(SingletonConfigurable):
79 """A singleton application with full configuration support."""
79 """A singleton application with full configuration support."""
80
80
81 # The name of the application, will usually match the name of the command
81 # The name of the application, will usually match the name of the command
82 # line application
82 # line application
83 name = Unicode(u'application')
83 name = Unicode(u'application')
84
84
85 # The description of the application that is printed at the beginning
85 # The description of the application that is printed at the beginning
86 # of the help.
86 # of the help.
87 description = Unicode(u'This is an application.')
87 description = Unicode(u'This is an application.')
88 # default section descriptions
88 # default section descriptions
89 flag_description = Unicode(flag_description)
89 flag_description = Unicode(flag_description)
90 alias_description = Unicode(alias_description)
90 alias_description = Unicode(alias_description)
91 keyvalue_description = Unicode(keyvalue_description)
91 keyvalue_description = Unicode(keyvalue_description)
92
92
93
93
94 # A sequence of Configurable subclasses whose config=True attributes will
94 # A sequence of Configurable subclasses whose config=True attributes will
95 # be exposed at the command line.
95 # be exposed at the command line.
96 classes = List([])
96 classes = List([])
97
97
98 # The version string of this application.
98 # The version string of this application.
99 version = Unicode(u'0.0')
99 version = Unicode(u'0.0')
100
100
101 # The log level for the application
101 # The log level for the application
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
102 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
103 default_value=logging.WARN,
103 default_value=logging.WARN,
104 config=True,
104 config=True,
105 help="Set the log level by value or name.")
105 help="Set the log level by value or name.")
106 def _log_level_changed(self, name, old, new):
106 def _log_level_changed(self, name, old, new):
107 """Adjust the log level when log_level is set."""
107 if isinstance(new, basestring):
108 if isinstance(new, basestring):
108 self.log_level = getattr(logging, new)
109 new = getattr(logging, new)
110 self.log_level = new
111 self.log.setLevel(new)
109
112
110 # the alias map for configurables
113 # the alias map for configurables
111 aliases = Dict(dict(log_level='Application.log_level'))
114 aliases = Dict(dict(log_level='Application.log_level'))
112
115
113 # flags for loading Configurables or store_const style flags
116 # flags for loading Configurables or store_const style flags
114 # flags are loaded from this dict by '--key' flags
117 # flags are loaded from this dict by '--key' flags
115 # this must be a dict of two-tuples, the first element being the Config/dict
118 # this must be a dict of two-tuples, the first element being the Config/dict
116 # and the second being the help string for the flag
119 # and the second being the help string for the flag
117 flags = Dict()
120 flags = Dict()
118 def _flags_changed(self, name, old, new):
121 def _flags_changed(self, name, old, new):
119 """ensure flags dict is valid"""
122 """ensure flags dict is valid"""
120 for key,value in new.iteritems():
123 for key,value in new.iteritems():
121 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
124 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
122 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
125 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
123 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
126 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
124
127
125
128
126 # subcommands for launching other applications
129 # subcommands for launching other applications
127 # if this is not empty, this will be a parent Application
130 # if this is not empty, this will be a parent Application
128 # this must be a dict of two-tuples,
131 # this must be a dict of two-tuples,
129 # the first element being the application class/import string
132 # the first element being the application class/import string
130 # and the second being the help string for the subcommand
133 # and the second being the help string for the subcommand
131 subcommands = Dict()
134 subcommands = Dict()
132 # parse_command_line will initialize a subapp, if requested
135 # parse_command_line will initialize a subapp, if requested
133 subapp = Instance('IPython.config.application.Application', allow_none=True)
136 subapp = Instance('IPython.config.application.Application', allow_none=True)
134
137
135 # extra command-line arguments that don't set config values
138 # extra command-line arguments that don't set config values
136 extra_args = List(Unicode)
139 extra_args = List(Unicode)
137
140
138
141
139 def __init__(self, **kwargs):
142 def __init__(self, **kwargs):
140 SingletonConfigurable.__init__(self, **kwargs)
143 SingletonConfigurable.__init__(self, **kwargs)
141 # Add my class to self.classes so my attributes appear in command line
144 # Add my class to self.classes so my attributes appear in command line
142 # options.
145 # options.
143 self.classes.insert(0, self.__class__)
146 self.classes.insert(0, self.__class__)
144
147
145 self.init_logging()
148 self.init_logging()
146
149
147 def _config_changed(self, name, old, new):
150 def _config_changed(self, name, old, new):
148 SingletonConfigurable._config_changed(self, name, old, new)
151 SingletonConfigurable._config_changed(self, name, old, new)
149 self.log.debug('Config changed:')
152 self.log.debug('Config changed:')
150 self.log.debug(repr(new))
153 self.log.debug(repr(new))
151
154
152 def init_logging(self):
155 def init_logging(self):
153 """Start logging for this application.
156 """Start logging for this application.
154
157
155 The default is to log to stdout using a StreaHandler. The log level
158 The default is to log to stdout using a StreaHandler. The log level
156 starts at loggin.WARN, but this can be adjusted by setting the
159 starts at loggin.WARN, but this can be adjusted by setting the
157 ``log_level`` attribute.
160 ``log_level`` attribute.
158 """
161 """
159 self.log = logging.getLogger(self.__class__.__name__)
162 self.log = logging.getLogger(self.__class__.__name__)
160 self.log.setLevel(self.log_level)
163 self.log.setLevel(self.log_level)
161 self._log_handler = logging.StreamHandler()
164 self._log_handler = logging.StreamHandler()
162 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
165 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
163 self._log_handler.setFormatter(self._log_formatter)
166 self._log_handler.setFormatter(self._log_formatter)
164 self.log.addHandler(self._log_handler)
167 self.log.addHandler(self._log_handler)
165
168
166 def initialize(self, argv=None):
169 def initialize(self, argv=None):
167 """Do the basic steps to configure me.
170 """Do the basic steps to configure me.
168
171
169 Override in subclasses.
172 Override in subclasses.
170 """
173 """
171 self.parse_command_line(argv)
174 self.parse_command_line(argv)
172
175
173
176
174 def start(self):
177 def start(self):
175 """Start the app mainloop.
178 """Start the app mainloop.
176
179
177 Override in subclasses.
180 Override in subclasses.
178 """
181 """
179 if self.subapp is not None:
182 if self.subapp is not None:
180 return self.subapp.start()
183 return self.subapp.start()
181
184
182 def _log_level_changed(self, name, old, new):
183 """Adjust the log level when log_level is set."""
184 self.log.setLevel(new)
185
186 def print_alias_help(self):
185 def print_alias_help(self):
187 """Print the alias part of the help."""
186 """Print the alias part of the help."""
188 if not self.aliases:
187 if not self.aliases:
189 return
188 return
190
189
191 lines = ['Aliases']
190 lines = ['Aliases']
192 lines.append('-'*len(lines[0]))
191 lines.append('-'*len(lines[0]))
193 lines.append('')
192 lines.append('')
194 for p in wrap_paragraphs(self.alias_description):
193 for p in wrap_paragraphs(self.alias_description):
195 lines.append(p)
194 lines.append(p)
196 lines.append('')
195 lines.append('')
197
196
198 classdict = {}
197 classdict = {}
199 for cls in self.classes:
198 for cls in self.classes:
200 # include all parents (up to, but excluding Configurable) in available names
199 # include all parents (up to, but excluding Configurable) in available names
201 for c in cls.mro()[:-3]:
200 for c in cls.mro()[:-3]:
202 classdict[c.__name__] = c
201 classdict[c.__name__] = c
203
202
204 for alias, longname in self.aliases.iteritems():
203 for alias, longname in self.aliases.iteritems():
205 classname, traitname = longname.split('.',1)
204 classname, traitname = longname.split('.',1)
206 cls = classdict[classname]
205 cls = classdict[classname]
207
206
208 trait = cls.class_traits(config=True)[traitname]
207 trait = cls.class_traits(config=True)[traitname]
209 help = cls.class_get_trait_help(trait)
208 help = cls.class_get_trait_help(trait)
210 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
209 help = help.replace(longname, "%s (%s)"%(alias, longname), 1)
211 lines.append(help)
210 lines.append(help)
212 lines.append('')
211 lines.append('')
213 print '\n'.join(lines)
212 print '\n'.join(lines)
214
213
215 def print_flag_help(self):
214 def print_flag_help(self):
216 """Print the flag part of the help."""
215 """Print the flag part of the help."""
217 if not self.flags:
216 if not self.flags:
218 return
217 return
219
218
220 lines = ['Flags']
219 lines = ['Flags']
221 lines.append('-'*len(lines[0]))
220 lines.append('-'*len(lines[0]))
222 lines.append('')
221 lines.append('')
223 for p in wrap_paragraphs(self.flag_description):
222 for p in wrap_paragraphs(self.flag_description):
224 lines.append(p)
223 lines.append(p)
225 lines.append('')
224 lines.append('')
226
225
227 for m, (cfg,help) in self.flags.iteritems():
226 for m, (cfg,help) in self.flags.iteritems():
228 lines.append('--'+m)
227 lines.append('--'+m)
229 lines.append(indent(dedent(help.strip())))
228 lines.append(indent(dedent(help.strip())))
230 lines.append('')
229 lines.append('')
231 print '\n'.join(lines)
230 print '\n'.join(lines)
232
231
233 def print_subcommands(self):
232 def print_subcommands(self):
234 """Print the subcommand part of the help."""
233 """Print the subcommand part of the help."""
235 if not self.subcommands:
234 if not self.subcommands:
236 return
235 return
237
236
238 lines = ["Subcommands"]
237 lines = ["Subcommands"]
239 lines.append('-'*len(lines[0]))
238 lines.append('-'*len(lines[0]))
240 for subc, (cls,help) in self.subcommands.iteritems():
239 for subc, (cls,help) in self.subcommands.iteritems():
241 lines.append("%s : %s"%(subc, cls))
240 lines.append("%s : %s"%(subc, cls))
242 if help:
241 if help:
243 lines.append(indent(dedent(help.strip())))
242 lines.append(indent(dedent(help.strip())))
244 lines.append('')
243 lines.append('')
245 print '\n'.join(lines)
244 print '\n'.join(lines)
246
245
247 def print_help(self, classes=False):
246 def print_help(self, classes=False):
248 """Print the help for each Configurable class in self.classes.
247 """Print the help for each Configurable class in self.classes.
249
248
250 If classes=False (the default), only flags and aliases are printed.
249 If classes=False (the default), only flags and aliases are printed.
251 """
250 """
252 self.print_subcommands()
251 self.print_subcommands()
253 self.print_flag_help()
252 self.print_flag_help()
254 self.print_alias_help()
253 self.print_alias_help()
255
254
256 if classes:
255 if classes:
257 if self.classes:
256 if self.classes:
258 print "Class parameters"
257 print "Class parameters"
259 print "----------------"
258 print "----------------"
260 print
259 print
261 for p in wrap_paragraphs(self.keyvalue_description):
260 for p in wrap_paragraphs(self.keyvalue_description):
262 print p
261 print p
263 print
262 print
264
263
265 for cls in self.classes:
264 for cls in self.classes:
266 cls.class_print_help()
265 cls.class_print_help()
267 print
266 print
268 else:
267 else:
269 print "To see all available configurables, use `--help-all`"
268 print "To see all available configurables, use `--help-all`"
270 print
269 print
271
270
272 def print_description(self):
271 def print_description(self):
273 """Print the application description."""
272 """Print the application description."""
274 for p in wrap_paragraphs(self.description):
273 for p in wrap_paragraphs(self.description):
275 print p
274 print p
276 print
275 print
277
276
278 def print_version(self):
277 def print_version(self):
279 """Print the version string."""
278 """Print the version string."""
280 print self.version
279 print self.version
281
280
282 def update_config(self, config):
281 def update_config(self, config):
283 """Fire the traits events when the config is updated."""
282 """Fire the traits events when the config is updated."""
284 # Save a copy of the current config.
283 # Save a copy of the current config.
285 newconfig = deepcopy(self.config)
284 newconfig = deepcopy(self.config)
286 # Merge the new config into the current one.
285 # Merge the new config into the current one.
287 newconfig._merge(config)
286 newconfig._merge(config)
288 # Save the combined config as self.config, which triggers the traits
287 # Save the combined config as self.config, which triggers the traits
289 # events.
288 # events.
290 self.config = newconfig
289 self.config = newconfig
291
290
292 def initialize_subcommand(self, subc, argv=None):
291 def initialize_subcommand(self, subc, argv=None):
293 """Initialize a subcommand with argv."""
292 """Initialize a subcommand with argv."""
294 subapp,help = self.subcommands.get(subc)
293 subapp,help = self.subcommands.get(subc)
295
294
296 if isinstance(subapp, basestring):
295 if isinstance(subapp, basestring):
297 subapp = import_item(subapp)
296 subapp = import_item(subapp)
298
297
299 # clear existing instances
298 # clear existing instances
300 self.__class__.clear_instance()
299 self.__class__.clear_instance()
301 # instantiate
300 # instantiate
302 self.subapp = subapp.instance()
301 self.subapp = subapp.instance()
303 # and initialize subapp
302 # and initialize subapp
304 self.subapp.initialize(argv)
303 self.subapp.initialize(argv)
305
304
306 def parse_command_line(self, argv=None):
305 def parse_command_line(self, argv=None):
307 """Parse the command line arguments."""
306 """Parse the command line arguments."""
308 argv = sys.argv[1:] if argv is None else argv
307 argv = sys.argv[1:] if argv is None else argv
309
308
310 if self.subcommands and len(argv) > 0:
309 if self.subcommands and len(argv) > 0:
311 # we have subcommands, and one may have been specified
310 # we have subcommands, and one may have been specified
312 subc, subargv = argv[0], argv[1:]
311 subc, subargv = argv[0], argv[1:]
313 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
312 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
314 # it's a subcommand, and *not* a flag or class parameter
313 # it's a subcommand, and *not* a flag or class parameter
315 return self.initialize_subcommand(subc, subargv)
314 return self.initialize_subcommand(subc, subargv)
316
315
317 if '-h' in argv or '--help' in argv or '--help-all' in argv:
316 if '-h' in argv or '--help' in argv or '--help-all' in argv:
318 self.print_description()
317 self.print_description()
319 self.print_help('--help-all' in argv)
318 self.print_help('--help-all' in argv)
320 self.exit(0)
319 self.exit(0)
321
320
322 if '--version' in argv:
321 if '--version' in argv:
323 self.print_version()
322 self.print_version()
324 self.exit(0)
323 self.exit(0)
325
324
326 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
325 loader = KeyValueConfigLoader(argv=argv, aliases=self.aliases,
327 flags=self.flags)
326 flags=self.flags)
328 try:
327 try:
329 config = loader.load_config()
328 config = loader.load_config()
330 except ArgumentError as e:
329 except ArgumentError as e:
331 self.log.fatal(str(e))
330 self.log.fatal(str(e))
332 self.print_description()
331 self.print_description()
333 self.print_help()
332 self.print_help()
334 self.exit(1)
333 self.exit(1)
335 self.update_config(config)
334 self.update_config(config)
336 # store unparsed args in extra_args
335 # store unparsed args in extra_args
337 self.extra_args = loader.extra_args
336 self.extra_args = loader.extra_args
338
337
339 def load_config_file(self, filename, path=None):
338 def load_config_file(self, filename, path=None):
340 """Load a .py based config file by filename and path."""
339 """Load a .py based config file by filename and path."""
341 loader = PyFileConfigLoader(filename, path=path)
340 loader = PyFileConfigLoader(filename, path=path)
342 config = loader.load_config()
341 config = loader.load_config()
343 self.update_config(config)
342 self.update_config(config)
344
343
345 def generate_config_file(self):
344 def generate_config_file(self):
346 """generate default config file from Configurables"""
345 """generate default config file from Configurables"""
347 lines = ["# Configuration file for %s."%self.name]
346 lines = ["# Configuration file for %s."%self.name]
348 lines.append('')
347 lines.append('')
349 lines.append('c = get_config()')
348 lines.append('c = get_config()')
350 lines.append('')
349 lines.append('')
351 for cls in self.classes:
350 for cls in self.classes:
352 lines.append(cls.class_config_section())
351 lines.append(cls.class_config_section())
353 return '\n'.join(lines)
352 return '\n'.join(lines)
354
353
355 def exit(self, exit_status=0):
354 def exit(self, exit_status=0):
356 self.log.debug("Exiting application: %s" % self.name)
355 self.log.debug("Exiting application: %s" % self.name)
357 sys.exit(exit_status)
356 sys.exit(exit_status)
358
357
359 #-----------------------------------------------------------------------------
358 #-----------------------------------------------------------------------------
360 # utility functions, for convenience
359 # utility functions, for convenience
361 #-----------------------------------------------------------------------------
360 #-----------------------------------------------------------------------------
362
361
363 def boolean_flag(name, configurable, set_help='', unset_help=''):
362 def boolean_flag(name, configurable, set_help='', unset_help=''):
364 """Helper for building basic --trait, --no-trait flags.
363 """Helper for building basic --trait, --no-trait flags.
365
364
366 Parameters
365 Parameters
367 ----------
366 ----------
368
367
369 name : str
368 name : str
370 The name of the flag.
369 The name of the flag.
371 configurable : str
370 configurable : str
372 The 'Class.trait' string of the trait to be set/unset with the flag
371 The 'Class.trait' string of the trait to be set/unset with the flag
373 set_help : unicode
372 set_help : unicode
374 help string for --name flag
373 help string for --name flag
375 unset_help : unicode
374 unset_help : unicode
376 help string for --no-name flag
375 help string for --no-name flag
377
376
378 Returns
377 Returns
379 -------
378 -------
380
379
381 cfg : dict
380 cfg : dict
382 A dict with two keys: 'name', and 'no-name', for setting and unsetting
381 A dict with two keys: 'name', and 'no-name', for setting and unsetting
383 the trait, respectively.
382 the trait, respectively.
384 """
383 """
385 # default helpstrings
384 # default helpstrings
386 set_help = set_help or "set %s=True"%configurable
385 set_help = set_help or "set %s=True"%configurable
387 unset_help = unset_help or "set %s=False"%configurable
386 unset_help = unset_help or "set %s=False"%configurable
388
387
389 cls,trait = configurable.split('.')
388 cls,trait = configurable.split('.')
390
389
391 setter = {cls : {trait : True}}
390 setter = {cls : {trait : True}}
392 unsetter = {cls : {trait : False}}
391 unsetter = {cls : {trait : False}}
393 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
392 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
394
393
General Comments 0
You need to be logged in to leave comments. Login now