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