mirror of https://github.com/python/cpython
[3.13] gh-118893: Evaluate all statements in the new REPL separately (GH-119318) (#119408)
(cherry picked from commit a3e4fec873
)
Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com>
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
eafd633fac
commit
a463cd8e45
|
@ -30,6 +30,7 @@ import _sitebuiltins
|
|||
import linecache
|
||||
import sys
|
||||
import code
|
||||
import ast
|
||||
from types import ModuleType
|
||||
|
||||
from .readline import _get_reader, multiline_input
|
||||
|
@ -74,9 +75,36 @@ class InteractiveColoredConsole(code.InteractiveConsole):
|
|||
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,9 +172,6 @@ def run_multiline_interactive_console(
|
|||
|
||||
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]
|
||||
assert not more
|
||||
input_n += 1
|
||||
|
|
|
@ -94,7 +94,7 @@ class InteractiveInterpreter:
|
|||
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 @@ class InteractiveInterpreter:
|
|||
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 @@ class InteractiveInterpreter:
|
|||
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
|
||||
|
|
|
@ -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()
|
|
@ -543,7 +543,7 @@ class TracebackCases(unittest.TestCase):
|
|||
|
||||
self.assertEqual(
|
||||
str(inspect.signature(traceback.format_exception_only)),
|
||||
'(exc, /, value=<implicit>, *, show_group=False)')
|
||||
'(exc, /, value=<implicit>, *, show_group=False, **kwargs)')
|
||||
|
||||
|
||||
class PurePythonExceptionFormattingMixin:
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue