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