https://github.com/python/cpython/commit/a463cd8e45d6fc6ca16a59bcfeece99cf268e684
commit: a463cd8e45d6fc6ca16a59bcfeece99cf268e684
branch: 3.13
author: Miss Islington (bot) <[email protected]>
committer: lysnikolaou <[email protected]>
date: 2024-05-22T16:22:01Z
summary:

[3.13] gh-118893: Evaluate all statements in the new REPL separately 
(GH-119318) (#119408)

(cherry picked from commit a3e4fec8734a304d654e4ae24a4aa2f41a7b0640)

Co-authored-by: Pablo Galindo Salgado <[email protected]>
Co-authored-by: Ɓukasz Langa <[email protected]>

files:
A Lib/test/test_pyrepl/test_interact.py
M Lib/_pyrepl/simple_interact.py
M Lib/code.py
M Lib/test/test_traceback.py
M Lib/traceback.py

diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py
index 31b2097a78a2268..d65b6d0d62790ac 100644
--- a/Lib/_pyrepl/simple_interact.py
+++ b/Lib/_pyrepl/simple_interact.py
@@ -30,6 +30,7 @@
 import linecache
 import sys
 import code
+import ast
 from types import ModuleType
 
 from .readline import _get_reader, multiline_input
@@ -74,9 +75,36 @@ def __init__(
         super().__init__(locals=locals, filename=filename, 
local_exit=local_exit)  # type: ignore[call-arg]
         self.can_colorize = _colorize.can_colorize()
 
+    def showsyntaxerror(self, filename=None):
+        super().showsyntaxerror(colorize=self.can_colorize)
+
     def showtraceback(self):
         super().showtraceback(colorize=self.can_colorize)
 
+    def runsource(self, source, filename="<input>", symbol="single"):
+        try:
+            tree = ast.parse(source)
+        except (OverflowError, SyntaxError, ValueError):
+            self.showsyntaxerror(filename)
+            return False
+        if tree.body:
+            *_, last_stmt = tree.body
+        for stmt in tree.body:
+            wrapper = ast.Interactive if stmt is last_stmt else ast.Module
+            the_symbol = symbol if stmt is last_stmt else "exec"
+            item = wrapper([stmt])
+            try:
+                code = compile(item, filename, the_symbol)
+            except (OverflowError, ValueError):
+                    self.showsyntaxerror(filename)
+                    return False
+
+            if code is None:
+                return True
+
+            self.runcode(code)
+        return False
+
 
 def run_multiline_interactive_console(
     mainmodule: ModuleType | None= None, future_flags: int = 0
@@ -144,10 +172,7 @@ def more_lines(unicodetext: str) -> bool:
 
             input_name = f"<python-input-{input_n}>"
             linecache._register_code(input_name, statement, "<stdin>")  # 
type: ignore[attr-defined]
-            symbol = "single" if not contains_pasted_code else "exec"
-            more = console.push(_strip_final_indent(statement), 
filename=input_name, _symbol=symbol)  # type: ignore[call-arg]
-            if contains_pasted_code and more:
-                more = console.push(_strip_final_indent(statement), 
filename=input_name, _symbol="single")  # type: ignore[call-arg]
+            more = console.push(_strip_final_indent(statement), 
filename=input_name, _symbol="single")  # type: ignore[call-arg]
             assert not more
             input_n += 1
         except KeyboardInterrupt:
diff --git a/Lib/code.py b/Lib/code.py
index 0c2fd2963b21186..b93902ccf545b34 100644
--- a/Lib/code.py
+++ b/Lib/code.py
@@ -94,7 +94,7 @@ def runcode(self, code):
         except:
             self.showtraceback()
 
-    def showsyntaxerror(self, filename=None):
+    def showsyntaxerror(self, filename=None, **kwargs):
         """Display the syntax error that just occurred.
 
         This doesn't display a stack trace because there isn't one.
@@ -106,6 +106,7 @@ def showsyntaxerror(self, filename=None):
         The output is written by self.write(), below.
 
         """
+        colorize = kwargs.pop('colorize', False)
         type, value, tb = sys.exc_info()
         sys.last_exc = value
         sys.last_type = type
@@ -123,7 +124,7 @@ def showsyntaxerror(self, filename=None):
                 value = SyntaxError(msg, (filename, lineno, offset, line))
                 sys.last_exc = sys.last_value = value
         if sys.excepthook is sys.__excepthook__:
-            lines = traceback.format_exception_only(type, value)
+            lines = traceback.format_exception_only(type, value, 
colorize=colorize)
             self.write(''.join(lines))
         else:
             # If someone has set sys.excepthook, we let that take precedence
diff --git a/Lib/test/test_pyrepl/test_interact.py 
b/Lib/test/test_pyrepl/test_interact.py
new file mode 100644
index 000000000000000..b3dc07c063f90eb
--- /dev/null
+++ b/Lib/test/test_pyrepl/test_interact.py
@@ -0,0 +1,92 @@
+import contextlib
+import io
+import unittest
+from unittest.mock import patch
+from textwrap import dedent
+
+from test.support import force_not_colorized
+
+from _pyrepl.simple_interact import InteractiveColoredConsole
+
+
+class TestSimpleInteract(unittest.TestCase):
+    def test_multiple_statements(self):
+        namespace = {}
+        code = dedent("""\
+        class A:
+            def foo(self):
+
+
+                pass
+
+        class B:
+            def bar(self):
+                pass
+
+        a = 1
+        a
+        """)
+        console = InteractiveColoredConsole(namespace, filename="<stdin>")
+        with (
+            patch.object(InteractiveColoredConsole, "showsyntaxerror") as 
showsyntaxerror,
+            patch.object(InteractiveColoredConsole, "runsource", 
wraps=console.runsource) as runsource,
+        ):
+            more = console.push(code, filename="<stdin>", _symbol="single")  # 
type: ignore[call-arg]
+        self.assertFalse(more)
+        showsyntaxerror.assert_not_called()
+
+
+    def test_multiple_statements_output(self):
+        namespace = {}
+        code = dedent("""\
+        b = 1
+        b
+        a = 1
+        a
+        """)
+        console = InteractiveColoredConsole(namespace, filename="<stdin>")
+        f = io.StringIO()
+        with contextlib.redirect_stdout(f):
+            more = console.push(code, filename="<stdin>", _symbol="single")  # 
type: ignore[call-arg]
+        self.assertFalse(more)
+        self.assertEqual(f.getvalue(), "1\n")
+
+    def test_empty(self):
+        namespace = {}
+        code = ""
+        console = InteractiveColoredConsole(namespace, filename="<stdin>")
+        f = io.StringIO()
+        with contextlib.redirect_stdout(f):
+            more = console.push(code, filename="<stdin>", _symbol="single")  # 
type: ignore[call-arg]
+        self.assertFalse(more)
+        self.assertEqual(f.getvalue(), "")
+
+    def test_runsource_compiles_and_runs_code(self):
+        console = InteractiveColoredConsole()
+        source = "print('Hello, world!')"
+        with patch.object(console, "runcode") as mock_runcode:
+            console.runsource(source)
+            mock_runcode.assert_called_once()
+
+    def test_runsource_returns_false_for_successful_compilation(self):
+        console = InteractiveColoredConsole()
+        source = "print('Hello, world!')"
+        result = console.runsource(source)
+        self.assertFalse(result)
+
+    @force_not_colorized
+    def test_runsource_returns_false_for_failed_compilation(self):
+        console = InteractiveColoredConsole()
+        source = "print('Hello, world!'"
+        f = io.StringIO()
+        with contextlib.redirect_stderr(f):
+            result = console.runsource(source)
+        self.assertFalse(result)
+        self.assertIn('SyntaxError', f.getvalue())
+
+    def test_runsource_shows_syntax_error_for_failed_compilation(self):
+        console = InteractiveColoredConsole()
+        source = "print('Hello, world!'"
+        with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
+            console.runsource(source)
+            mock_showsyntaxerror.assert_called_once()
diff --git a/Lib/test/test_traceback.py b/Lib/test/test_traceback.py
index 5987ec382e6c853..5035de114b5e9d3 100644
--- a/Lib/test/test_traceback.py
+++ b/Lib/test/test_traceback.py
@@ -543,7 +543,7 @@ def test_signatures(self):
 
         self.assertEqual(
             str(inspect.signature(traceback.format_exception_only)),
-            '(exc, /, value=<implicit>, *, show_group=False)')
+            '(exc, /, value=<implicit>, *, show_group=False, **kwargs)')
 
 
 class PurePythonExceptionFormattingMixin:
diff --git a/Lib/traceback.py b/Lib/traceback.py
index 9401b461497cc1e..280d92d04cac9b4 100644
--- a/Lib/traceback.py
+++ b/Lib/traceback.py
@@ -155,7 +155,7 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, 
limit=None, \
     return list(te.format(chain=chain, colorize=colorize))
 
 
-def format_exception_only(exc, /, value=_sentinel, *, show_group=False):
+def format_exception_only(exc, /, value=_sentinel, *, show_group=False, 
**kwargs):
     """Format the exception part of a traceback.
 
     The return value is a list of strings, each ending in a newline.
@@ -170,10 +170,11 @@ def format_exception_only(exc, /, value=_sentinel, *, 
show_group=False):
     :exc:`BaseExceptionGroup`, the nested exceptions are included as
     well, recursively, with indentation relative to their nesting depth.
     """
+    colorize = kwargs.get("colorize", False)
     if value is _sentinel:
         value = exc
     te = TracebackException(type(value), value, None, compact=True)
-    return list(te.format_exception_only(show_group=show_group))
+    return list(te.format_exception_only(show_group=show_group, 
colorize=colorize))
 
 
 # -- not official API but folk probably use these two functions.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: [email protected]

Reply via email to