diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py index 0443e68..5285644 100644 --- a/IPython/core/inputtransformer2.py +++ b/IPython/core/inputtransformer2.py @@ -634,7 +634,8 @@ class TransformerManager: try: for transform in self.cleanup_transforms: - lines = transform(lines) + if not getattr(transform, 'has_side_effects', False): + lines = transform(lines) except SyntaxError: return 'invalid', None @@ -647,7 +648,8 @@ class TransformerManager: try: for transform in self.line_transforms: - lines = transform(lines) + if not getattr(transform, 'has_side_effects', False): + lines = transform(lines) lines = self.do_token_transforms(lines) except SyntaxError: return 'invalid', None diff --git a/IPython/core/tests/test_inputtransformer2.py b/IPython/core/tests/test_inputtransformer2.py index b29a019..1ff0fd8 100644 --- a/IPython/core/tests/test_inputtransformer2.py +++ b/IPython/core/tests/test_inputtransformer2.py @@ -289,4 +289,38 @@ def test_check_complete_II(): def test_null_cleanup_transformer(): manager = ipt2.TransformerManager() manager.cleanup_transforms.insert(0, null_cleanup_transformer) - nt.assert_is(manager.transform_cell(""), "") + assert manager.transform_cell("") == "" + + + + +def test_side_effects_I(): + count = 0 + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.cleanup_transforms.insert(0, counter) + assert manager.check_complete("a=1\n") == ('complete', None) + assert count == 0 + + + + +def test_side_effects_II(): + count = 0 + def counter(lines): + nonlocal count + count += 1 + return lines + + counter.has_side_effects = True + + manager = ipt2.TransformerManager() + manager.line_transforms.insert(0, counter) + assert manager.check_complete("b=1\n") == ('complete', None) + assert count == 0 diff --git a/IPython/core/tests/test_interactiveshell.py b/IPython/core/tests/test_interactiveshell.py index 496e3bd..951b843 100644 --- a/IPython/core/tests/test_interactiveshell.py +++ b/IPython/core/tests/test_interactiveshell.py @@ -689,6 +689,30 @@ class TestAstTransform(unittest.TestCase): with tt.AssertPrints("8"): ip.run_cell("amacro") +class TestMiscTransform(unittest.TestCase): + + + def test_transform_only_once(self): + cleanup = 0 + line_t = 0 + def count_cleanup(lines): + nonlocal cleanup + cleanup += 1 + return lines + + def count_line_t(lines): + nonlocal line_t + line_t += 1 + return lines + + ip.input_transformer_manager.cleanup_transforms.append(count_cleanup) + ip.input_transformer_manager.line_transforms.append(count_line_t) + + ip.run_cell('1') + + assert cleanup == 1 + assert line_t == 1 + class IntegerWrapper(ast.NodeTransformer): """Wraps all integers in a call to Integer()""" diff --git a/docs/source/config/inputtransforms.rst b/docs/source/config/inputtransforms.rst index 9e41707..6b7943b 100644 --- a/docs/source/config/inputtransforms.rst +++ b/docs/source/config/inputtransforms.rst @@ -62,6 +62,14 @@ To start using this:: ip = get_ipython() ip.input_transformers_cleanup.append(reverse_line_chars) +.. versionadded:: 7.17 + + input_transformers can now have an attribute ``has_side_effects`` set to + `True`, which will prevent the transformers from being ran when IPython is + trying to guess whether the user input is complete. + + + AST transformations =================== diff --git a/docs/source/whatsnew/pr/side-effects-tr.rst b/docs/source/whatsnew/pr/side-effects-tr.rst new file mode 100644 index 0000000..439a665 --- /dev/null +++ b/docs/source/whatsnew/pr/side-effects-tr.rst @@ -0,0 +1,2 @@ +input_transformers can now have an attribute ``has_side_effects`` set to `True`, which will prevent the +transformers from being ran when IPython is trying to guess whether the user input is complete.