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