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