##// 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 299 class ArgParseConfigLoader(CommandLineConfigLoader):
300 300
301 # arguments = [(('-f','--file'),dict(type=str,dest='file'))]
302 arguments = ()
303
304 def __init__(self, argv=None, *args, **kw):
301 def __init__(self, argv=None, arguments=(), *args, **kw):
305 302 """Create a config loader for use with argparse.
306 303
307 With the exception of argv, other args and kwargs arguments here are
308 passed onto the constructor of :class:`argparse.ArgumentParser`.
304 With the exception of ``argv`` and ``arguments``, other args and kwargs
305 arguments here are passed onto the constructor of
306 :class:`argparse.ArgumentParser`.
309 307
310 308 Parameters
311 309 ----------
@@ -313,11 +311,16 b' class ArgParseConfigLoader(CommandLineConfigLoader):'
313 311 argv : optional, list
314 312 If given, used to read command-line arguments from, otherwise
315 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 319 super(CommandLineConfigLoader, self).__init__()
318 320 if argv == None:
319 321 argv = sys.argv[1:]
320 322 self.argv = argv
323 self.arguments = arguments
321 324 self.args = args
322 325 self.kw = kw
323 326
@@ -66,15 +66,13 b' class TestArgParseCL(TestCase):'
66 66
67 67 def test_basic(self):
68 68
69 class MyLoader(ArgParseConfigLoader):
70 arguments = (
69 arguments = (
71 70 (('-f','--foo'), dict(dest='Global.foo', type=str)),
72 71 (('-b',), dict(dest='MyClass.bar', type=int)),
73 72 (('-n',), dict(dest='n', action='store_true')),
74 73 (('Global.bam',), dict(type=str))
75 74 )
76
77 cl = MyLoader()
75 cl = ArgParseConfigLoader(arguments=arguments)
78 76 config = cl.load_config('-f hi -b 10 -n wow'.split())
79 77 self.assertEquals(config.Global.foo, 'hi')
80 78 self.assertEquals(config.MyClass.bar, 10)
@@ -33,7 +33,7 b' import logging'
33 33 import os
34 34 import sys
35 35
36 from IPython.core import release
36 from IPython.core import release, crashhandler
37 37 from IPython.utils.genutils import get_ipython_dir, get_ipython_package_dir
38 38 from IPython.config.loader import (
39 39 PyFileConfigLoader,
@@ -77,6 +77,29 b' class ApplicationError(Exception):'
77 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 103 class Application(object):
81 104 """Load a config, construct components and set them running."""
82 105
@@ -94,11 +117,17 b' class Application(object):'
94 117 ipython_dir = None
95 118 #: A reference to the argv to be used (typically ends up being sys.argv[1:])
96 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 124 # Private attributes
99 125 _exiting = False
100 126 _initialized = False
101 127
128 # Class choices for things that will be instantiated at runtime.
129 _CrashHandler = crashhandler.CrashHandler
130
102 131 def __init__(self, argv=None):
103 132 self.argv = sys.argv[1:] if argv is None else argv
104 133 self.init_logger()
@@ -125,39 +154,49 b' class Application(object):'
125 154
126 155 if self._initialized:
127 156 return
128
129 self.attempt(self.create_default_config)
157
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 164 self.log_default_config()
131 165 self.set_default_config_log_level()
132 self.attempt(self.pre_load_command_line_config)
133 self.attempt(self.load_command_line_config, action='abort')
166 self.pre_load_command_line_config()
167 self.load_command_line_config()
134 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 170 self.log_command_line_config()
137 self.attempt(self.find_ipython_dir)
138 self.attempt(self.find_resources)
139 self.attempt(self.find_config_file_name)
140 self.attempt(self.find_config_file_paths)
141 self.attempt(self.pre_load_file_config)
142 self.attempt(self.load_file_config)
171 self.find_ipython_dir()
172 self.find_resources()
173 self.find_config_file_name()
174 self.find_config_file_paths()
175 self.pre_load_file_config()
176 self.load_file_config()
143 177 self.set_file_config_log_level()
144 self.attempt(self.post_load_file_config)
178 self.post_load_file_config()
145 179 self.log_file_config()
146 self.attempt(self.merge_configs)
180 self.merge_configs()
147 181 self.log_master_config()
148 self.attempt(self.pre_construct)
149 self.attempt(self.construct)
150 self.attempt(self.post_construct)
182 self.pre_construct()
183 self.construct()
184 self.post_construct()
151 185 self._initialized = True
152 186
153 187 def start(self):
154 188 self.initialize()
155 self.attempt(self.start_app)
189 self.start_app()
156 190
157 191 #-------------------------------------------------------------------------
158 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 200 def create_default_config(self):
162 201 """Create defaults that can't be set elsewhere.
163 202
@@ -185,10 +224,9 b' class Application(object):'
185 224
186 225 def create_command_line_config(self):
187 226 """Create and return a command line config loader."""
188 return BaseAppArgParseConfigLoader(self.argv,
189 description=self.description,
190 version=release.version
191 )
227 return ArgParseConfigLoader(self.argv, self.cl_arguments,
228 description=self.description,
229 version=release.version)
192 230
193 231 def pre_load_command_line_config(self):
194 232 """Do actions just before loading the command line config."""
@@ -384,7 +422,10 b' class Application(object):'
384 422 raise
385 423 except:
386 424 if action == 'abort':
425 self.log.critical("Aborting application: %s" % self.name,
426 exc_info=True)
387 427 self.abort()
428 raise
388 429 elif action == 'exit':
389 430 self.exit(0)
390 431
@@ -28,10 +28,8 b' from IPython.core import release'
28 28 from IPython.core import ultratb
29 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 33 """Customizable crash handlers for IPython-based systems.
36 34
37 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,
45 bug_tracker,crash_report_fname,
42 def __init__(self,app, app_name, contact_name=None, contact_email=None,
43 bug_tracker=None, crash_report_fname='CrashReport.txt',
46 44 show_crash_traceback=True):
47 45 """New crash handler.
48 46
49 47 Inputs:
50 48
51 - IP: a running IPython instance, which will be queried at crash time
52 for internal information.
49 - app: a running application instance, which will be queried at crash
50 time for internal information.
53 51
54 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 77 # apply args into instance
80 self.IP = IP # IPython instance
78 self.app = app
81 79 self.app_name = app_name
82 80 self.contact_name = contact_name
83 81 self.contact_email = contact_email
84 82 self.bug_tracker = bug_tracker
85 83 self.crash_report_fname = crash_report_fname
86 84 self.show_crash_traceback = show_crash_traceback
85 self.section_sep = '\n\n'+'*'*75+'\n\n'
87 86
88 87 # Hardcoded defaults, which can be overridden either by subclasses or
89 88 # at runtime for the instance.
@@ -124,7 +123,7 b' $self.bug_tracker'
124 123 #color_scheme = 'Linux' # dbg
125 124
126 125 try:
127 rptdir = self.IP.ipython_dir
126 rptdir = self.app.ipython_dir
128 127 except:
129 128 rptdir = os.getcwd()
130 129 if not os.path.isdir(rptdir):
@@ -134,7 +133,7 b' $self.bug_tracker'
134 133 # properly expanded out in the user message template
135 134 self.crash_report_fname = report_name
136 135 TBhandler = ultratb.VerboseTB(color_scheme=color_scheme,
137 long_header=1)
136 long_header=1)
138 137 traceback = TBhandler.text(etype,evalue,etb,context=31)
139 138
140 139 # print traceback to screen
@@ -159,70 +158,62 b' $self.bug_tracker'
159 158
160 159 def make_report(self,traceback):
161 160 """Return a string containing a crash report."""
162
163 sec_sep = '\n\n'+'*'*75+'\n\n'
164
161 import platform
162
163 sec_sep = self.section_sep
164
165 165 report = []
166 166 rpt_add = report.append
167 167
168 168 rpt_add('*'*75+'\n\n'+'IPython post-mortem report\n\n')
169 rpt_add('IPython version: %s \n\n' % release.version)
170 rpt_add('BZR revision : %s \n\n' % release.revision)
171 rpt_add('Platform info : os.name -> %s, sys.platform -> %s' %
169 rpt_add('IPython version: %s \n' % release.version)
170 rpt_add('BZR revision : %s \n' % release.revision)
171 rpt_add('Platform info : os.name -> %s, sys.platform -> %s\n' %
172 172 (os.name,sys.platform) )
173 rpt_add(sec_sep+'Current user configuration structure:\n\n')
174 rpt_add(pformat(self.IP.dict()))
175 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
173 rpt_add(' : %s\n' % platform.platform())
174 rpt_add('Python info : %s\n' % sys.version)
175
176 176 try:
177 rpt_add(sec_sep+"History of session input:")
178 for line in self.IP.user_ns['_ih']:
179 rpt_add(line)
180 rpt_add('\n*** Last line of input (may not be in above history):\n')
181 rpt_add(self.IP._last_input_line+'\n')
177 config = pformat(self.app.config)
178 rpt_add(sec_sep+'Current user configuration structure:\n\n')
179 rpt_add(config)
182 180 except:
183 181 pass
182 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
184 183
185 184 return ''.join(report)
186 185
186
187 187 class IPythonCrashHandler(CrashHandler):
188 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 192 # Set here which of the IPython authors should be listed as contact
193 193 AUTHOR_CONTACT = 'Fernando'
194 194
195 195 # Set argument defaults
196 app_name = 'IPython'
197 196 bug_tracker = 'https://bugs.launchpad.net/ipython/+filebug'
198 197 contact_name,contact_email = release.authors[AUTHOR_CONTACT][:2]
199 198 crash_report_fname = 'IPython_crash_report.txt'
200 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 201 bug_tracker,crash_report_fname)
203 202
204 203 def make_report(self,traceback):
205 204 """Return a string containing a crash report."""
206 205
207 sec_sep = '\n\n'+'*'*75+'\n\n'
208
209 report = []
206 sec_sep = self.section_sep
207 # Start with parent report
208 report = [super(IPythonCrashHandler, self).make_report(traceback)]
209 # Add interactive-specific info we may have
210 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 211 try:
221 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 214 rpt_add(line)
224 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 217 except:
227 218 pass
228 219
@@ -28,6 +28,7 b' import logging'
28 28 import os
29 29 import sys
30 30
31 from IPython.core import crashhandler
31 32 from IPython.core import release
32 33 from IPython.core.application import Application, BaseAppArgParseConfigLoader
33 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 284 default_config_file_name = u'ipython_config.py'
289 285
290
291 286 class IPythonApp(Application):
292 287 name = u'ipython'
293 288 description = 'IPython: an enhanced interactive Python shell.'
294 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 296 def __init__(self, argv=None, **shell_params):
297 297 """Create a new IPythonApp.
298 298
@@ -309,6 +309,7 b' class IPythonApp(Application):'
309 309 super(IPythonApp, self).__init__(argv)
310 310 self.shell_params = shell_params
311 311
312
312 313 def create_default_config(self):
313 314 super(IPythonApp, self).create_default_config()
314 315 # Eliminate multiple lookups
@@ -340,13 +341,6 b' class IPythonApp(Application):'
340 341 Global.wthread = False
341 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 344 def load_file_config(self):
351 345 if hasattr(self.command_line_config.Global, 'quick'):
352 346 if self.command_line_config.Global.quick:
@@ -1167,37 +1167,14 b' class InteractiveShell(Component, Magic):'
1167 1167 color_scheme='NoColor',
1168 1168 tb_offset = 1)
1169 1169
1170 # IPython itself shouldn't crash. This will produce a detailed
1171 # post-mortem if it does. But we only install the crash handler for
1172 # non-threaded shells, the threaded ones use a normal verbose reporter
1173 # and lose the crash handler. This is because exceptions in the main
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)
1170 # The instance will store a pointer to the system-wide exception hook,
1171 # so that runtime code (such as magics) can access it. This is because
1172 # during the read-eval loop, it may get temporarily overwritten.
1173 self.sys_excepthook = sys.excepthook
1182 1174
1183 1175 # and add any custom exception handlers the user may have specified
1184 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 1178 def set_custom_exc(self,exc_tuple,handler):
1202 1179 """set_custom_exc(exc_tuple,handler)
1203 1180
@@ -1880,7 +1857,7 b' class InteractiveShell(Component, Magic):'
1880 1857 if (self.SyntaxTB.last_syntax_error and
1881 1858 self.autoedit_syntax):
1882 1859 self.edit_syntax_error()
1883
1860
1884 1861 # We are off again...
1885 1862 __builtin__.__dict__['__IPYTHON__active'] -= 1
1886 1863
@@ -77,12 +77,6 b' class PrefilterFrontEnd(LineFrontEndBase):'
77 77 """
78 78 if argv is None:
79 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 81 LineFrontEndBase.__init__(self, *args, **kwargs)
88 82 self.shell.output_trap = RedirectorOutputTrap(
@@ -88,9 +88,6 b' def isolate_ipython0(func):'
88 88 del user_ns[k]
89 89 for k in new_globals:
90 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 91 return out
95 92
96 93 my_func.__name__ = func.__name__
General Comments 0
You need to be logged in to leave comments. Login now