##// END OF EJS Templates
Fix docstring of _log_default.
Antony Lee -
Show More
@@ -1,528 +1,528 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 from collections import defaultdict
28 28
29 29 from IPython.external.decorator import decorator
30 30
31 31 from IPython.config.configurable import SingletonConfigurable
32 32 from IPython.config.loader import (
33 33 KVArgParseConfigLoader, PyFileConfigLoader, Config, ArgumentError, ConfigFileNotFound,
34 34 )
35 35
36 36 from IPython.utils.traitlets import (
37 37 Unicode, List, Enum, Dict, Instance, TraitError
38 38 )
39 39 from IPython.utils.importstring import import_item
40 40 from IPython.utils.text import indent, wrap_paragraphs, dedent
41 41
42 42 #-----------------------------------------------------------------------------
43 43 # function for re-wrapping a helpstring
44 44 #-----------------------------------------------------------------------------
45 45
46 46 #-----------------------------------------------------------------------------
47 47 # Descriptions for the various sections
48 48 #-----------------------------------------------------------------------------
49 49
50 50 # merge flags&aliases into options
51 51 option_description = """
52 52 Arguments that take values are actually convenience aliases to full
53 53 Configurables, whose aliases are listed on the help line. For more information
54 54 on full configurables, see '--help-all'.
55 55 """.strip() # trim newlines of front and back
56 56
57 57 keyvalue_description = """
58 58 Parameters are set from command-line arguments of the form:
59 59 `--Class.trait=value`.
60 60 This line is evaluated in Python, so simple expressions are allowed, e.g.::
61 61 `--C.a='range(3)'` For setting C.a=[0,1,2].
62 62 """.strip() # trim newlines of front and back
63 63
64 64 subcommand_description = """
65 65 Subcommands are launched as `{app} cmd [args]`. For information on using
66 66 subcommand 'cmd', do: `{app} cmd -h`.
67 67 """.strip().format(app=os.path.basename(sys.argv[0]))
68 68 # get running program name
69 69
70 70 #-----------------------------------------------------------------------------
71 71 # Application class
72 72 #-----------------------------------------------------------------------------
73 73
74 74 @decorator
75 75 def catch_config_error(method, app, *args, **kwargs):
76 76 """Method decorator for catching invalid config (Trait/ArgumentErrors) during init.
77 77
78 78 On a TraitError (generally caused by bad config), this will print the trait's
79 79 message, and exit the app.
80 80
81 81 For use on init methods, to prevent invoking excepthook on invalid input.
82 82 """
83 83 try:
84 84 return method(app, *args, **kwargs)
85 85 except (TraitError, ArgumentError) as e:
86 86 app.print_description()
87 87 app.print_help()
88 88 app.print_examples()
89 89 app.log.fatal("Bad config encountered during initialization:")
90 90 app.log.fatal(str(e))
91 91 app.log.debug("Config at the time: %s", app.config)
92 92 app.exit(1)
93 93
94 94
95 95 class ApplicationError(Exception):
96 96 pass
97 97
98 98
99 99 class Application(SingletonConfigurable):
100 100 """A singleton application with full configuration support."""
101 101
102 102 # The name of the application, will usually match the name of the command
103 103 # line application
104 104 name = Unicode(u'application')
105 105
106 106 # The description of the application that is printed at the beginning
107 107 # of the help.
108 108 description = Unicode(u'This is an application.')
109 109 # default section descriptions
110 110 option_description = Unicode(option_description)
111 111 keyvalue_description = Unicode(keyvalue_description)
112 112 subcommand_description = Unicode(subcommand_description)
113 113
114 114 # The usage and example string that goes at the end of the help string.
115 115 examples = Unicode()
116 116
117 117 # A sequence of Configurable subclasses whose config=True attributes will
118 118 # be exposed at the command line.
119 119 classes = List([])
120 120
121 121 # The version string of this application.
122 122 version = Unicode(u'0.0')
123 123
124 124 # The log level for the application
125 125 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
126 126 default_value=logging.WARN,
127 127 config=True,
128 128 help="Set the log level by value or name.")
129 129 def _log_level_changed(self, name, old, new):
130 130 """Adjust the log level when log_level is set."""
131 131 if isinstance(new, basestring):
132 132 new = getattr(logging, new)
133 133 self.log_level = new
134 134 self.log.setLevel(new)
135 135
136 136 log_format = Unicode("[%(name)s] %(message)s", config=True,
137 137 help="The Logging format template",
138 138 )
139 139 log = Instance(logging.Logger)
140 140 def _log_default(self):
141 141 """Start logging for this application.
142 142
143 The default is to log to stdout using a StreamHandler, if no default
143 The default is to log to stderr using a StreamHandler, if no default
144 144 handler already exists. The log level starts at logging.WARN, but this
145 145 can be adjusted by setting the ``log_level`` attribute.
146 146 """
147 147 log = logging.getLogger(self.__class__.__name__)
148 148 log.setLevel(self.log_level)
149 149 _log = log # copied from Logger.hasHandlers() (new in Python 3.2)
150 150 while _log:
151 151 if _log.handlers:
152 152 return log
153 153 if not _log.propagate:
154 154 break
155 155 else:
156 156 _log = _log.parent
157 157 if sys.executable.endswith('pythonw.exe'):
158 158 # this should really go to a file, but file-logging is only
159 159 # hooked up in parallel applications
160 160 _log_handler = logging.StreamHandler(open(os.devnull, 'w'))
161 161 else:
162 162 _log_handler = logging.StreamHandler()
163 163 _log_formatter = logging.Formatter(self.log_format)
164 164 _log_handler.setFormatter(_log_formatter)
165 165 log.addHandler(_log_handler)
166 166 return log
167 167
168 168 # the alias map for configurables
169 169 aliases = Dict({'log-level' : 'Application.log_level'})
170 170
171 171 # flags for loading Configurables or store_const style flags
172 172 # flags are loaded from this dict by '--key' flags
173 173 # this must be a dict of two-tuples, the first element being the Config/dict
174 174 # and the second being the help string for the flag
175 175 flags = Dict()
176 176 def _flags_changed(self, name, old, new):
177 177 """ensure flags dict is valid"""
178 178 for key,value in new.iteritems():
179 179 assert len(value) == 2, "Bad flag: %r:%s"%(key,value)
180 180 assert isinstance(value[0], (dict, Config)), "Bad flag: %r:%s"%(key,value)
181 181 assert isinstance(value[1], basestring), "Bad flag: %r:%s"%(key,value)
182 182
183 183
184 184 # subcommands for launching other applications
185 185 # if this is not empty, this will be a parent Application
186 186 # this must be a dict of two-tuples,
187 187 # the first element being the application class/import string
188 188 # and the second being the help string for the subcommand
189 189 subcommands = Dict()
190 190 # parse_command_line will initialize a subapp, if requested
191 191 subapp = Instance('IPython.config.application.Application', allow_none=True)
192 192
193 193 # extra command-line arguments that don't set config values
194 194 extra_args = List(Unicode)
195 195
196 196
197 197 def __init__(self, **kwargs):
198 198 SingletonConfigurable.__init__(self, **kwargs)
199 199 # Ensure my class is in self.classes, so my attributes appear in command line
200 200 # options and config files.
201 201 if self.__class__ not in self.classes:
202 202 self.classes.insert(0, self.__class__)
203 203
204 204 def _config_changed(self, name, old, new):
205 205 SingletonConfigurable._config_changed(self, name, old, new)
206 206 self.log.debug('Config changed:')
207 207 self.log.debug(repr(new))
208 208
209 209 @catch_config_error
210 210 def initialize(self, argv=None):
211 211 """Do the basic steps to configure me.
212 212
213 213 Override in subclasses.
214 214 """
215 215 self.parse_command_line(argv)
216 216
217 217
218 218 def start(self):
219 219 """Start the app mainloop.
220 220
221 221 Override in subclasses.
222 222 """
223 223 if self.subapp is not None:
224 224 return self.subapp.start()
225 225
226 226 def print_alias_help(self):
227 227 """Print the alias part of the help."""
228 228 if not self.aliases:
229 229 return
230 230
231 231 lines = []
232 232 classdict = {}
233 233 for cls in self.classes:
234 234 # include all parents (up to, but excluding Configurable) in available names
235 235 for c in cls.mro()[:-3]:
236 236 classdict[c.__name__] = c
237 237
238 238 for alias, longname in self.aliases.iteritems():
239 239 classname, traitname = longname.split('.',1)
240 240 cls = classdict[classname]
241 241
242 242 trait = cls.class_traits(config=True)[traitname]
243 243 help = cls.class_get_trait_help(trait).splitlines()
244 244 # reformat first line
245 245 help[0] = help[0].replace(longname, alias) + ' (%s)'%longname
246 246 if len(alias) == 1:
247 247 help[0] = help[0].replace('--%s='%alias, '-%s '%alias)
248 248 lines.extend(help)
249 249 # lines.append('')
250 250 print os.linesep.join(lines)
251 251
252 252 def print_flag_help(self):
253 253 """Print the flag part of the help."""
254 254 if not self.flags:
255 255 return
256 256
257 257 lines = []
258 258 for m, (cfg,help) in self.flags.iteritems():
259 259 prefix = '--' if len(m) > 1 else '-'
260 260 lines.append(prefix+m)
261 261 lines.append(indent(dedent(help.strip())))
262 262 # lines.append('')
263 263 print os.linesep.join(lines)
264 264
265 265 def print_options(self):
266 266 if not self.flags and not self.aliases:
267 267 return
268 268 lines = ['Options']
269 269 lines.append('-'*len(lines[0]))
270 270 lines.append('')
271 271 for p in wrap_paragraphs(self.option_description):
272 272 lines.append(p)
273 273 lines.append('')
274 274 print os.linesep.join(lines)
275 275 self.print_flag_help()
276 276 self.print_alias_help()
277 277 print
278 278
279 279 def print_subcommands(self):
280 280 """Print the subcommand part of the help."""
281 281 if not self.subcommands:
282 282 return
283 283
284 284 lines = ["Subcommands"]
285 285 lines.append('-'*len(lines[0]))
286 286 lines.append('')
287 287 for p in wrap_paragraphs(self.subcommand_description):
288 288 lines.append(p)
289 289 lines.append('')
290 290 for subc, (cls, help) in self.subcommands.iteritems():
291 291 lines.append(subc)
292 292 if help:
293 293 lines.append(indent(dedent(help.strip())))
294 294 lines.append('')
295 295 print os.linesep.join(lines)
296 296
297 297 def print_help(self, classes=False):
298 298 """Print the help for each Configurable class in self.classes.
299 299
300 300 If classes=False (the default), only flags and aliases are printed.
301 301 """
302 302 self.print_subcommands()
303 303 self.print_options()
304 304
305 305 if classes:
306 306 if self.classes:
307 307 print "Class parameters"
308 308 print "----------------"
309 309 print
310 310 for p in wrap_paragraphs(self.keyvalue_description):
311 311 print p
312 312 print
313 313
314 314 for cls in self.classes:
315 315 cls.class_print_help()
316 316 print
317 317 else:
318 318 print "To see all available configurables, use `--help-all`"
319 319 print
320 320
321 321 def print_description(self):
322 322 """Print the application description."""
323 323 for p in wrap_paragraphs(self.description):
324 324 print p
325 325 print
326 326
327 327 def print_examples(self):
328 328 """Print usage and examples.
329 329
330 330 This usage string goes at the end of the command line help string
331 331 and should contain examples of the application's usage.
332 332 """
333 333 if self.examples:
334 334 print "Examples"
335 335 print "--------"
336 336 print
337 337 print indent(dedent(self.examples.strip()))
338 338 print
339 339
340 340 def print_version(self):
341 341 """Print the version string."""
342 342 print self.version
343 343
344 344 def update_config(self, config):
345 345 """Fire the traits events when the config is updated."""
346 346 # Save a copy of the current config.
347 347 newconfig = deepcopy(self.config)
348 348 # Merge the new config into the current one.
349 349 newconfig._merge(config)
350 350 # Save the combined config as self.config, which triggers the traits
351 351 # events.
352 352 self.config = newconfig
353 353
354 354 @catch_config_error
355 355 def initialize_subcommand(self, subc, argv=None):
356 356 """Initialize a subcommand with argv."""
357 357 subapp,help = self.subcommands.get(subc)
358 358
359 359 if isinstance(subapp, basestring):
360 360 subapp = import_item(subapp)
361 361
362 362 # clear existing instances
363 363 self.__class__.clear_instance()
364 364 # instantiate
365 365 self.subapp = subapp.instance()
366 366 # and initialize subapp
367 367 self.subapp.initialize(argv)
368 368
369 369 def flatten_flags(self):
370 370 """flatten flags and aliases, so cl-args override as expected.
371 371
372 372 This prevents issues such as an alias pointing to InteractiveShell,
373 373 but a config file setting the same trait in TerminalInteraciveShell
374 374 getting inappropriate priority over the command-line arg.
375 375
376 376 Only aliases with exactly one descendent in the class list
377 377 will be promoted.
378 378
379 379 """
380 380 # build a tree of classes in our list that inherit from a particular
381 381 # it will be a dict by parent classname of classes in our list
382 382 # that are descendents
383 383 mro_tree = defaultdict(list)
384 384 for cls in self.classes:
385 385 clsname = cls.__name__
386 386 for parent in cls.mro()[1:-3]:
387 387 # exclude cls itself and Configurable,HasTraits,object
388 388 mro_tree[parent.__name__].append(clsname)
389 389 # flatten aliases, which have the form:
390 390 # { 'alias' : 'Class.trait' }
391 391 aliases = {}
392 392 for alias, cls_trait in self.aliases.iteritems():
393 393 cls,trait = cls_trait.split('.',1)
394 394 children = mro_tree[cls]
395 395 if len(children) == 1:
396 396 # exactly one descendent, promote alias
397 397 cls = children[0]
398 398 aliases[alias] = '.'.join([cls,trait])
399 399
400 400 # flatten flags, which are of the form:
401 401 # { 'key' : ({'Cls' : {'trait' : value}}, 'help')}
402 402 flags = {}
403 403 for key, (flagdict, help) in self.flags.iteritems():
404 404 newflag = {}
405 405 for cls, subdict in flagdict.iteritems():
406 406 children = mro_tree[cls]
407 407 # exactly one descendent, promote flag section
408 408 if len(children) == 1:
409 409 cls = children[0]
410 410 newflag[cls] = subdict
411 411 flags[key] = (newflag, help)
412 412 return flags, aliases
413 413
414 414 @catch_config_error
415 415 def parse_command_line(self, argv=None):
416 416 """Parse the command line arguments."""
417 417 argv = sys.argv[1:] if argv is None else argv
418 418
419 419 if argv and argv[0] == 'help':
420 420 # turn `ipython help notebook` into `ipython notebook -h`
421 421 argv = argv[1:] + ['-h']
422 422
423 423 if self.subcommands and len(argv) > 0:
424 424 # we have subcommands, and one may have been specified
425 425 subc, subargv = argv[0], argv[1:]
426 426 if re.match(r'^\w(\-?\w)*$', subc) and subc in self.subcommands:
427 427 # it's a subcommand, and *not* a flag or class parameter
428 428 return self.initialize_subcommand(subc, subargv)
429 429
430 430 # Arguments after a '--' argument are for the script IPython may be
431 431 # about to run, not IPython iteslf. For arguments parsed here (help and
432 432 # version), we want to only search the arguments up to the first
433 433 # occurrence of '--', which we're calling interpreted_argv.
434 434 try:
435 435 interpreted_argv = argv[:argv.index('--')]
436 436 except ValueError:
437 437 interpreted_argv = argv
438 438
439 439 if any(x in interpreted_argv for x in ('-h', '--help-all', '--help')):
440 440 self.print_description()
441 441 self.print_help('--help-all' in interpreted_argv)
442 442 self.print_examples()
443 443 self.exit(0)
444 444
445 445 if '--version' in interpreted_argv or '-V' in interpreted_argv:
446 446 self.print_version()
447 447 self.exit(0)
448 448
449 449 # flatten flags&aliases, so cl-args get appropriate priority:
450 450 flags,aliases = self.flatten_flags()
451 451
452 452 loader = KVArgParseConfigLoader(argv=argv, aliases=aliases,
453 453 flags=flags)
454 454 config = loader.load_config()
455 455 self.update_config(config)
456 456 # store unparsed args in extra_args
457 457 self.extra_args = loader.extra_args
458 458
459 459 @catch_config_error
460 460 def load_config_file(self, filename, path=None):
461 461 """Load a .py based config file by filename and path."""
462 462 loader = PyFileConfigLoader(filename, path=path)
463 463 try:
464 464 config = loader.load_config()
465 465 except ConfigFileNotFound:
466 466 # problem finding the file, raise
467 467 raise
468 468 except Exception:
469 469 # try to get the full filename, but it will be empty in the
470 470 # unlikely event that the error raised before filefind finished
471 471 filename = loader.full_filename or filename
472 472 # problem while running the file
473 473 self.log.error("Exception while loading config file %s",
474 474 filename, exc_info=True)
475 475 else:
476 476 self.log.debug("Loaded config file: %s", loader.full_filename)
477 477 self.update_config(config)
478 478
479 479 def generate_config_file(self):
480 480 """generate default config file from Configurables"""
481 481 lines = ["# Configuration file for %s."%self.name]
482 482 lines.append('')
483 483 lines.append('c = get_config()')
484 484 lines.append('')
485 485 for cls in self.classes:
486 486 lines.append(cls.class_config_section())
487 487 return '\n'.join(lines)
488 488
489 489 def exit(self, exit_status=0):
490 490 self.log.debug("Exiting application: %s" % self.name)
491 491 sys.exit(exit_status)
492 492
493 493 #-----------------------------------------------------------------------------
494 494 # utility functions, for convenience
495 495 #-----------------------------------------------------------------------------
496 496
497 497 def boolean_flag(name, configurable, set_help='', unset_help=''):
498 498 """Helper for building basic --trait, --no-trait flags.
499 499
500 500 Parameters
501 501 ----------
502 502
503 503 name : str
504 504 The name of the flag.
505 505 configurable : str
506 506 The 'Class.trait' string of the trait to be set/unset with the flag
507 507 set_help : unicode
508 508 help string for --name flag
509 509 unset_help : unicode
510 510 help string for --no-name flag
511 511
512 512 Returns
513 513 -------
514 514
515 515 cfg : dict
516 516 A dict with two keys: 'name', and 'no-name', for setting and unsetting
517 517 the trait, respectively.
518 518 """
519 519 # default helpstrings
520 520 set_help = set_help or "set %s=True"%configurable
521 521 unset_help = unset_help or "set %s=False"%configurable
522 522
523 523 cls,trait = configurable.split('.')
524 524
525 525 setter = {cls : {trait : True}}
526 526 unsetter = {cls : {trait : False}}
527 527 return {name : (setter, set_help), 'no-'+name : (unsetter, unset_help)}
528 528
General Comments 0
You need to be logged in to leave comments. Login now