##// END OF EJS Templates
Handle errors from older versions of argparse.
Thomas Kluyver -
Show More
@@ -1,374 +1,374 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # coding: utf-8
2 # coding: utf-8
3 """A simple configuration system.
3 """A simple configuration system.
4
4
5 Authors
5 Authors
6 -------
6 -------
7 * Brian Granger
7 * Brian Granger
8 * Fernando Perez
8 * Fernando Perez
9 """
9 """
10
10
11 #-----------------------------------------------------------------------------
11 #-----------------------------------------------------------------------------
12 # Copyright (C) 2008-2009 The IPython Development Team
12 # Copyright (C) 2008-2009 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 __builtin__
22 import __builtin__
23 import os
23 import os
24 import sys
24 import sys
25
25
26 from IPython.external import argparse
26 from IPython.external import argparse
27 from IPython.utils.path import filefind
27 from IPython.utils.path import filefind
28
28
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30 # Exceptions
30 # Exceptions
31 #-----------------------------------------------------------------------------
31 #-----------------------------------------------------------------------------
32
32
33
33
34 class ConfigError(Exception):
34 class ConfigError(Exception):
35 pass
35 pass
36
36
37
37
38 class ConfigLoaderError(ConfigError):
38 class ConfigLoaderError(ConfigError):
39 pass
39 pass
40
40
41 #-----------------------------------------------------------------------------
41 #-----------------------------------------------------------------------------
42 # Argparse fix
42 # Argparse fix
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44
44
45 # Unfortunately argparse by default prints help messages to stderr instead of
45 # Unfortunately argparse by default prints help messages to stderr instead of
46 # stdout. This makes it annoying to capture long help screens at the command
46 # stdout. This makes it annoying to capture long help screens at the command
47 # line, since one must know how to pipe stderr, which many users don't know how
47 # line, since one must know how to pipe stderr, which many users don't know how
48 # to do. So we override the print_help method with one that defaults to
48 # to do. So we override the print_help method with one that defaults to
49 # stdout and use our class instead.
49 # stdout and use our class instead.
50
50
51 class ArgumentParser(argparse.ArgumentParser):
51 class ArgumentParser(argparse.ArgumentParser):
52 """Simple argparse subclass that prints help to stdout by default."""
52 """Simple argparse subclass that prints help to stdout by default."""
53
53
54 def print_help(self, file=None):
54 def print_help(self, file=None):
55 if file is None:
55 if file is None:
56 file = sys.stdout
56 file = sys.stdout
57 return super(ArgumentParser, self).print_help(file)
57 return super(ArgumentParser, self).print_help(file)
58
58
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
59 print_help.__doc__ = argparse.ArgumentParser.print_help.__doc__
60
60
61 #-----------------------------------------------------------------------------
61 #-----------------------------------------------------------------------------
62 # Config class for holding config information
62 # Config class for holding config information
63 #-----------------------------------------------------------------------------
63 #-----------------------------------------------------------------------------
64
64
65
65
66 class Config(dict):
66 class Config(dict):
67 """An attribute based dict that can do smart merges."""
67 """An attribute based dict that can do smart merges."""
68
68
69 def __init__(self, *args, **kwds):
69 def __init__(self, *args, **kwds):
70 dict.__init__(self, *args, **kwds)
70 dict.__init__(self, *args, **kwds)
71 # This sets self.__dict__ = self, but it has to be done this way
71 # This sets self.__dict__ = self, but it has to be done this way
72 # because we are also overriding __setattr__.
72 # because we are also overriding __setattr__.
73 dict.__setattr__(self, '__dict__', self)
73 dict.__setattr__(self, '__dict__', self)
74
74
75 def _merge(self, other):
75 def _merge(self, other):
76 to_update = {}
76 to_update = {}
77 for k, v in other.iteritems():
77 for k, v in other.iteritems():
78 if not self.has_key(k):
78 if not self.has_key(k):
79 to_update[k] = v
79 to_update[k] = v
80 else: # I have this key
80 else: # I have this key
81 if isinstance(v, Config):
81 if isinstance(v, Config):
82 # Recursively merge common sub Configs
82 # Recursively merge common sub Configs
83 self[k]._merge(v)
83 self[k]._merge(v)
84 else:
84 else:
85 # Plain updates for non-Configs
85 # Plain updates for non-Configs
86 to_update[k] = v
86 to_update[k] = v
87
87
88 self.update(to_update)
88 self.update(to_update)
89
89
90 def _is_section_key(self, key):
90 def _is_section_key(self, key):
91 if key[0].upper()==key[0] and not key.startswith('_'):
91 if key[0].upper()==key[0] and not key.startswith('_'):
92 return True
92 return True
93 else:
93 else:
94 return False
94 return False
95
95
96 def __contains__(self, key):
96 def __contains__(self, key):
97 if self._is_section_key(key):
97 if self._is_section_key(key):
98 return True
98 return True
99 else:
99 else:
100 return super(Config, self).__contains__(key)
100 return super(Config, self).__contains__(key)
101 # .has_key is deprecated for dictionaries.
101 # .has_key is deprecated for dictionaries.
102 has_key = __contains__
102 has_key = __contains__
103
103
104 def _has_section(self, key):
104 def _has_section(self, key):
105 if self._is_section_key(key):
105 if self._is_section_key(key):
106 if super(Config, self).__contains__(key):
106 if super(Config, self).__contains__(key):
107 return True
107 return True
108 return False
108 return False
109
109
110 def copy(self):
110 def copy(self):
111 return type(self)(dict.copy(self))
111 return type(self)(dict.copy(self))
112
112
113 def __copy__(self):
113 def __copy__(self):
114 return self.copy()
114 return self.copy()
115
115
116 def __deepcopy__(self, memo):
116 def __deepcopy__(self, memo):
117 import copy
117 import copy
118 return type(self)(copy.deepcopy(self.items()))
118 return type(self)(copy.deepcopy(self.items()))
119
119
120 def __getitem__(self, key):
120 def __getitem__(self, key):
121 # Because we use this for an exec namespace, we need to delegate
121 # Because we use this for an exec namespace, we need to delegate
122 # the lookup of names in __builtin__ to itself. This means
122 # the lookup of names in __builtin__ to itself. This means
123 # that you can't have section or attribute names that are
123 # that you can't have section or attribute names that are
124 # builtins.
124 # builtins.
125 try:
125 try:
126 return getattr(__builtin__, key)
126 return getattr(__builtin__, key)
127 except AttributeError:
127 except AttributeError:
128 pass
128 pass
129 if self._is_section_key(key):
129 if self._is_section_key(key):
130 try:
130 try:
131 return dict.__getitem__(self, key)
131 return dict.__getitem__(self, key)
132 except KeyError:
132 except KeyError:
133 c = Config()
133 c = Config()
134 dict.__setitem__(self, key, c)
134 dict.__setitem__(self, key, c)
135 return c
135 return c
136 else:
136 else:
137 return dict.__getitem__(self, key)
137 return dict.__getitem__(self, key)
138
138
139 def __setitem__(self, key, value):
139 def __setitem__(self, key, value):
140 # Don't allow names in __builtin__ to be modified.
140 # Don't allow names in __builtin__ to be modified.
141 if hasattr(__builtin__, key):
141 if hasattr(__builtin__, key):
142 raise ConfigError('Config variable names cannot have the same name '
142 raise ConfigError('Config variable names cannot have the same name '
143 'as a Python builtin: %s' % key)
143 'as a Python builtin: %s' % key)
144 if self._is_section_key(key):
144 if self._is_section_key(key):
145 if not isinstance(value, Config):
145 if not isinstance(value, Config):
146 raise ValueError('values whose keys begin with an uppercase '
146 raise ValueError('values whose keys begin with an uppercase '
147 'char must be Config instances: %r, %r' % (key, value))
147 'char must be Config instances: %r, %r' % (key, value))
148 else:
148 else:
149 dict.__setitem__(self, key, value)
149 dict.__setitem__(self, key, value)
150
150
151 def __getattr__(self, key):
151 def __getattr__(self, key):
152 try:
152 try:
153 return self.__getitem__(key)
153 return self.__getitem__(key)
154 except KeyError, e:
154 except KeyError, e:
155 raise AttributeError(e)
155 raise AttributeError(e)
156
156
157 def __setattr__(self, key, value):
157 def __setattr__(self, key, value):
158 try:
158 try:
159 self.__setitem__(key, value)
159 self.__setitem__(key, value)
160 except KeyError, e:
160 except KeyError, e:
161 raise AttributeError(e)
161 raise AttributeError(e)
162
162
163 def __delattr__(self, key):
163 def __delattr__(self, key):
164 try:
164 try:
165 dict.__delitem__(self, key)
165 dict.__delitem__(self, key)
166 except KeyError, e:
166 except KeyError, e:
167 raise AttributeError(e)
167 raise AttributeError(e)
168
168
169
169
170 #-----------------------------------------------------------------------------
170 #-----------------------------------------------------------------------------
171 # Config loading classes
171 # Config loading classes
172 #-----------------------------------------------------------------------------
172 #-----------------------------------------------------------------------------
173
173
174
174
175 class ConfigLoader(object):
175 class ConfigLoader(object):
176 """A object for loading configurations from just about anywhere.
176 """A object for loading configurations from just about anywhere.
177
177
178 The resulting configuration is packaged as a :class:`Struct`.
178 The resulting configuration is packaged as a :class:`Struct`.
179
179
180 Notes
180 Notes
181 -----
181 -----
182 A :class:`ConfigLoader` does one thing: load a config from a source
182 A :class:`ConfigLoader` does one thing: load a config from a source
183 (file, command line arguments) and returns the data as a :class:`Struct`.
183 (file, command line arguments) and returns the data as a :class:`Struct`.
184 There are lots of things that :class:`ConfigLoader` does not do. It does
184 There are lots of things that :class:`ConfigLoader` does not do. It does
185 not implement complex logic for finding config files. It does not handle
185 not implement complex logic for finding config files. It does not handle
186 default values or merge multiple configs. These things need to be
186 default values or merge multiple configs. These things need to be
187 handled elsewhere.
187 handled elsewhere.
188 """
188 """
189
189
190 def __init__(self):
190 def __init__(self):
191 """A base class for config loaders.
191 """A base class for config loaders.
192
192
193 Examples
193 Examples
194 --------
194 --------
195
195
196 >>> cl = ConfigLoader()
196 >>> cl = ConfigLoader()
197 >>> config = cl.load_config()
197 >>> config = cl.load_config()
198 >>> config
198 >>> config
199 {}
199 {}
200 """
200 """
201 self.clear()
201 self.clear()
202
202
203 def clear(self):
203 def clear(self):
204 self.config = Config()
204 self.config = Config()
205
205
206 def load_config(self):
206 def load_config(self):
207 """Load a config from somewhere, return a :class:`Config` instance.
207 """Load a config from somewhere, return a :class:`Config` instance.
208
208
209 Usually, this will cause self.config to be set and then returned.
209 Usually, this will cause self.config to be set and then returned.
210 However, in most cases, :meth:`ConfigLoader.clear` should be called
210 However, in most cases, :meth:`ConfigLoader.clear` should be called
211 to erase any previous state.
211 to erase any previous state.
212 """
212 """
213 self.clear()
213 self.clear()
214 return self.config
214 return self.config
215
215
216
216
217 class FileConfigLoader(ConfigLoader):
217 class FileConfigLoader(ConfigLoader):
218 """A base class for file based configurations.
218 """A base class for file based configurations.
219
219
220 As we add more file based config loaders, the common logic should go
220 As we add more file based config loaders, the common logic should go
221 here.
221 here.
222 """
222 """
223 pass
223 pass
224
224
225
225
226 class PyFileConfigLoader(FileConfigLoader):
226 class PyFileConfigLoader(FileConfigLoader):
227 """A config loader for pure python files.
227 """A config loader for pure python files.
228
228
229 This calls execfile on a plain python file and looks for attributes
229 This calls execfile on a plain python file and looks for attributes
230 that are all caps. These attribute are added to the config Struct.
230 that are all caps. These attribute are added to the config Struct.
231 """
231 """
232
232
233 def __init__(self, filename, path=None):
233 def __init__(self, filename, path=None):
234 """Build a config loader for a filename and path.
234 """Build a config loader for a filename and path.
235
235
236 Parameters
236 Parameters
237 ----------
237 ----------
238 filename : str
238 filename : str
239 The file name of the config file.
239 The file name of the config file.
240 path : str, list, tuple
240 path : str, list, tuple
241 The path to search for the config file on, or a sequence of
241 The path to search for the config file on, or a sequence of
242 paths to try in order.
242 paths to try in order.
243 """
243 """
244 super(PyFileConfigLoader, self).__init__()
244 super(PyFileConfigLoader, self).__init__()
245 self.filename = filename
245 self.filename = filename
246 self.path = path
246 self.path = path
247 self.full_filename = ''
247 self.full_filename = ''
248 self.data = None
248 self.data = None
249
249
250 def load_config(self):
250 def load_config(self):
251 """Load the config from a file and return it as a Struct."""
251 """Load the config from a file and return it as a Struct."""
252 self.clear()
252 self.clear()
253 self._find_file()
253 self._find_file()
254 self._read_file_as_dict()
254 self._read_file_as_dict()
255 self._convert_to_config()
255 self._convert_to_config()
256 return self.config
256 return self.config
257
257
258 def _find_file(self):
258 def _find_file(self):
259 """Try to find the file by searching the paths."""
259 """Try to find the file by searching the paths."""
260 self.full_filename = filefind(self.filename, self.path)
260 self.full_filename = filefind(self.filename, self.path)
261
261
262 def _read_file_as_dict(self):
262 def _read_file_as_dict(self):
263 """Load the config file into self.config, with recursive loading."""
263 """Load the config file into self.config, with recursive loading."""
264 # This closure is made available in the namespace that is used
264 # This closure is made available in the namespace that is used
265 # to exec the config file. This allows users to call
265 # to exec the config file. This allows users to call
266 # load_subconfig('myconfig.py') to load config files recursively.
266 # load_subconfig('myconfig.py') to load config files recursively.
267 # It needs to be a closure because it has references to self.path
267 # It needs to be a closure because it has references to self.path
268 # and self.config. The sub-config is loaded with the same path
268 # and self.config. The sub-config is loaded with the same path
269 # as the parent, but it uses an empty config which is then merged
269 # as the parent, but it uses an empty config which is then merged
270 # with the parents.
270 # with the parents.
271 def load_subconfig(fname):
271 def load_subconfig(fname):
272 loader = PyFileConfigLoader(fname, self.path)
272 loader = PyFileConfigLoader(fname, self.path)
273 try:
273 try:
274 sub_config = loader.load_config()
274 sub_config = loader.load_config()
275 except IOError:
275 except IOError:
276 # Pass silently if the sub config is not there. This happens
276 # Pass silently if the sub config is not there. This happens
277 # when a user us using a profile, but not the default config.
277 # when a user us using a profile, but not the default config.
278 pass
278 pass
279 else:
279 else:
280 self.config._merge(sub_config)
280 self.config._merge(sub_config)
281
281
282 # Again, this needs to be a closure and should be used in config
282 # Again, this needs to be a closure and should be used in config
283 # files to get the config being loaded.
283 # files to get the config being loaded.
284 def get_config():
284 def get_config():
285 return self.config
285 return self.config
286
286
287 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
287 namespace = dict(load_subconfig=load_subconfig, get_config=get_config)
288 execfile(self.full_filename, namespace)
288 execfile(self.full_filename, namespace)
289
289
290 def _convert_to_config(self):
290 def _convert_to_config(self):
291 if self.data is None:
291 if self.data is None:
292 ConfigLoaderError('self.data does not exist')
292 ConfigLoaderError('self.data does not exist')
293
293
294
294
295 class CommandLineConfigLoader(ConfigLoader):
295 class CommandLineConfigLoader(ConfigLoader):
296 """A config loader for command line arguments.
296 """A config loader for command line arguments.
297
297
298 As we add more command line based loaders, the common logic should go
298 As we add more command line based loaders, the common logic should go
299 here.
299 here.
300 """
300 """
301
301
302
302
303 class ArgParseConfigLoader(CommandLineConfigLoader):
303 class ArgParseConfigLoader(CommandLineConfigLoader):
304
304
305 def __init__(self, argv=None, *parser_args, **parser_kw):
305 def __init__(self, argv=None, *parser_args, **parser_kw):
306 """Create a config loader for use with argparse.
306 """Create a config loader for use with argparse.
307
307
308 Parameters
308 Parameters
309 ----------
309 ----------
310
310
311 argv : optional, list
311 argv : optional, list
312 If given, used to read command-line arguments from, otherwise
312 If given, used to read command-line arguments from, otherwise
313 sys.argv[1:] is used.
313 sys.argv[1:] is used.
314
314
315 parser_args : tuple
315 parser_args : tuple
316 A tuple of positional arguments that will be passed to the
316 A tuple of positional arguments that will be passed to the
317 constructor of :class:`argparse.ArgumentParser`.
317 constructor of :class:`argparse.ArgumentParser`.
318
318
319 parser_kw : dict
319 parser_kw : dict
320 A tuple of keyword arguments that will be passed to the
320 A tuple of keyword arguments that will be passed to the
321 constructor of :class:`argparse.ArgumentParser`.
321 constructor of :class:`argparse.ArgumentParser`.
322 """
322 """
323 super(CommandLineConfigLoader, self).__init__()
323 super(CommandLineConfigLoader, self).__init__()
324 if argv == None:
324 if argv == None:
325 argv = sys.argv[1:]
325 argv = sys.argv[1:]
326 self.argv = argv
326 self.argv = argv
327 self.parser_args = parser_args
327 self.parser_args = parser_args
328 self.version = parser_kw.pop("version", None)
328 self.version = parser_kw.pop("version", None)
329 kwargs = dict(argument_default=argparse.SUPPRESS)
329 kwargs = dict(argument_default=argparse.SUPPRESS)
330 kwargs.update(parser_kw)
330 kwargs.update(parser_kw)
331 self.parser_kw = kwargs
331 self.parser_kw = kwargs
332
332
333 def load_config(self, args=None):
333 def load_config(self, args=None):
334 """Parse command line arguments and return as a Struct.
334 """Parse command line arguments and return as a Struct.
335
335
336 Parameters
336 Parameters
337 ----------
337 ----------
338
338
339 args : optional, list
339 args : optional, list
340 If given, a list with the structure of sys.argv[1:] to parse
340 If given, a list with the structure of sys.argv[1:] to parse
341 arguments from. If not given, the instance's self.argv attribute
341 arguments from. If not given, the instance's self.argv attribute
342 (given at construction time) is used."""
342 (given at construction time) is used."""
343 self.clear()
343 self.clear()
344 if args is None:
344 if args is None:
345 args = self.argv
345 args = self.argv
346 self._create_parser()
346 self._create_parser()
347 self._parse_args(args)
347 self._parse_args(args)
348 self._convert_to_config()
348 self._convert_to_config()
349 return self.config
349 return self.config
350
350
351 def get_extra_args(self):
351 def get_extra_args(self):
352 if hasattr(self, 'extra_args'):
352 if hasattr(self, 'extra_args'):
353 return self.extra_args
353 return self.extra_args
354 else:
354 else:
355 return []
355 return []
356
356
357 def _create_parser(self):
357 def _create_parser(self):
358 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
358 self.parser = ArgumentParser(*self.parser_args, **self.parser_kw)
359 self._add_arguments()
359 self._add_arguments()
360
360
361 def _add_arguments(self):
361 def _add_arguments(self):
362 raise NotImplementedError("subclasses must implement _add_arguments")
362 raise NotImplementedError("subclasses must implement _add_arguments")
363
363
364 def _parse_args(self, args):
364 def _parse_args(self, args):
365 """self.parser->self.parsed_data"""
365 """self.parser->self.parsed_data"""
366 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
366 self.parsed_data, self.extra_args = self.parser.parse_known_args(args)
367
367
368 def _convert_to_config(self):
368 def _convert_to_config(self):
369 """self.parsed_data->self.config"""
369 """self.parsed_data->self.config"""
370 for k, v in vars(self.parsed_data).iteritems():
370 for k, v in vars(self.parsed_data).iteritems():
371 exec_str = 'self.config.' + k + '= v'
371 exec_str = 'self.config.' + k + '= v'
372 exec exec_str in locals(), globals()
372 exec exec_str in locals(), globals()
373
373
374
374
@@ -1,459 +1,462 b''
1 # encoding: utf-8
1 # encoding: utf-8
2 """
2 """
3 An application for IPython.
3 An application for IPython.
4
4
5 All top-level applications should use the classes in this module for
5 All top-level applications should use the classes in this module for
6 handling configuration and creating componenets.
6 handling configuration and creating componenets.
7
7
8 The job of an :class:`Application` is to create the master configuration
8 The job of an :class:`Application` is to create the master configuration
9 object and then create the configurable objects, passing the config to them.
9 object and then create the configurable objects, passing the config to them.
10
10
11 Authors:
11 Authors:
12
12
13 * Brian Granger
13 * Brian Granger
14 * Fernando Perez
14 * Fernando Perez
15
15
16 Notes
16 Notes
17 -----
17 -----
18 """
18 """
19
19
20 #-----------------------------------------------------------------------------
20 #-----------------------------------------------------------------------------
21 # Copyright (C) 2008-2009 The IPython Development Team
21 # Copyright (C) 2008-2009 The IPython Development Team
22 #
22 #
23 # Distributed under the terms of the BSD License. The full license is in
23 # Distributed under the terms of the BSD License. The full license is in
24 # the file COPYING, distributed as part of this software.
24 # the file COPYING, distributed as part of this software.
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27 #-----------------------------------------------------------------------------
27 #-----------------------------------------------------------------------------
28 # Imports
28 # Imports
29 #-----------------------------------------------------------------------------
29 #-----------------------------------------------------------------------------
30
30
31 import logging
31 import logging
32 import os
32 import os
33 import sys
33 import sys
34
34
35 from IPython.core import release, crashhandler
35 from IPython.core import release, crashhandler
36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
36 from IPython.utils.path import get_ipython_dir, get_ipython_package_dir
37 from IPython.config.loader import (
37 from IPython.config.loader import (
38 PyFileConfigLoader,
38 PyFileConfigLoader,
39 ArgParseConfigLoader,
39 ArgParseConfigLoader,
40 Config,
40 Config,
41 )
41 )
42
42
43 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
44 # Classes and functions
44 # Classes and functions
45 #-----------------------------------------------------------------------------
45 #-----------------------------------------------------------------------------
46
46
47 class ApplicationError(Exception):
47 class ApplicationError(Exception):
48 pass
48 pass
49
49
50
50
51 class BaseAppConfigLoader(ArgParseConfigLoader):
51 class BaseAppConfigLoader(ArgParseConfigLoader):
52 """Default command line options for IPython based applications."""
52 """Default command line options for IPython based applications."""
53
53
54 def _add_ipython_dir(self, parser):
54 def _add_ipython_dir(self, parser):
55 """Add the --ipython-dir option to the parser."""
55 """Add the --ipython-dir option to the parser."""
56 paa = parser.add_argument
56 paa = parser.add_argument
57 paa('--ipython-dir',
57 paa('--ipython-dir',
58 dest='Global.ipython_dir',type=unicode,
58 dest='Global.ipython_dir',type=unicode,
59 help=
59 help=
60 """Set to override default location of the IPython directory
60 """Set to override default location of the IPython directory
61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
61 IPYTHON_DIR, stored as Global.ipython_dir. This can also be
62 specified through the environment variable IPYTHON_DIR.""",
62 specified through the environment variable IPYTHON_DIR.""",
63 metavar='Global.ipython_dir')
63 metavar='Global.ipython_dir')
64
64
65 def _add_log_level(self, parser):
65 def _add_log_level(self, parser):
66 """Add the --log-level option to the parser."""
66 """Add the --log-level option to the parser."""
67 paa = parser.add_argument
67 paa = parser.add_argument
68 paa('--log-level',
68 paa('--log-level',
69 dest="Global.log_level",type=int,
69 dest="Global.log_level",type=int,
70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
70 help='Set the log level (0,10,20,30,40,50). Default is 30.',
71 metavar='Global.log_level')
71 metavar='Global.log_level')
72
72
73 def _add_version(self, parser):
73 def _add_version(self, parser):
74 """Add the --version option to the parser."""
74 """Add the --version option to the parser."""
75 parser.add_argument('--version', action="version",
75 parser.add_argument('--version', action="version",
76 version=self.version)
76 version=self.version)
77
77
78 def _add_arguments(self):
78 def _add_arguments(self):
79 self._add_ipython_dir(self.parser)
79 self._add_ipython_dir(self.parser)
80 self._add_log_level(self.parser)
80 self._add_log_level(self.parser)
81 self._add_version(self.parser)
81 try: # Old versions of argparse don't have a version action
82 self._add_version(self.parser)
83 except Exception:
84 pass
82
85
83
86
84 class Application(object):
87 class Application(object):
85 """Load a config, construct configurables and set them running.
88 """Load a config, construct configurables and set them running.
86
89
87 The configuration of an application can be done via three different Config
90 The configuration of an application can be done via three different Config
88 objects, which are loaded and ultimately merged into a single one used
91 objects, which are loaded and ultimately merged into a single one used
89 from that point on by the app. These are:
92 from that point on by the app. These are:
90
93
91 1. default_config: internal defaults, implemented in code.
94 1. default_config: internal defaults, implemented in code.
92 2. file_config: read from the filesystem.
95 2. file_config: read from the filesystem.
93 3. command_line_config: read from the system's command line flags.
96 3. command_line_config: read from the system's command line flags.
94
97
95 During initialization, 3 is actually read before 2, since at the
98 During initialization, 3 is actually read before 2, since at the
96 command-line one may override the location of the file to be read. But the
99 command-line one may override the location of the file to be read. But the
97 above is the order in which the merge is made.
100 above is the order in which the merge is made.
98 """
101 """
99
102
100 name = u'ipython'
103 name = u'ipython'
101 description = 'IPython: an enhanced interactive Python shell.'
104 description = 'IPython: an enhanced interactive Python shell.'
102 #: Usage message printed by argparse. If None, auto-generate
105 #: Usage message printed by argparse. If None, auto-generate
103 usage = None
106 usage = None
104 #: The command line config loader. Subclass of ArgParseConfigLoader.
107 #: The command line config loader. Subclass of ArgParseConfigLoader.
105 command_line_loader = BaseAppConfigLoader
108 command_line_loader = BaseAppConfigLoader
106 #: The name of the config file to load, determined at runtime
109 #: The name of the config file to load, determined at runtime
107 config_file_name = None
110 config_file_name = None
108 #: The name of the default config file. Track separately from the actual
111 #: The name of the default config file. Track separately from the actual
109 #: name because some logic happens only if we aren't using the default.
112 #: name because some logic happens only if we aren't using the default.
110 default_config_file_name = u'ipython_config.py'
113 default_config_file_name = u'ipython_config.py'
111 default_log_level = logging.WARN
114 default_log_level = logging.WARN
112 #: Set by --profile option
115 #: Set by --profile option
113 profile_name = None
116 profile_name = None
114 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
117 #: User's ipython directory, typically ~/.ipython or ~/.config/ipython/
115 ipython_dir = None
118 ipython_dir = None
116 #: Internal defaults, implemented in code.
119 #: Internal defaults, implemented in code.
117 default_config = None
120 default_config = None
118 #: Read from the filesystem.
121 #: Read from the filesystem.
119 file_config = None
122 file_config = None
120 #: Read from the system's command line flags.
123 #: Read from the system's command line flags.
121 command_line_config = None
124 command_line_config = None
122 #: The final config that will be passed to the main object.
125 #: The final config that will be passed to the main object.
123 master_config = None
126 master_config = None
124 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
127 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
125 argv = None
128 argv = None
126 #: extra arguments computed by the command-line loader
129 #: extra arguments computed by the command-line loader
127 extra_args = None
130 extra_args = None
128 #: The class to use as the crash handler.
131 #: The class to use as the crash handler.
129 crash_handler_class = crashhandler.CrashHandler
132 crash_handler_class = crashhandler.CrashHandler
130
133
131 # Private attributes
134 # Private attributes
132 _exiting = False
135 _exiting = False
133 _initialized = False
136 _initialized = False
134
137
135 def __init__(self, argv=None):
138 def __init__(self, argv=None):
136 self.argv = sys.argv[1:] if argv is None else argv
139 self.argv = sys.argv[1:] if argv is None else argv
137 self.init_logger()
140 self.init_logger()
138
141
139 def init_logger(self):
142 def init_logger(self):
140 self.log = logging.getLogger(self.__class__.__name__)
143 self.log = logging.getLogger(self.__class__.__name__)
141 # This is used as the default until the command line arguments are read.
144 # This is used as the default until the command line arguments are read.
142 self.log.setLevel(self.default_log_level)
145 self.log.setLevel(self.default_log_level)
143 self._log_handler = logging.StreamHandler()
146 self._log_handler = logging.StreamHandler()
144 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
147 self._log_formatter = logging.Formatter("[%(name)s] %(message)s")
145 self._log_handler.setFormatter(self._log_formatter)
148 self._log_handler.setFormatter(self._log_formatter)
146 self.log.addHandler(self._log_handler)
149 self.log.addHandler(self._log_handler)
147
150
148 def _set_log_level(self, level):
151 def _set_log_level(self, level):
149 self.log.setLevel(level)
152 self.log.setLevel(level)
150
153
151 def _get_log_level(self):
154 def _get_log_level(self):
152 return self.log.level
155 return self.log.level
153
156
154 log_level = property(_get_log_level, _set_log_level)
157 log_level = property(_get_log_level, _set_log_level)
155
158
156 def initialize(self):
159 def initialize(self):
157 """Initialize the application.
160 """Initialize the application.
158
161
159 Loads all configuration information and sets all application state, but
162 Loads all configuration information and sets all application state, but
160 does not start any relevant processing (typically some kind of event
163 does not start any relevant processing (typically some kind of event
161 loop).
164 loop).
162
165
163 Once this method has been called, the application is flagged as
166 Once this method has been called, the application is flagged as
164 initialized and the method becomes a no-op."""
167 initialized and the method becomes a no-op."""
165
168
166 if self._initialized:
169 if self._initialized:
167 return
170 return
168
171
169 # The first part is protected with an 'attempt' wrapper, that will log
172 # The first part is protected with an 'attempt' wrapper, that will log
170 # failures with the basic system traceback machinery. Once our crash
173 # failures with the basic system traceback machinery. Once our crash
171 # handler is in place, we can let any subsequent exception propagate,
174 # handler is in place, we can let any subsequent exception propagate,
172 # as our handler will log it with much better detail than the default.
175 # as our handler will log it with much better detail than the default.
173 self.attempt(self.create_crash_handler)
176 self.attempt(self.create_crash_handler)
174
177
175 # Configuration phase
178 # Configuration phase
176 # Default config (internally hardwired in application code)
179 # Default config (internally hardwired in application code)
177 self.create_default_config()
180 self.create_default_config()
178 self.log_default_config()
181 self.log_default_config()
179 self.set_default_config_log_level()
182 self.set_default_config_log_level()
180
183
181 # Command-line config
184 # Command-line config
182 self.pre_load_command_line_config()
185 self.pre_load_command_line_config()
183 self.load_command_line_config()
186 self.load_command_line_config()
184 self.set_command_line_config_log_level()
187 self.set_command_line_config_log_level()
185 self.post_load_command_line_config()
188 self.post_load_command_line_config()
186 self.log_command_line_config()
189 self.log_command_line_config()
187
190
188 # Find resources needed for filesystem access, using information from
191 # Find resources needed for filesystem access, using information from
189 # the above two
192 # the above two
190 self.find_ipython_dir()
193 self.find_ipython_dir()
191 self.find_resources()
194 self.find_resources()
192 self.find_config_file_name()
195 self.find_config_file_name()
193 self.find_config_file_paths()
196 self.find_config_file_paths()
194
197
195 # File-based config
198 # File-based config
196 self.pre_load_file_config()
199 self.pre_load_file_config()
197 self.load_file_config()
200 self.load_file_config()
198 self.set_file_config_log_level()
201 self.set_file_config_log_level()
199 self.post_load_file_config()
202 self.post_load_file_config()
200 self.log_file_config()
203 self.log_file_config()
201
204
202 # Merge all config objects into a single one the app can then use
205 # Merge all config objects into a single one the app can then use
203 self.merge_configs()
206 self.merge_configs()
204 self.log_master_config()
207 self.log_master_config()
205
208
206 # Construction phase
209 # Construction phase
207 self.pre_construct()
210 self.pre_construct()
208 self.construct()
211 self.construct()
209 self.post_construct()
212 self.post_construct()
210
213
211 # Done, flag as such and
214 # Done, flag as such and
212 self._initialized = True
215 self._initialized = True
213
216
214 def start(self):
217 def start(self):
215 """Start the application."""
218 """Start the application."""
216 self.initialize()
219 self.initialize()
217 self.start_app()
220 self.start_app()
218
221
219 #-------------------------------------------------------------------------
222 #-------------------------------------------------------------------------
220 # Various stages of Application creation
223 # Various stages of Application creation
221 #-------------------------------------------------------------------------
224 #-------------------------------------------------------------------------
222
225
223 def create_crash_handler(self):
226 def create_crash_handler(self):
224 """Create a crash handler, typically setting sys.excepthook to it."""
227 """Create a crash handler, typically setting sys.excepthook to it."""
225 self.crash_handler = self.crash_handler_class(self)
228 self.crash_handler = self.crash_handler_class(self)
226 sys.excepthook = self.crash_handler
229 sys.excepthook = self.crash_handler
227
230
228 def create_default_config(self):
231 def create_default_config(self):
229 """Create defaults that can't be set elsewhere.
232 """Create defaults that can't be set elsewhere.
230
233
231 For the most part, we try to set default in the class attributes
234 For the most part, we try to set default in the class attributes
232 of Configurables. But, defaults the top-level Application (which is
235 of Configurables. But, defaults the top-level Application (which is
233 not a HasTraits or Configurables) are not set in this way. Instead
236 not a HasTraits or Configurables) are not set in this way. Instead
234 we set them here. The Global section is for variables like this that
237 we set them here. The Global section is for variables like this that
235 don't belong to a particular configurable.
238 don't belong to a particular configurable.
236 """
239 """
237 c = Config()
240 c = Config()
238 c.Global.ipython_dir = get_ipython_dir()
241 c.Global.ipython_dir = get_ipython_dir()
239 c.Global.log_level = self.log_level
242 c.Global.log_level = self.log_level
240 self.default_config = c
243 self.default_config = c
241
244
242 def log_default_config(self):
245 def log_default_config(self):
243 self.log.debug('Default config loaded:')
246 self.log.debug('Default config loaded:')
244 self.log.debug(repr(self.default_config))
247 self.log.debug(repr(self.default_config))
245
248
246 def set_default_config_log_level(self):
249 def set_default_config_log_level(self):
247 try:
250 try:
248 self.log_level = self.default_config.Global.log_level
251 self.log_level = self.default_config.Global.log_level
249 except AttributeError:
252 except AttributeError:
250 # Fallback to the default_log_level class attribute
253 # Fallback to the default_log_level class attribute
251 pass
254 pass
252
255
253 def create_command_line_config(self):
256 def create_command_line_config(self):
254 """Create and return a command line config loader."""
257 """Create and return a command line config loader."""
255 return self.command_line_loader(
258 return self.command_line_loader(
256 self.argv,
259 self.argv,
257 description=self.description,
260 description=self.description,
258 version=release.version,
261 version=release.version,
259 usage=self.usage
262 usage=self.usage
260 )
263 )
261
264
262 def pre_load_command_line_config(self):
265 def pre_load_command_line_config(self):
263 """Do actions just before loading the command line config."""
266 """Do actions just before loading the command line config."""
264 pass
267 pass
265
268
266 def load_command_line_config(self):
269 def load_command_line_config(self):
267 """Load the command line config."""
270 """Load the command line config."""
268 loader = self.create_command_line_config()
271 loader = self.create_command_line_config()
269 self.command_line_config = loader.load_config()
272 self.command_line_config = loader.load_config()
270 self.extra_args = loader.get_extra_args()
273 self.extra_args = loader.get_extra_args()
271
274
272 def set_command_line_config_log_level(self):
275 def set_command_line_config_log_level(self):
273 try:
276 try:
274 self.log_level = self.command_line_config.Global.log_level
277 self.log_level = self.command_line_config.Global.log_level
275 except AttributeError:
278 except AttributeError:
276 pass
279 pass
277
280
278 def post_load_command_line_config(self):
281 def post_load_command_line_config(self):
279 """Do actions just after loading the command line config."""
282 """Do actions just after loading the command line config."""
280 pass
283 pass
281
284
282 def log_command_line_config(self):
285 def log_command_line_config(self):
283 self.log.debug("Command line config loaded:")
286 self.log.debug("Command line config loaded:")
284 self.log.debug(repr(self.command_line_config))
287 self.log.debug(repr(self.command_line_config))
285
288
286 def find_ipython_dir(self):
289 def find_ipython_dir(self):
287 """Set the IPython directory.
290 """Set the IPython directory.
288
291
289 This sets ``self.ipython_dir``, but the actual value that is passed to
292 This sets ``self.ipython_dir``, but the actual value that is passed to
290 the application is kept in either ``self.default_config`` or
293 the application is kept in either ``self.default_config`` or
291 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
294 ``self.command_line_config``. This also adds ``self.ipython_dir`` to
292 ``sys.path`` so config files there can be referenced by other config
295 ``sys.path`` so config files there can be referenced by other config
293 files.
296 files.
294 """
297 """
295
298
296 try:
299 try:
297 self.ipython_dir = self.command_line_config.Global.ipython_dir
300 self.ipython_dir = self.command_line_config.Global.ipython_dir
298 except AttributeError:
301 except AttributeError:
299 self.ipython_dir = self.default_config.Global.ipython_dir
302 self.ipython_dir = self.default_config.Global.ipython_dir
300 sys.path.append(os.path.abspath(self.ipython_dir))
303 sys.path.append(os.path.abspath(self.ipython_dir))
301 if not os.path.isdir(self.ipython_dir):
304 if not os.path.isdir(self.ipython_dir):
302 os.makedirs(self.ipython_dir, mode=0777)
305 os.makedirs(self.ipython_dir, mode=0777)
303 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
306 self.log.debug("IPYTHON_DIR set to: %s" % self.ipython_dir)
304
307
305 def find_resources(self):
308 def find_resources(self):
306 """Find other resources that need to be in place.
309 """Find other resources that need to be in place.
307
310
308 Things like cluster directories need to be in place to find the
311 Things like cluster directories need to be in place to find the
309 config file. These happen right after the IPython directory has
312 config file. These happen right after the IPython directory has
310 been set.
313 been set.
311 """
314 """
312 pass
315 pass
313
316
314 def find_config_file_name(self):
317 def find_config_file_name(self):
315 """Find the config file name for this application.
318 """Find the config file name for this application.
316
319
317 This must set ``self.config_file_name`` to the filename of the
320 This must set ``self.config_file_name`` to the filename of the
318 config file to use (just the filename). The search paths for the
321 config file to use (just the filename). The search paths for the
319 config file are set in :meth:`find_config_file_paths` and then passed
322 config file are set in :meth:`find_config_file_paths` and then passed
320 to the config file loader where they are resolved to an absolute path.
323 to the config file loader where they are resolved to an absolute path.
321
324
322 If a profile has been set at the command line, this will resolve it.
325 If a profile has been set at the command line, this will resolve it.
323 """
326 """
324 try:
327 try:
325 self.config_file_name = self.command_line_config.Global.config_file
328 self.config_file_name = self.command_line_config.Global.config_file
326 except AttributeError:
329 except AttributeError:
327 pass
330 pass
328 else:
331 else:
329 return
332 return
330
333
331 try:
334 try:
332 self.profile_name = self.command_line_config.Global.profile
335 self.profile_name = self.command_line_config.Global.profile
333 except AttributeError:
336 except AttributeError:
334 # Just use the default as there is no profile
337 # Just use the default as there is no profile
335 self.config_file_name = self.default_config_file_name
338 self.config_file_name = self.default_config_file_name
336 else:
339 else:
337 # Use the default config file name and profile name if set
340 # Use the default config file name and profile name if set
338 # to determine the used config file name.
341 # to determine the used config file name.
339 name_parts = self.default_config_file_name.split('.')
342 name_parts = self.default_config_file_name.split('.')
340 name_parts.insert(1, u'_' + self.profile_name + u'.')
343 name_parts.insert(1, u'_' + self.profile_name + u'.')
341 self.config_file_name = ''.join(name_parts)
344 self.config_file_name = ''.join(name_parts)
342
345
343 def find_config_file_paths(self):
346 def find_config_file_paths(self):
344 """Set the search paths for resolving the config file.
347 """Set the search paths for resolving the config file.
345
348
346 This must set ``self.config_file_paths`` to a sequence of search
349 This must set ``self.config_file_paths`` to a sequence of search
347 paths to pass to the config file loader.
350 paths to pass to the config file loader.
348 """
351 """
349 # Include our own profiles directory last, so that users can still find
352 # Include our own profiles directory last, so that users can still find
350 # our shipped copies of builtin profiles even if they don't have them
353 # our shipped copies of builtin profiles even if they don't have them
351 # in their local ipython directory.
354 # in their local ipython directory.
352 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
355 prof_dir = os.path.join(get_ipython_package_dir(), 'config', 'profile')
353 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
356 self.config_file_paths = (os.getcwd(), self.ipython_dir, prof_dir)
354
357
355 def pre_load_file_config(self):
358 def pre_load_file_config(self):
356 """Do actions before the config file is loaded."""
359 """Do actions before the config file is loaded."""
357 pass
360 pass
358
361
359 def load_file_config(self):
362 def load_file_config(self):
360 """Load the config file.
363 """Load the config file.
361
364
362 This tries to load the config file from disk. If successful, the
365 This tries to load the config file from disk. If successful, the
363 ``CONFIG_FILE`` config variable is set to the resolved config file
366 ``CONFIG_FILE`` config variable is set to the resolved config file
364 location. If not successful, an empty config is used.
367 location. If not successful, an empty config is used.
365 """
368 """
366 self.log.debug("Attempting to load config file: %s" %
369 self.log.debug("Attempting to load config file: %s" %
367 self.config_file_name)
370 self.config_file_name)
368 loader = PyFileConfigLoader(self.config_file_name,
371 loader = PyFileConfigLoader(self.config_file_name,
369 path=self.config_file_paths)
372 path=self.config_file_paths)
370 try:
373 try:
371 self.file_config = loader.load_config()
374 self.file_config = loader.load_config()
372 self.file_config.Global.config_file = loader.full_filename
375 self.file_config.Global.config_file = loader.full_filename
373 except IOError:
376 except IOError:
374 # Only warn if the default config file was NOT being used.
377 # Only warn if the default config file was NOT being used.
375 if not self.config_file_name==self.default_config_file_name:
378 if not self.config_file_name==self.default_config_file_name:
376 self.log.warn("Config file not found, skipping: %s" %
379 self.log.warn("Config file not found, skipping: %s" %
377 self.config_file_name, exc_info=True)
380 self.config_file_name, exc_info=True)
378 self.file_config = Config()
381 self.file_config = Config()
379 except:
382 except:
380 self.log.warn("Error loading config file: %s" %
383 self.log.warn("Error loading config file: %s" %
381 self.config_file_name, exc_info=True)
384 self.config_file_name, exc_info=True)
382 self.file_config = Config()
385 self.file_config = Config()
383
386
384 def set_file_config_log_level(self):
387 def set_file_config_log_level(self):
385 # We need to keeep self.log_level updated. But we only use the value
388 # We need to keeep self.log_level updated. But we only use the value
386 # of the file_config if a value was not specified at the command
389 # of the file_config if a value was not specified at the command
387 # line, because the command line overrides everything.
390 # line, because the command line overrides everything.
388 if not hasattr(self.command_line_config.Global, 'log_level'):
391 if not hasattr(self.command_line_config.Global, 'log_level'):
389 try:
392 try:
390 self.log_level = self.file_config.Global.log_level
393 self.log_level = self.file_config.Global.log_level
391 except AttributeError:
394 except AttributeError:
392 pass # Use existing value
395 pass # Use existing value
393
396
394 def post_load_file_config(self):
397 def post_load_file_config(self):
395 """Do actions after the config file is loaded."""
398 """Do actions after the config file is loaded."""
396 pass
399 pass
397
400
398 def log_file_config(self):
401 def log_file_config(self):
399 if hasattr(self.file_config.Global, 'config_file'):
402 if hasattr(self.file_config.Global, 'config_file'):
400 self.log.debug("Config file loaded: %s" %
403 self.log.debug("Config file loaded: %s" %
401 self.file_config.Global.config_file)
404 self.file_config.Global.config_file)
402 self.log.debug(repr(self.file_config))
405 self.log.debug(repr(self.file_config))
403
406
404 def merge_configs(self):
407 def merge_configs(self):
405 """Merge the default, command line and file config objects."""
408 """Merge the default, command line and file config objects."""
406 config = Config()
409 config = Config()
407 config._merge(self.default_config)
410 config._merge(self.default_config)
408 config._merge(self.file_config)
411 config._merge(self.file_config)
409 config._merge(self.command_line_config)
412 config._merge(self.command_line_config)
410
413
411 # XXX fperez - propose to Brian we rename master_config to simply
414 # XXX fperez - propose to Brian we rename master_config to simply
412 # config, I think this is going to be heavily used in examples and
415 # config, I think this is going to be heavily used in examples and
413 # application code and the name is shorter/easier to find/remember.
416 # application code and the name is shorter/easier to find/remember.
414 # For now, just alias it...
417 # For now, just alias it...
415 self.master_config = config
418 self.master_config = config
416 self.config = config
419 self.config = config
417
420
418 def log_master_config(self):
421 def log_master_config(self):
419 self.log.debug("Master config created:")
422 self.log.debug("Master config created:")
420 self.log.debug(repr(self.master_config))
423 self.log.debug(repr(self.master_config))
421
424
422 def pre_construct(self):
425 def pre_construct(self):
423 """Do actions after the config has been built, but before construct."""
426 """Do actions after the config has been built, but before construct."""
424 pass
427 pass
425
428
426 def construct(self):
429 def construct(self):
427 """Construct the main objects that make up this app."""
430 """Construct the main objects that make up this app."""
428 self.log.debug("Constructing main objects for application")
431 self.log.debug("Constructing main objects for application")
429
432
430 def post_construct(self):
433 def post_construct(self):
431 """Do actions after construct, but before starting the app."""
434 """Do actions after construct, but before starting the app."""
432 pass
435 pass
433
436
434 def start_app(self):
437 def start_app(self):
435 """Actually start the app."""
438 """Actually start the app."""
436 self.log.debug("Starting application")
439 self.log.debug("Starting application")
437
440
438 #-------------------------------------------------------------------------
441 #-------------------------------------------------------------------------
439 # Utility methods
442 # Utility methods
440 #-------------------------------------------------------------------------
443 #-------------------------------------------------------------------------
441
444
442 def exit(self, exit_status=0):
445 def exit(self, exit_status=0):
443 if self._exiting:
446 if self._exiting:
444 pass
447 pass
445 else:
448 else:
446 self.log.debug("Exiting application: %s" % self.name)
449 self.log.debug("Exiting application: %s" % self.name)
447 self._exiting = True
450 self._exiting = True
448 sys.exit(exit_status)
451 sys.exit(exit_status)
449
452
450 def attempt(self, func):
453 def attempt(self, func):
451 try:
454 try:
452 func()
455 func()
453 except SystemExit:
456 except SystemExit:
454 raise
457 raise
455 except:
458 except:
456 self.log.critical("Aborting application: %s" % self.name,
459 self.log.critical("Aborting application: %s" % self.name,
457 exc_info=True)
460 exc_info=True)
458 self.exit(0)
461 self.exit(0)
459
462
General Comments 0
You need to be logged in to leave comments. Login now