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