diff --git a/IPython/core/inputtransformer.py b/IPython/core/inputtransformer.py index 115a5ca..e511c5c 100644 --- a/IPython/core/inputtransformer.py +++ b/IPython/core/inputtransformer.py @@ -47,6 +47,9 @@ class InputTransformer(with_metaclass(abc.ABCMeta, object)): input or None if the transformer is waiting for more input. Must be overridden by subclasses. + + Implementations may raise ``SyntaxError`` if the input is invalid. No + other exceptions may be raised. """ pass diff --git a/IPython/core/interactiveshell.py b/IPython/core/interactiveshell.py index 1b13c98..24ac215 100644 --- a/IPython/core/interactiveshell.py +++ b/IPython/core/interactiveshell.py @@ -2632,7 +2632,12 @@ class InteractiveShell(SingletonConfigurable): if silent: store_history = False - self.input_transformer_manager.push(raw_cell) + prefilter_failed = False + try: + self.input_transformer_manager.push(raw_cell) + except SyntaxError: + self.showtraceback() + prefilter_failed = True cell = self.input_transformer_manager.source_reset() # Our own compiler remembers the __future__ environment. If we want to @@ -2641,8 +2646,7 @@ class InteractiveShell(SingletonConfigurable): compiler = self.compile if shell_futures else CachingCompiler() with self.builtin_trap: - prefilter_failed = False - if len(cell.splitlines()) == 1: + if not prefilter_failed and len(cell.splitlines()) == 1: try: # use prefilter_lines to handle trailing newlines # restore trailing newline for ast.parse diff --git a/IPython/core/tests/test_application.py b/IPython/core/tests/test_application.py index dea26ea..8ef97f3 100644 --- a/IPython/core/tests/test_application.py +++ b/IPython/core/tests/test_application.py @@ -3,13 +3,9 @@ import os import tempfile -import shutil - -import nose.tools as nt from IPython.core.application import BaseIPythonApplication from IPython.testing import decorators as dec -from IPython.testing.tools import make_tempfile, ipexec from IPython.utils import py3compat @dec.onlyif_unicode_paths @@ -52,46 +48,3 @@ def test_unicode_ipdir(): os.environ["IPYTHONDIR"] = old_ipdir1 if old_ipdir2: os.environ["IPYTHONDIR"] = old_ipdir2 - - - -TEST_SYNTAX_ERROR_CMDS = """ -from IPython.core.inputtransformer import InputTransformer - -%cpaste -class SyntaxErrorTransformer(InputTransformer): - - def push(self, line): - if 'syntaxerror' in line: - raise SyntaxError('in input '+line) - return line - - def reset(self): - pass --- - -ip = get_ipython() -transformer = SyntaxErrorTransformer() -ip.input_splitter.python_line_transforms.append(transformer) -ip.input_transformer_manager.python_line_transforms.append(transformer) - -# now the actual commands -1234 -2345 # syntaxerror <- triggered here -3456 -""" - -def test_syntax_error(): - """Check that IPython does not abort if a SyntaxError is raised in an InputTransformer""" - try: - tmp = tempfile.mkdtemp() - filename = os.path.join(tmp, 'test_syntax_error.py') - with open(filename, 'w') as f: - f.write(TEST_SYNTAX_ERROR_CMDS) - out, err = ipexec(filename, pipe=True) - nt.assert_equal(err, '') - nt.assert_in('1234', out) - nt.assert_in('SyntaxError: in input 2345 # syntaxerror <- triggered here', out) - nt.assert_in('3456', out) - finally: - shutil.rmtree(tmp) diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index ee7fb28..9d38cbb 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -33,6 +33,7 @@ from os.path import join import nose.tools as nt # Our own +from IPython.core.inputtransformer import InputTransformer from IPython.testing.decorators import skipif, skip_win32, onlyif_unicode_paths from IPython.testing import tools as tt from IPython.utils import io @@ -674,4 +675,41 @@ def test_user_expression(): +class TestSyntaxErrorTransformer(unittest.TestCase): + """Check that SyntaxError raised by an input transformer is handled by run_cell()""" + + class SyntaxErrorTransformer(InputTransformer): + + def push(self, line): + pos = line.find('syntaxerror') + if pos >= 0: + e = SyntaxError('input contains "syntaxerror"') + e.text = line + e.offset = pos + 1 + raise e + return line + + def reset(self): + pass + + def setUp(self): + self.transformer = TestSyntaxErrorTransformer.SyntaxErrorTransformer() + ip.input_splitter.python_line_transforms.append(self.transformer) + ip.input_transformer_manager.python_line_transforms.append(self.transformer) + + def tearDown(self): + ip.input_splitter.python_line_transforms.remove(self.transformer) + ip.input_transformer_manager.python_line_transforms.remove(self.transformer) + + def test_syntaxerror_input_transformer(self): + with tt.AssertPrints('1234'): + ip.run_cell('1234') + with tt.AssertPrints('SyntaxError: invalid syntax'): + ip.run_cell('1 2 3') # plain python syntax error + with tt.AssertPrints('SyntaxError: input contains "syntaxerror"'): + ip.run_cell('2345 # syntaxerror') # input transformer syntax error + with tt.AssertPrints('3456'): + ip.run_cell('3456') + + diff --git a/IPython/terminal/tests/test_terminal.py b/IPython/terminal/tests/test_terminal.py new file mode 100644 index 0000000..20c5eef --- /dev/null +++ b/IPython/terminal/tests/test_terminal.py @@ -0,0 +1,58 @@ +# coding: utf-8 +"""Tests for the IPython terminal""" + +import os +import tempfile +import shutil + +import nose.tools as nt + +from IPython.testing.tools import make_tempfile, ipexec + + +TEST_SYNTAX_ERROR_CMDS = """ +from IPython.core.inputtransformer import InputTransformer + +%cpaste +class SyntaxErrorTransformer(InputTransformer): + + def push(self, line): + pos = line.find('syntaxerror') + if pos >= 0: + e = SyntaxError('input contains "syntaxerror"') + e.text = line + e.offset = pos + 1 + raise e + return line + + def reset(self): + pass +-- + +ip = get_ipython() +transformer = SyntaxErrorTransformer() +ip.input_splitter.python_line_transforms.append(transformer) +ip.input_transformer_manager.python_line_transforms.append(transformer) + +# now the actual commands +1234 +2345 # syntaxerror <- triggered here +3456 +""" + +def test_syntax_error(): + """Check that the IPython terminal does not abort if a SyntaxError is raised in an InputTransformer""" + try: + tmp = tempfile.mkdtemp() + filename = os.path.join(tmp, 'test_syntax_error.py') + with open(filename, 'w') as f: + f.write(TEST_SYNTAX_ERROR_CMDS) + out, err = ipexec(filename, pipe=True) + nt.assert_equal(err, '') + nt.assert_in('1234', out) + nt.assert_in(' 2345 # syntaxerror <- triggered here', out) + nt.assert_in(' ^', out) + nt.assert_in('SyntaxError: input contains "syntaxerror"', out) + nt.assert_in('3456', out) + finally: + shutil.rmtree(tmp)