diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 7775b63..14beee8 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -1626,6 +1626,39 @@ class InteractiveShell(SingletonConfigurable, Magic): """ self.showtraceback((etype,value,tb),tb_offset=0) + def _get_exc_info(self, exc_tuple=None): + """get exc_info from a given tuple, sys.exc_info() or sys.last_type etc. + + Ensures sys.last_type,value,traceback hold the exc_info we found, + from whichever source. + + raises ValueError if none of these contain any information + """ + if exc_tuple is None: + etype, value, tb = sys.exc_info() + else: + etype, value, tb = exc_tuple + + if etype is None: + if hasattr(sys, 'last_type'): + etype, value, tb = sys.last_type, sys.last_value, \ + sys.last_traceback + + if etype is None: + raise ValueError("No exception to find") + + # Now store the exception info in sys.last_type etc. + # WARNING: these variables are somewhat deprecated and not + # necessarily safe to use in a threaded environment, but tools + # like pdb depend on their existence, so let's set them. If we + # find problems in the field, we'll need to revisit their use. + sys.last_type = etype + sys.last_value = value + sys.last_traceback = tb + + return etype, value, tb + + def showtraceback(self,exc_tuple = None,filename=None,tb_offset=None, exception_only=False): """Display the exception that just occurred. @@ -1640,18 +1673,11 @@ class InteractiveShell(SingletonConfigurable, Magic): simply call this method.""" try: - if exc_tuple is None: - etype, value, tb = sys.exc_info() - else: - etype, value, tb = exc_tuple - - if etype is None: - if hasattr(sys, 'last_type'): - etype, value, tb = sys.last_type, sys.last_value, \ - sys.last_traceback - else: - self.write_err('No traceback available to show.\n') - return + try: + etype, value, tb = self._get_exc_info(exc_tuple) + except ValueError: + self.write_err('No traceback available to show.\n') + return if etype is SyntaxError: # Though this won't be called by syntax errors in the input @@ -1660,13 +1686,6 @@ class InteractiveShell(SingletonConfigurable, Magic): elif etype is UsageError: self.write_err("UsageError: %s" % value) else: - # WARNING: these variables are somewhat deprecated and not - # necessarily safe to use in a threaded environment, but tools - # like pdb depend on their existence, so let's set them. If we - # find problems in the field, we'll need to revisit their use. - sys.last_type = etype - sys.last_value = value - sys.last_traceback = tb if etype in self.custom_exceptions: stb = self.CustomTB(etype, value, tb, tb_offset) else: @@ -1708,12 +1727,7 @@ class InteractiveShell(SingletonConfigurable, Magic): of what was there before (because Python's parser always uses "<string>" when reading from a string). """ - etype, value, last_traceback = sys.exc_info() - - # See note about these variables in showtraceback() above - sys.last_type = etype - sys.last_value = value - sys.last_traceback = last_traceback + etype, value, last_traceback = self._get_exc_info() if filename and etype is SyntaxError: try: diff --git a/IPython/core/tests/test_magic.py b/IPython/core/tests/test_magic.py index e3a2e38..6efc4b1 100644 --- a/IPython/core/tests/test_magic.py +++ b/IPython/core/tests/test_magic.py @@ -9,6 +9,8 @@ from __future__ import absolute_import #----------------------------------------------------------------------------- import os +import sys +from StringIO import StringIO import nose.tools as nt @@ -232,6 +234,23 @@ def test_reset_in_length(): def test_time(): _ip.magic('time None') +def test_tb_syntaxerror(): + """test %tb after a SyntaxError""" + ip = get_ipython() + ip.run_cell("for") + + # trap and validate stdout + save_stdout = sys.stdout + try: + sys.stdout = StringIO() + ip.run_cell("%tb") + out = sys.stdout.getvalue() + finally: + sys.stdout = save_stdout + # trim output, and only check the last line + last_line = out.rstrip().splitlines()[-1].strip() + nt.assert_equals(last_line, "SyntaxError: invalid syntax") + @py3compat.doctest_refactor_print def doctest_time():