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