From b9bfb192d1335fa619d1bf89e64e89443ebaa01a 2020-07-23 22:11:14
From: Matthias Bussonnier <bussonniermatthias@gmail.com>
Date: 2020-07-23 22:11:14
Subject: [PATCH] Allow to mark transformers as having side effects

We then unly run them when actually running the code.

---

diff --git a/IPython/core/inputtransformer2.py b/IPython/core/inputtransformer2.py
index 097dd88..afbce23 100644
--- a/IPython/core/inputtransformer2.py
+++ b/IPython/core/inputtransformer2.py
@@ -637,7 +637,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
 
@@ -650,7 +651,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()"""