##// END OF EJS Templates
Use str.format instead of Itpl in crash handler.
Thomas Kluyver -
Show More
@@ -1,179 +1,180 b''
1 1 # encoding: utf-8
2 2 """sys.excepthook for IPython itself, leaves a detailed report on disk.
3 3
4 4 Authors:
5 5
6 6 * Fernando Perez
7 7 * Brian E. Granger
8 8 """
9 9
10 10 #-----------------------------------------------------------------------------
11 11 # Copyright (C) 2001-2007 Fernando Perez. <fperez@colorado.edu>
12 12 # Copyright (C) 2008-2010 The IPython Development Team
13 13 #
14 14 # Distributed under the terms of the BSD License. The full license is in
15 15 # the file COPYING, distributed as part of this software.
16 16 #-----------------------------------------------------------------------------
17 17
18 18 #-----------------------------------------------------------------------------
19 19 # Imports
20 20 #-----------------------------------------------------------------------------
21 21
22 22 import os
23 23 import sys
24 24 from pprint import pformat
25 25
26 26 from IPython.core import ultratb
27 from IPython.external.Itpl import itpl
28 27 from IPython.utils.sysinfo import sys_info
29 28
30 29 #-----------------------------------------------------------------------------
31 30 # Code
32 31 #-----------------------------------------------------------------------------
33 32
34 33 # Template for the user message.
35 34 _default_message_template = """\
36 Oops, $self.app_name crashed. We do our best to make it stable, but...
35 Oops, {app_name} crashed. We do our best to make it stable, but...
37 36
38 37 A crash report was automatically generated with the following information:
39 38 - A verbatim copy of the crash traceback.
40 39 - A copy of your input history during this session.
41 40 - Data on your current $self.app_name configuration.
42 41
43 42 It was left in the file named:
44 \t'$self.crash_report_fname'
43 \t'{crash_report_fname}'
45 44 If you can email this file to the developers, the information in it will help
46 45 them in understanding and correcting the problem.
47 46
48 You can mail it to: $self.contact_name at $self.contact_email
49 with the subject '$self.app_name Crash Report'.
47 You can mail it to: $self.contact_name at {contact_email}
48 with the subject '{app_name} Crash Report'.
50 49
51 50 If you want to do it now, the following command will work (under Unix):
52 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
51 mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
53 52
54 53 To ensure accurate tracking of this issue, please file a report about it at:
55 $self.bug_tracker
54 {bug_tracker}
56 55 """
57 56
58 57
59 58 class CrashHandler(object):
60 59 """Customizable crash handlers for IPython applications.
61 60
62 61 Instances of this class provide a :meth:`__call__` method which can be
63 62 used as a ``sys.excepthook``. The :meth:`__call__` signature is::
64 63
65 64 def __call__(self, etype, evalue, etb)
66 65 """
67 66
68 67 message_template = _default_message_template
68 section_sep = '\n\n'+'*'*75+'\n\n'
69 69
70 70 def __init__(self, app, contact_name=None, contact_email=None,
71 71 bug_tracker=None, show_crash_traceback=True, call_pdb=False):
72 72 """Create a new crash handler
73 73
74 74 Parameters
75 75 ----------
76 76 app : Application
77 77 A running :class:`Application` instance, which will be queried at
78 78 crash time for internal information.
79 79
80 80 contact_name : str
81 81 A string with the name of the person to contact.
82 82
83 83 contact_email : str
84 84 A string with the email address of the contact.
85 85
86 86 bug_tracker : str
87 87 A string with the URL for your project's bug tracker.
88 88
89 89 show_crash_traceback : bool
90 90 If false, don't print the crash traceback on stderr, only generate
91 91 the on-disk report
92 92
93 93 Non-argument instance attributes:
94 94
95 95 These instances contain some non-argument attributes which allow for
96 96 further customization of the crash handler's behavior. Please see the
97 97 source for further details.
98 98 """
99 self.crash_report_fname = "Crash_report_%s.txt" % app.name
99 100 self.app = app
100 self.app_name = self.app.name
101 self.contact_name = contact_name
102 self.contact_email = contact_email
103 self.bug_tracker = bug_tracker
104 self.crash_report_fname = "Crash_report_%s.txt" % self.app_name
105 self.show_crash_traceback = show_crash_traceback
106 self.section_sep = '\n\n'+'*'*75+'\n\n'
107 101 self.call_pdb = call_pdb
108 102 #self.call_pdb = True # dbg
103 self.show_crash_traceback = show_crash_traceback
104 self.info = dict(app_name = app.name,
105 contact_name = contact_name,
106 contact_email = contact_email,
107 bug_tracker = bug_tracker,
108 crash_report_fname = self.crash_report_fname)
109
109 110
110 111 def __call__(self, etype, evalue, etb):
111 112 """Handle an exception, call for compatible with sys.excepthook"""
112 113
113 114 # Report tracebacks shouldn't use color in general (safer for users)
114 115 color_scheme = 'NoColor'
115 116
116 117 # Use this ONLY for developer debugging (keep commented out for release)
117 118 #color_scheme = 'Linux' # dbg
118 119 try:
119 120 rptdir = self.app.ipython_dir
120 121 except:
121 122 rptdir = os.getcwd()
122 123 if rptdir is None or not os.path.isdir(rptdir):
123 124 rptdir = os.getcwd()
124 125 report_name = os.path.join(rptdir,self.crash_report_fname)
125 126 # write the report filename into the instance dict so it can get
126 127 # properly expanded out in the user message template
127 128 self.crash_report_fname = report_name
128 129 TBhandler = ultratb.VerboseTB(
129 130 color_scheme=color_scheme,
130 131 long_header=1,
131 132 call_pdb=self.call_pdb,
132 133 )
133 134 if self.call_pdb:
134 135 TBhandler(etype,evalue,etb)
135 136 return
136 137 else:
137 138 traceback = TBhandler.text(etype,evalue,etb,context=31)
138 139
139 140 # print traceback to screen
140 141 if self.show_crash_traceback:
141 142 print >> sys.stderr, traceback
142 143
143 144 # and generate a complete report on disk
144 145 try:
145 146 report = open(report_name,'w')
146 147 except:
147 148 print >> sys.stderr, 'Could not create crash report on disk.'
148 149 return
149 150
150 151 # Inform user on stderr of what happened
151 msg = itpl('\n'+'*'*70+'\n'+self.message_template)
152 print >> sys.stderr, msg
152 print >> sys.stderr, '\n'+'*'*70+'\n'
153 print >> sys.stderr, self.message_template.format(**self.info)
153 154
154 155 # Construct report on disk
155 156 report.write(self.make_report(traceback))
156 157 report.close()
157 158 raw_input("Hit <Enter> to quit this message (your terminal may close):")
158 159
159 160 def make_report(self,traceback):
160 161 """Return a string containing a crash report."""
161 162
162 163 sec_sep = self.section_sep
163 164
164 165 report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
165 166 rpt_add = report.append
166 167 rpt_add(sys_info())
167 168
168 169 try:
169 170 config = pformat(self.app.config)
170 171 rpt_add(sec_sep)
171 172 rpt_add('Application name: %s\n\n' % self.app_name)
172 173 rpt_add('Current user configuration structure:\n\n')
173 174 rpt_add(config)
174 175 except:
175 176 pass
176 177 rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
177 178
178 179 return ''.join(report)
179 180
@@ -1,357 +1,332 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 """
4 4 The :class:`~IPython.core.application.Application` object for the command
5 5 line :command:`ipython` program.
6 6
7 7 Authors
8 8 -------
9 9
10 10 * Brian Granger
11 11 * Fernando Perez
12 12 * Min Ragan-Kelley
13 13 """
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Copyright (C) 2008-2010 The IPython Development Team
17 17 #
18 18 # Distributed under the terms of the BSD License. The full license is in
19 19 # the file COPYING, distributed as part of this software.
20 20 #-----------------------------------------------------------------------------
21 21
22 22 #-----------------------------------------------------------------------------
23 23 # Imports
24 24 #-----------------------------------------------------------------------------
25 25
26 26 from __future__ import absolute_import
27 27
28 28 import logging
29 29 import os
30 30 import sys
31 31
32 32 from IPython.config.loader import (
33 33 Config, PyFileConfigLoader
34 34 )
35 35 from IPython.config.application import boolean_flag
36 36 from IPython.core import release
37 37 from IPython.core import usage
38 38 from IPython.core.crashhandler import CrashHandler
39 39 from IPython.core.formatters import PlainTextFormatter
40 40 from IPython.core.application import (
41 41 ProfileDir, BaseIPythonApplication, base_flags, base_aliases
42 42 )
43 43 from IPython.core.shellapp import (
44 44 InteractiveShellApp, shell_flags, shell_aliases
45 45 )
46 46 from IPython.frontend.terminal.interactiveshell import TerminalInteractiveShell
47 47 from IPython.lib import inputhook
48 48 from IPython.utils.path import get_ipython_dir, check_for_old_config
49 49 from IPython.utils.traitlets import (
50 50 Bool, Dict, CaselessStrEnum
51 51 )
52 52
53 53 #-----------------------------------------------------------------------------
54 54 # Globals, utilities and helpers
55 55 #-----------------------------------------------------------------------------
56 56
57 57 #: The default config file name for this application.
58 58 default_config_file_name = u'ipython_config.py'
59 59
60 60
61 61 #-----------------------------------------------------------------------------
62 62 # Crash handler for this application
63 63 #-----------------------------------------------------------------------------
64 64
65 _message_template = """\
66 Oops, $self.app_name crashed. We do our best to make it stable, but...
67
68 A crash report was automatically generated with the following information:
69 - A verbatim copy of the crash traceback.
70 - A copy of your input history during this session.
71 - Data on your current $self.app_name configuration.
72
73 It was left in the file named:
74 \t'$self.crash_report_fname'
75 If you can email this file to the developers, the information in it will help
76 them in understanding and correcting the problem.
77
78 You can mail it to: $self.contact_name at $self.contact_email
79 with the subject '$self.app_name Crash Report'.
80
81 If you want to do it now, the following command will work (under Unix):
82 mail -s '$self.app_name Crash Report' $self.contact_email < $self.crash_report_fname
83
84 To ensure accurate tracking of this issue, please file a report about it at:
85 $self.bug_tracker
86 """
87
88 65 class IPAppCrashHandler(CrashHandler):
89 66 """sys.excepthook for IPython itself, leaves a detailed report on disk."""
90 67
91 message_template = _message_template
92
93 68 def __init__(self, app):
94 69 contact_name = release.authors['Fernando'][0]
95 70 contact_email = release.authors['Fernando'][1]
96 71 bug_tracker = 'http://github.com/ipython/ipython/issues'
97 72 super(IPAppCrashHandler,self).__init__(
98 73 app, contact_name, contact_email, bug_tracker
99 74 )
100 75
101 76 def make_report(self,traceback):
102 77 """Return a string containing a crash report."""
103 78
104 79 sec_sep = self.section_sep
105 80 # Start with parent report
106 81 report = [super(IPAppCrashHandler, self).make_report(traceback)]
107 82 # Add interactive-specific info we may have
108 83 rpt_add = report.append
109 84 try:
110 85 rpt_add(sec_sep+"History of session input:")
111 86 for line in self.app.shell.user_ns['_ih']:
112 87 rpt_add(line)
113 88 rpt_add('\n*** Last line of input (may not be in above history):\n')
114 89 rpt_add(self.app.shell._last_input_line+'\n')
115 90 except:
116 91 pass
117 92
118 93 return ''.join(report)
119 94
120 95 #-----------------------------------------------------------------------------
121 96 # Aliases and Flags
122 97 #-----------------------------------------------------------------------------
123 98 flags = dict(base_flags)
124 99 flags.update(shell_flags)
125 100 addflag = lambda *args: flags.update(boolean_flag(*args))
126 101 addflag('autoedit-syntax', 'TerminalInteractiveShell.autoedit_syntax',
127 102 'Turn on auto editing of files with syntax errors.',
128 103 'Turn off auto editing of files with syntax errors.'
129 104 )
130 105 addflag('banner', 'TerminalIPythonApp.display_banner',
131 106 "Display a banner upon starting IPython.",
132 107 "Don't display a banner upon starting IPython."
133 108 )
134 109 addflag('confirm-exit', 'TerminalInteractiveShell.confirm_exit',
135 110 """Set to confirm when you try to exit IPython with an EOF (Control-D
136 111 in Unix, Control-Z/Enter in Windows). By typing 'exit' or 'quit',
137 112 you can force a direct exit without any confirmation.""",
138 113 "Don't prompt the user when exiting."
139 114 )
140 115 addflag('term-title', 'TerminalInteractiveShell.term_title',
141 116 "Enable auto setting the terminal title.",
142 117 "Disable auto setting the terminal title."
143 118 )
144 119 classic_config = Config()
145 120 classic_config.InteractiveShell.cache_size = 0
146 121 classic_config.PlainTextFormatter.pprint = False
147 122 classic_config.InteractiveShell.prompt_in1 = '>>> '
148 123 classic_config.InteractiveShell.prompt_in2 = '... '
149 124 classic_config.InteractiveShell.prompt_out = ''
150 125 classic_config.InteractiveShell.separate_in = ''
151 126 classic_config.InteractiveShell.separate_out = ''
152 127 classic_config.InteractiveShell.separate_out2 = ''
153 128 classic_config.InteractiveShell.colors = 'NoColor'
154 129 classic_config.InteractiveShell.xmode = 'Plain'
155 130
156 131 flags['classic']=(
157 132 classic_config,
158 133 "Gives IPython a similar feel to the classic Python prompt."
159 134 )
160 135 # # log doesn't make so much sense this way anymore
161 136 # paa('--log','-l',
162 137 # action='store_true', dest='InteractiveShell.logstart',
163 138 # help="Start logging to the default log file (./ipython_log.py).")
164 139 #
165 140 # # quick is harder to implement
166 141 flags['quick']=(
167 142 {'TerminalIPythonApp' : {'quick' : True}},
168 143 "Enable quick startup with no config files."
169 144 )
170 145
171 146 flags['i'] = (
172 147 {'TerminalIPythonApp' : {'force_interact' : True}},
173 148 "If running code from the command line, become interactive afterwards."
174 149 )
175 150 flags['pylab'] = (
176 151 {'TerminalIPythonApp' : {'pylab' : 'auto'}},
177 152 """Pre-load matplotlib and numpy for interactive use with
178 153 the default matplotlib backend."""
179 154 )
180 155
181 156 aliases = dict(base_aliases)
182 157 aliases.update(shell_aliases)
183 158
184 159 # it's possible we don't want short aliases for *all* of these:
185 160 aliases.update(dict(
186 161 gui='TerminalIPythonApp.gui',
187 162 pylab='TerminalIPythonApp.pylab',
188 163 ))
189 164
190 165 #-----------------------------------------------------------------------------
191 166 # Main classes and functions
192 167 #-----------------------------------------------------------------------------
193 168
194 169 class TerminalIPythonApp(BaseIPythonApplication, InteractiveShellApp):
195 170 name = u'ipython'
196 171 description = usage.cl_usage
197 172 default_config_file_name = default_config_file_name
198 173 crash_handler_class = IPAppCrashHandler
199 174
200 175 flags = Dict(flags)
201 176 aliases = Dict(aliases)
202 177 classes = [InteractiveShellApp, TerminalInteractiveShell, ProfileDir, PlainTextFormatter]
203 178 subcommands = Dict(dict(
204 179 qtconsole=('IPython.frontend.qt.console.qtconsoleapp.IPythonQtConsoleApp',
205 180 """Launch the IPython Qt Console."""
206 181 ),
207 182 profile = ("IPython.core.profileapp.ProfileApp",
208 183 "Create and manage IPython profiles.")
209 184 ))
210 185
211 186 # *do* autocreate requested profile, but don't create the config file.
212 187 auto_create=Bool(True)
213 188 # configurables
214 189 ignore_old_config=Bool(False, config=True,
215 190 help="Suppress warning messages about legacy config files"
216 191 )
217 192 quick = Bool(False, config=True,
218 193 help="""Start IPython quickly by skipping the loading of config files."""
219 194 )
220 195 def _quick_changed(self, name, old, new):
221 196 if new:
222 197 self.load_config_file = lambda *a, **kw: None
223 198 self.ignore_old_config=True
224 199
225 200 gui = CaselessStrEnum(('qt','wx','gtk'), config=True,
226 201 help="Enable GUI event loop integration ('qt', 'wx', 'gtk')."
227 202 )
228 203 pylab = CaselessStrEnum(['tk', 'qt', 'wx', 'gtk', 'osx', 'auto'],
229 204 config=True,
230 205 help="""Pre-load matplotlib and numpy for interactive use,
231 206 selecting a particular matplotlib backend and loop integration.
232 207 """
233 208 )
234 209 display_banner = Bool(True, config=True,
235 210 help="Whether to display a banner upon starting IPython."
236 211 )
237 212
238 213 # if there is code of files to run from the cmd line, don't interact
239 214 # unless the --i flag (App.force_interact) is true.
240 215 force_interact = Bool(False, config=True,
241 216 help="""If a command or file is given via the command-line,
242 217 e.g. 'ipython foo.py"""
243 218 )
244 219 def _force_interact_changed(self, name, old, new):
245 220 if new:
246 221 self.interact = True
247 222
248 223 def _file_to_run_changed(self, name, old, new):
249 224 if new and not self.force_interact:
250 225 self.interact = False
251 226 _code_to_run_changed = _file_to_run_changed
252 227
253 228 # internal, not-configurable
254 229 interact=Bool(True)
255 230
256 231
257 232 def initialize(self, argv=None):
258 233 """Do actions after construct, but before starting the app."""
259 234 super(TerminalIPythonApp, self).initialize(argv)
260 235 if self.subapp is not None:
261 236 # don't bother initializing further, starting subapp
262 237 return
263 238 if not self.ignore_old_config:
264 239 check_for_old_config(self.ipython_dir)
265 240 # print self.extra_args
266 241 if self.extra_args:
267 242 self.file_to_run = self.extra_args[0]
268 243 # create the shell
269 244 self.init_shell()
270 245 # and draw the banner
271 246 self.init_banner()
272 247 # Now a variety of things that happen after the banner is printed.
273 248 self.init_gui_pylab()
274 249 self.init_extensions()
275 250 self.init_code()
276 251
277 252 def init_shell(self):
278 253 """initialize the InteractiveShell instance"""
279 254 # I am a little hesitant to put these into InteractiveShell itself.
280 255 # But that might be the place for them
281 256 sys.path.insert(0, '')
282 257
283 258 # Create an InteractiveShell instance.
284 259 # shell.display_banner should always be False for the terminal
285 260 # based app, because we call shell.show_banner() by hand below
286 261 # so the banner shows *before* all extension loading stuff.
287 262 self.shell = TerminalInteractiveShell.instance(config=self.config,
288 263 display_banner=False, profile_dir=self.profile_dir,
289 264 ipython_dir=self.ipython_dir)
290 265
291 266 def init_banner(self):
292 267 """optionally display the banner"""
293 268 if self.display_banner and self.interact:
294 269 self.shell.show_banner()
295 270 # Make sure there is a space below the banner.
296 271 if self.log_level <= logging.INFO: print
297 272
298 273
299 274 def init_gui_pylab(self):
300 275 """Enable GUI event loop integration, taking pylab into account."""
301 276 gui = self.gui
302 277
303 278 # Using `pylab` will also require gui activation, though which toolkit
304 279 # to use may be chosen automatically based on mpl configuration.
305 280 if self.pylab:
306 281 activate = self.shell.enable_pylab
307 282 if self.pylab == 'auto':
308 283 gui = None
309 284 else:
310 285 gui = self.pylab
311 286 else:
312 287 # Enable only GUI integration, no pylab
313 288 activate = inputhook.enable_gui
314 289
315 290 if gui or self.pylab:
316 291 try:
317 292 self.log.info("Enabling GUI event loop integration, "
318 293 "toolkit=%s, pylab=%s" % (gui, self.pylab) )
319 294 activate(gui)
320 295 except:
321 296 self.log.warn("Error in enabling GUI event loop integration:")
322 297 self.shell.showtraceback()
323 298
324 299 def start(self):
325 300 if self.subapp is not None:
326 301 return self.subapp.start()
327 302 # perform any prexec steps:
328 303 if self.interact:
329 304 self.log.debug("Starting IPython's mainloop...")
330 305 self.shell.mainloop()
331 306 else:
332 307 self.log.debug("IPython not interactive...")
333 308
334 309
335 310 def load_default_config(ipython_dir=None):
336 311 """Load the default config file from the default ipython_dir.
337 312
338 313 This is useful for embedded shells.
339 314 """
340 315 if ipython_dir is None:
341 316 ipython_dir = get_ipython_dir()
342 317 profile_dir = os.path.join(ipython_dir, 'profile_default')
343 318 cl = PyFileConfigLoader(default_config_file_name, profile_dir)
344 319 config = cl.load_config()
345 320 return config
346 321
347 322
348 323 def launch_new_instance():
349 324 """Create and run a full blown IPython instance"""
350 325 app = TerminalIPythonApp.instance()
351 326 app.initialize()
352 327 app.start()
353 328
354 329
355 330 if __name__ == '__main__':
356 331 launch_new_instance()
357 332
General Comments 0
You need to be logged in to leave comments. Login now