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