##// END OF EJS Templates
Move crash handling to the application level and simplify class structure....
Fernando Perez -
Show More
@@ -298,14 +298,12 b' NoConfigDefault = NoConfigDefault()'
298
298
299 class ArgParseConfigLoader(CommandLineConfigLoader):
299 class ArgParseConfigLoader(CommandLineConfigLoader):
300
300
301 # arguments = [(('-f','--file'),dict(type=str,dest='file'))]
301 def __init__(self, argv=None, arguments=(), *args, **kw):
302 arguments = ()
303
304 def __init__(self, argv=None, *args, **kw):
305 """Create a config loader for use with argparse.
302 """Create a config loader for use with argparse.
306
303
307 With the exception of argv, other args and kwargs arguments here are
304 With the exception of ``argv`` and ``arguments``, other args and kwargs
308 passed onto the constructor of :class:`argparse.ArgumentParser`.
305 arguments here are passed onto the constructor of
306 :class:`argparse.ArgumentParser`.
309
307
310 Parameters
308 Parameters
311 ----------
309 ----------
@@ -313,11 +311,16 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
313 argv : optional, list
311 argv : optional, list
314 If given, used to read command-line arguments from, otherwise
312 If given, used to read command-line arguments from, otherwise
315 sys.argv[1:] is used.
313 sys.argv[1:] is used.
314
315 arguments : optional, tuple
316 Description of valid command-line arguments, to be called in sequence
317 with parser.add_argument() to configure the parser.
316 """
318 """
317 super(CommandLineConfigLoader, self).__init__()
319 super(CommandLineConfigLoader, self).__init__()
318 if argv == None:
320 if argv == None:
319 argv = sys.argv[1:]
321 argv = sys.argv[1:]
320 self.argv = argv
322 self.argv = argv
323 self.arguments = arguments
321 self.args = args
324 self.args = args
322 self.kw = kw
325 self.kw = kw
323
326
@@ -66,15 +66,13 b' class TestArgParseCL(TestCase):'
66
66
67 def test_basic(self):
67 def test_basic(self):
68
68
69 class MyLoader(ArgParseConfigLoader):
69 arguments = (
70 arguments = (
71 (('-f','--foo'), dict(dest='Global.foo', type=str)),
70 (('-f','--foo'), dict(dest='Global.foo', type=str)),
72 (('-b',), dict(dest='MyClass.bar', type=int)),
71 (('-b',), dict(dest='MyClass.bar', type=int)),
73 (('-n',), dict(dest='n', action='store_true')),
72 (('-n',), dict(dest='n', action='store_true')),
74 (('Global.bam',), dict(type=str))
73 (('Global.bam',), dict(type=str))
75 )
74 )
76
75 cl = ArgParseConfigLoader(arguments=arguments)
77 cl = MyLoader()
78 config = cl.load_config('-f hi -b 10 -n wow'.split())
76 config = cl.load_config('-f hi -b 10 -n wow'.split())
79 self.assertEquals(config.Global.foo, 'hi')
77 self.assertEquals(config.Global.foo, 'hi')
80 self.assertEquals(config.MyClass.bar, 10)
78 self.assertEquals(config.MyClass.bar, 10)
@@ -33,7 +33,7 b' import logging'
33 import os
33 import os
34 import sys
34 import sys
35
35
36 from IPython.core import release
36 from IPython.core import release, crashhandler
37 from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
37 from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
38 from IPython.config.loader import (
38 from IPython.config.loader import (
39 PyFileConfigLoader,
39 PyFileConfigLoader,
@@ -77,6 +77,29 b' class ApplicationError(Exception):'
77 pass
77 pass
78
78
79
79
80 app_cl_args = (
81 (('--ipython-dir', ), dict(
82 dest='Global.ipython_dir',type=unicode,
83 help='Set to override default location of Global.ipython_dir.',
84 default=NoConfigDefault,
85 metavar='Global.ipython_dir') ),
86 (('-p', '--profile',), dict(
87 dest='Global.profile',type=unicode,
88 help='The string name of the ipython profile to be used.',
89 default=NoConfigDefault,
90 metavar='Global.profile') ),
91 (('--log-level',), dict(
92 dest="Global.log_level",type=int,
93 help='Set the log level (0,10,20,30,40,50). Default is 30.',
94 default=NoConfigDefault,
95 metavar='Global.log_level')),
96 (('--config-file',), dict(
97 dest='Global.config_file',type=unicode,
98 help='Set the config file name to override default.',
99 default=NoConfigDefault,
100 metavar='Global.config_file')),
101 )
102
80 class Application(object):
103 class Application(object):
81 """Load a config, construct components and set them running."""
104 """Load a config, construct components and set them running."""
82
105
@@ -94,11 +117,17 b' class Application(object):'
94 ipython_dir = None
117 ipython_dir = None
95 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
96 argv = None
119 argv = None
120 #: Default command line arguments. Subclasses should create a new tuple
121 #: that *includes* these.
122 cl_arguments = app_cl_args
97
123
98 # Private attributes
124 # Private attributes
99 _exiting = False
125 _exiting = False
100 _initialized = False
126 _initialized = False
101
127
128 # Class choices for things that will be instantiated at runtime.
129 _CrashHandler = crashhandler.CrashHandler
130
102 def __init__(self, argv=None):
131 def __init__(self, argv=None):
103 self.argv = sys.argv[1:] if argv is None else argv
132 self.argv = sys.argv[1:] if argv is None else argv
104 self.init_logger()
133 self.init_logger()
@@ -125,39 +154,49 b' class Application(object):'
125
154
126 if self._initialized:
155 if self._initialized:
127 return
156 return
128
157
129 self.attempt(self.create_default_config)
158 # The first part is protected with an 'attempt' wrapper, that will log
159 # failures with the basic system traceback machinery. Once our crash
160 # handler is in place, we can let any subsequent exception propagate,
161 # as our handler will log it with much better detail than the default.
162 self.attempt(self.create_crash_handler)
163 self.create_default_config()
130 self.log_default_config()
164 self.log_default_config()
131 self.set_default_config_log_level()
165 self.set_default_config_log_level()
132 self.attempt(self.pre_load_command_line_config)
166 self.pre_load_command_line_config()
133 self.attempt(self.load_command_line_config, action='abort')
167 self.load_command_line_config()
134 self.set_command_line_config_log_level()
168 self.set_command_line_config_log_level()
135 self.attempt(self.post_load_command_line_config)
169 self.post_load_command_line_config()
136 self.log_command_line_config()
170 self.log_command_line_config()
137 self.attempt(self.find_ipython_dir)
171 self.find_ipython_dir()
138 self.attempt(self.find_resources)
172 self.find_resources()
139 self.attempt(self.find_config_file_name)
173 self.find_config_file_name()
140 self.attempt(self.find_config_file_paths)
174 self.find_config_file_paths()
141 self.attempt(self.pre_load_file_config)
175 self.pre_load_file_config()
142 self.attempt(self.load_file_config)
176 self.load_file_config()
143 self.set_file_config_log_level()
177 self.set_file_config_log_level()
144 self.attempt(self.post_load_file_config)
178 self.post_load_file_config()
145 self.log_file_config()
179 self.log_file_config()
146 self.attempt(self.merge_configs)
180 self.merge_configs()
147 self.log_master_config()
181 self.log_master_config()
148 self.attempt(self.pre_construct)
182 self.pre_construct()
149 self.attempt(self.construct)
183 self.construct()
150 self.attempt(self.post_construct)
184 self.post_construct()
151 self._initialized = True
185 self._initialized = True
152
186
153 def start(self):
187 def start(self):
154 self.initialize()
188 self.initialize()
155 self.attempt(self.start_app)
189 self.start_app()
156
190
157 #-------------------------------------------------------------------------
191 #-------------------------------------------------------------------------
158 # Various stages of Application creation
192 # Various stages of Application creation
159 #-------------------------------------------------------------------------
193 #-------------------------------------------------------------------------
160
194
195 def create_crash_handler(self):
196 """Create a crash handler, typically setting sys.excepthook to it."""
197 self.crash_handler = self._CrashHandler(self, self.name)
198 sys.excepthook = self.crash_handler
199
161 def create_default_config(self):
200 def create_default_config(self):
162 """Create defaults that can't be set elsewhere.
201 """Create defaults that can't be set elsewhere.
163
202
@@ -185,10 +224,9 b' class Application(object):'
185
224
186 def create_command_line_config(self):
225 def create_command_line_config(self):
187 """Create and return a command line config loader."""
226 """Create and return a command line config loader."""
188 return BaseAppArgParseConfigLoader(self.argv,
227 return ArgParseConfigLoader(self.argv, self.cl_arguments,
189 description=self.description,
228 description=self.description,
190 version=release.version
229 version=release.version)
191 )
192
230
193 def pre_load_command_line_config(self):
231 def pre_load_command_line_config(self):
194 """Do actions just before loading the command line config."""
232 """Do actions just before loading the command line config."""
@@ -384,7 +422,10 b' class Application(object):'
384 raise
422 raise
385 except:
423 except:
386 if action == 'abort':
424 if action == 'abort':
425 self.log.critical("Aborting application: %s" % self.name,
426 exc_info=True)
387 self.abort()
427 self.abort()
428 raise
388 elif action == 'exit':
429 elif action == 'exit':
389 self.exit(0)
430 self.exit(0)
390
431
@@ -28,10 +28,8 b' from IPython.core import release'
28 from IPython.core import ultratb
28 from IPython.core import ultratb
29 from IPython.external.Itpl import itpl
29 from IPython.external.Itpl import itpl
30
30
31 from IPython.utils.genutils import *
32
33 #****************************************************************************
31 #****************************************************************************
34 class CrashHandler:
32 class CrashHandler(object):
35 """Customizable crash handlers for IPython-based systems.
33 """Customizable crash handlers for IPython-based systems.
36
34
37 Instances of this class provide a __call__ method which can be used as a
35 Instances of this class provide a __call__ method which can be used as a
@@ -41,15 +39,15 b' class CrashHandler:'
41
39
42 """
40 """
43
41
44 def __init__(self,IP,app_name,contact_name,contact_email,
42 def __init__(self,app, app_name, contact_name=None, contact_email=None,
45 bug_tracker,crash_report_fname,
43 bug_tracker=None, crash_report_fname='CrashReport.txt',
46 show_crash_traceback=True):
44 show_crash_traceback=True):
47 """New crash handler.
45 """New crash handler.
48
46
49 Inputs:
47 Inputs:
50
48
51 - IP: a running IPython instance, which will be queried at crash time
49 - app: a running application instance, which will be queried at crash
52 for internal information.
50 time for internal information.
53
51
54 - app_name: a string containing the name of your application.
52 - app_name: a string containing the name of your application.
55
53
@@ -77,13 +75,14 b' class CrashHandler:'
77 """
75 """
78
76
79 # apply args into instance
77 # apply args into instance
80 self.IP = IP # IPython instance
78 self.app = app
81 self.app_name = app_name
79 self.app_name = app_name
82 self.contact_name = contact_name
80 self.contact_name = contact_name
83 self.contact_email = contact_email
81 self.contact_email = contact_email
84 self.bug_tracker = bug_tracker
82 self.bug_tracker = bug_tracker
85 self.crash_report_fname = crash_report_fname
83 self.crash_report_fname = crash_report_fname
86 self.show_crash_traceback = show_crash_traceback
84 self.show_crash_traceback = show_crash_traceback
85 self.section_sep = '\n\n'+'*'*75+'\n\n'
87
86
88 # Hardcoded defaults, which can be overridden either by subclasses or
87 # Hardcoded defaults, which can be overridden either by subclasses or
89 # at runtime for the instance.
88 # at runtime for the instance.
@@ -124,7 +123,7 b' $self.bug_tracker'
124 #color_scheme = 'Linux' # dbg
123 #color_scheme = 'Linux' # dbg
125
124
126 try:
125 try:
127 rptdir = self.IP.ipython_dir
126 rptdir = self.app.ipython_dir
128 except:
127 except:
129 rptdir = os.getcwd()
128 rptdir = os.getcwd()
130 if not os.path.isdir(rptdir):
129 if not os.path.isdir(rptdir):
@@ -134,7 +133,7 b' $self.bug_tracker'
134 # properly expanded out in the user message template
133 # properly expanded out in the user message template
135 self.crash_report_fname = report_name
134 self.crash_report_fname = report_name
136 TBhandler = ultratb.VerboseTB(color_scheme=color_scheme,
135 TBhandler = ultratb.VerboseTB(color_scheme=color_scheme,
137 long_header=1)
136 long_header=1)
138 traceback = TBhandler.text(etype,evalue,etb,context=31)
137 traceback = TBhandler.text(etype,evalue,etb,context=31)
139
138
140 # print traceback to screen
139 # print traceback to screen
@@ -159,70 +158,62 b' $self.bug_tracker'
159
158
160 def make_report(self,traceback):
159 def make_report(self,traceback):
161 """Return a string containing a crash report."""
160 """Return a string containing a crash report."""
162
161 import platform
163 sec_sep = '\n\n'+'*'*75+'\n\n'
162
164
163 sec_sep = self.section_sep
164
165 report = []
165 report = []
166 rpt_add = report.append
166 rpt_add = report.append
167
167
168 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
168 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
169 rpt_add('IPython version: %s \n\n' % release.version)
169 rpt_add('IPython version: %s \n' % release.version)
170 rpt_add('BZR revision : %s \n\n' % release.revision)
170 rpt_add('BZR revision : %s \n' % release.revision)
171 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
171 rpt_add('Platform info : os.name -> %s, sys.platform -> %s\n' %
172 (os.name,sys.platform) )
172 (os.name,sys.platform) )
173 rpt_add(sec_sep+'Current user configuration structure:\n\n')
173 rpt_add(' : %s\n' % platform.platform())
174 rpt_add(pformat(self.IP.dict()))
174 rpt_add('Python info : %s\n' % sys.version)
175 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
175
176 try:
176 try:
177 rpt_add(sec_sep+"History of session input:")
177 config = pformat(self.app.config)
178 for line in self.IP.user_ns['_ih']:
178 rpt_add(sec_sep+'Current user configuration structure:\n\n')
179 rpt_add(line)
179 rpt_add(config)
180 rpt_add('\n*** Last line of input (may not be in above history):\n')
181 rpt_add(self.IP._last_input_line+'\n')
182 except:
180 except:
183 pass
181 pass
182 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
184
183
185 return ''.join(report)
184 return ''.join(report)
186
185
186
187 class IPythonCrashHandler(CrashHandler):
187 class IPythonCrashHandler(CrashHandler):
188 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
188 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
189
189
190 def __init__(self,IP):
190 def __init__(self, app, app_name='IPython'):
191
191
192 # Set here which of the IPython authors should be listed as contact
192 # Set here which of the IPython authors should be listed as contact
193 AUTHOR_CONTACT = 'Fernando'
193 AUTHOR_CONTACT = 'Fernando'
194
194
195 # Set argument defaults
195 # Set argument defaults
196 app_name = 'IPython'
197 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
196 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
198 contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2]
197 contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2]
199 crash_report_fname = 'IPython_crash_report.txt'
198 crash_report_fname = 'IPython_crash_report.txt'
200 # Call parent constructor
199 # Call parent constructor
201 CrashHandler.__init__(self,IP,app_name,contact_name,contact_email,
200 CrashHandler.__init__(self,app,app_name,contact_name,contact_email,
202 bug_tracker,crash_report_fname)
201 bug_tracker,crash_report_fname)
203
202
204 def make_report(self,traceback):
203 def make_report(self,traceback):
205 """Return a string containing a crash report."""
204 """Return a string containing a crash report."""
206
205
207 sec_sep = '\n\n'+'*'*75+'\n\n'
206 sec_sep = self.section_sep
208
207 # Start with parent report
209 report = []
208 report = [super(IPythonCrashHandler, self).make_report(traceback)]
209 # Add interactive-specific info we may have
210 rpt_add = report.append
210 rpt_add = report.append
211
212 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
213 rpt_add('IPython version: %s \n\n' % release.version)
214 rpt_add('BZR revision : %s \n\n' % release.revision)
215 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
216 (os.name,sys.platform) )
217 rpt_add(sec_sep+'Current user configuration structure:\n\n')
218 # rpt_add(pformat(self.IP.dict()))
219 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
220 try:
211 try:
221 rpt_add(sec_sep+"History of session input:")
212 rpt_add(sec_sep+"History of session input:")
222 for line in self.IP.user_ns['_ih']:
213 for line in self.app.shell.user_ns['_ih']:
223 rpt_add(line)
214 rpt_add(line)
224 rpt_add('\n*** Last line of input (may not be in above history):\n')
215 rpt_add('\n*** Last line of input (may not be in above history):\n')
225 rpt_add(self.IP._last_input_line+'\n')
216 rpt_add(self.app.shell._last_input_line+'\n')
226 except:
217 except:
227 pass
218 pass
228
219
@@ -28,6 +28,7 b' import logging'
28 import os
28 import os
29 import sys
29 import sys
30
30
31 from IPython.core import crashhandler
31 from IPython.core import release
32 from IPython.core import release
32 from IPython.core.application import Application, BaseAppArgParseConfigLoader
33 from IPython.core.application import Application, BaseAppArgParseConfigLoader
33 from IPython.core.error import UsageError
34 from IPython.core.error import UsageError
@@ -280,19 +281,18 b' cl_args = ('
280 )
281 )
281
282
282
283
283 class IPythonAppCLConfigLoader(BaseAppArgParseConfigLoader):
284
285 arguments = cl_args
286
287
288 default_config_file_name = u'ipython_config.py'
284 default_config_file_name = u'ipython_config.py'
289
285
290
291 class IPythonApp(Application):
286 class IPythonApp(Application):
292 name = u'ipython'
287 name = u'ipython'
293 description = 'IPython: an enhanced interactive Python shell.'
288 description = 'IPython: an enhanced interactive Python shell.'
294 config_file_name = default_config_file_name
289 config_file_name = default_config_file_name
295
290
291 cl_arguments = Application.cl_arguments + cl_args
292
293 # Private and configuration attributes
294 _CrashHandler = crashhandler.IPythonCrashHandler
295
296 def __init__(self, argv=None, **shell_params):
296 def __init__(self, argv=None, **shell_params):
297 """Create a new IPythonApp.
297 """Create a new IPythonApp.
298
298
@@ -309,6 +309,7 b' class IPythonApp(Application):'
309 super(IPythonApp, self).__init__(argv)
309 super(IPythonApp, self).__init__(argv)
310 self.shell_params = shell_params
310 self.shell_params = shell_params
311
311
312
312 def create_default_config(self):
313 def create_default_config(self):
313 super(IPythonApp, self).create_default_config()
314 super(IPythonApp, self).create_default_config()
314 # Eliminate multiple lookups
315 # Eliminate multiple lookups
@@ -340,13 +341,6 b' class IPythonApp(Application):'
340 Global.wthread = False
341 Global.wthread = False
341 Global.gthread = False
342 Global.gthread = False
342
343
343 def create_command_line_config(self):
344 """Create and return a command line config loader."""
345 return IPythonAppCLConfigLoader(self.argv,
346 description=self.description,
347 version=release.version
348 )
349
350 def load_file_config(self):
344 def load_file_config(self):
351 if hasattr(self.command_line_config.Global, 'quick'):
345 if hasattr(self.command_line_config.Global, 'quick'):
352 if self.command_line_config.Global.quick:
346 if self.command_line_config.Global.quick:
@@ -1167,37 +1167,14 b' class InteractiveShell(Component, Magic):'
1167 color_scheme='NoColor',
1167 color_scheme='NoColor',
1168 tb_offset = 1)
1168 tb_offset = 1)
1169
1169
1170 # IPython itself shouldn't crash. This will produce a detailed
1170 # The instance will store a pointer to the system-wide exception hook,
1171 # post-mortem if it does. But we only install the crash handler for
1171 # so that runtime code (such as magics) can access it. This is because
1172 # non-threaded shells, the threaded ones use a normal verbose reporter
1172 # during the read-eval loop, it may get temporarily overwritten.
1173 # and lose the crash handler. This is because exceptions in the main
1173 self.sys_excepthook = sys.excepthook
1174 # thread (such as in GUI code) propagate directly to sys.excepthook,
1175 # and there's no point in printing crash dumps for every user exception.
1176 if self.isthreaded:
1177 ipCrashHandler = ultratb.FormattedTB()
1178 else:
1179 from IPython.core import crashhandler
1180 ipCrashHandler = crashhandler.IPythonCrashHandler(self)
1181 self.set_crash_handler(ipCrashHandler)
1182
1174
1183 # and add any custom exception handlers the user may have specified
1175 # and add any custom exception handlers the user may have specified
1184 self.set_custom_exc(*custom_exceptions)
1176 self.set_custom_exc(*custom_exceptions)
1185
1177
1186 def set_crash_handler(self, crashHandler):
1187 """Set the IPython crash handler.
1188
1189 This must be a callable with a signature suitable for use as
1190 sys.excepthook."""
1191
1192 # Install the given crash handler as the Python exception hook
1193 sys.excepthook = crashHandler
1194
1195 # The instance will store a pointer to this, so that runtime code
1196 # (such as magics) can access it. This is because during the
1197 # read-eval loop, it gets temporarily overwritten (to deal with GUI
1198 # frameworks).
1199 self.sys_excepthook = sys.excepthook
1200
1201 def set_custom_exc(self,exc_tuple,handler):
1178 def set_custom_exc(self,exc_tuple,handler):
1202 """set_custom_exc(exc_tuple,handler)
1179 """set_custom_exc(exc_tuple,handler)
1203
1180
@@ -1880,7 +1857,7 b' class InteractiveShell(Component, Magic):'
1880 if (self.SyntaxTB.last_syntax_error and
1857 if (self.SyntaxTB.last_syntax_error and
1881 self.autoedit_syntax):
1858 self.autoedit_syntax):
1882 self.edit_syntax_error()
1859 self.edit_syntax_error()
1883
1860
1884 # We are off again...
1861 # We are off again...
1885 __builtin__.__dict__['__IPYTHON__active'] -= 1
1862 __builtin__.__dict__['__IPYTHON__active'] -= 1
1886
1863
@@ -77,12 +77,6 b' class PrefilterFrontEnd(LineFrontEndBase):'
77 """
77 """
78 if argv is None:
78 if argv is None:
79 argv = ['--no-banner']
79 argv = ['--no-banner']
80 # This is a hack to avoid the IPython exception hook to trigger
81 # on exceptions (https://bugs.launchpad.net/bugs/337105)
82 # XXX: This is horrible: module-leve monkey patching -> side
83 # effects.
84 from IPython.core import iplib
85 iplib.InteractiveShell.isthreaded = True
86
80
87 LineFrontEndBase.__init__(self, *args, **kwargs)
81 LineFrontEndBase.__init__(self, *args, **kwargs)
88 self.shell.output_trap = RedirectorOutputTrap(
82 self.shell.output_trap = RedirectorOutputTrap(
@@ -88,9 +88,6 b' def isolate_ipython0(func):'
88 del user_ns[k]
88 del user_ns[k]
89 for k in new_globals:
89 for k in new_globals:
90 del user_global_ns[k]
90 del user_global_ns[k]
91 # Undo the hack at creation of PrefilterFrontEnd
92 from IPython.core import iplib
93 iplib.InteractiveShell.isthreaded = False
94 return out
91 return out
95
92
96 my_func.__name__ = func.__name__
93 my_func.__name__ = func.__name__
General Comments 0
You need to be logged in to leave comments. Login now