mirror of
https://github.com/python/cpython.git
synced 2025-08-04 08:59:19 +00:00
gh-118893: Evaluate all statements in the new REPL separately (#119318)
Co-authored-by: Łukasz Langa <lukasz@langa.pl>
This commit is contained in:
parent
9fa206aaec
commit
a3e4fec873
5 changed files with 128 additions and 9 deletions
|
@ -30,6 +30,7 @@ import _sitebuiltins
|
||||||
import linecache
|
import linecache
|
||||||
import sys
|
import sys
|
||||||
import code
|
import code
|
||||||
|
import ast
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
|
|
||||||
from .readline import _get_reader, multiline_input
|
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]
|
super().__init__(locals=locals, filename=filename, local_exit=local_exit) # type: ignore[call-arg]
|
||||||
self.can_colorize = _colorize.can_colorize()
|
self.can_colorize = _colorize.can_colorize()
|
||||||
|
|
||||||
|
def showsyntaxerror(self, filename=None):
|
||||||
|
super().showsyntaxerror(colorize=self.can_colorize)
|
||||||
|
|
||||||
def showtraceback(self):
|
def showtraceback(self):
|
||||||
super().showtraceback(colorize=self.can_colorize)
|
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(
|
def run_multiline_interactive_console(
|
||||||
mainmodule: ModuleType | None= None, future_flags: int = 0
|
mainmodule: ModuleType | None= None, future_flags: int = 0
|
||||||
|
@ -144,10 +172,7 @@ def run_multiline_interactive_console(
|
||||||
|
|
||||||
input_name = f"<python-input-{input_n}>"
|
input_name = f"<python-input-{input_n}>"
|
||||||
linecache._register_code(input_name, statement, "<stdin>") # type: ignore[attr-defined]
|
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="single") # type: ignore[call-arg]
|
||||||
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
|
assert not more
|
||||||
input_n += 1
|
input_n += 1
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
|
|
|
@ -94,7 +94,7 @@ class InteractiveInterpreter:
|
||||||
except:
|
except:
|
||||||
self.showtraceback()
|
self.showtraceback()
|
||||||
|
|
||||||
def showsyntaxerror(self, filename=None):
|
def showsyntaxerror(self, filename=None, **kwargs):
|
||||||
"""Display the syntax error that just occurred.
|
"""Display the syntax error that just occurred.
|
||||||
|
|
||||||
This doesn't display a stack trace because there isn't one.
|
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.
|
The output is written by self.write(), below.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
colorize = kwargs.pop('colorize', False)
|
||||||
type, value, tb = sys.exc_info()
|
type, value, tb = sys.exc_info()
|
||||||
sys.last_exc = value
|
sys.last_exc = value
|
||||||
sys.last_type = type
|
sys.last_type = type
|
||||||
|
@ -123,7 +124,7 @@ class InteractiveInterpreter:
|
||||||
value = SyntaxError(msg, (filename, lineno, offset, line))
|
value = SyntaxError(msg, (filename, lineno, offset, line))
|
||||||
sys.last_exc = sys.last_value = value
|
sys.last_exc = sys.last_value = value
|
||||||
if sys.excepthook is sys.__excepthook__:
|
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))
|
self.write(''.join(lines))
|
||||||
else:
|
else:
|
||||||
# If someone has set sys.excepthook, we let that take precedence
|
# If someone has set sys.excepthook, we let that take precedence
|
||||||
|
|
92
Lib/test/test_pyrepl/test_interact.py
Normal file
92
Lib/test/test_pyrepl/test_interact.py
Normal file
|
@ -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(
|
self.assertEqual(
|
||||||
str(inspect.signature(traceback.format_exception_only)),
|
str(inspect.signature(traceback.format_exception_only)),
|
||||||
'(exc, /, value=<implicit>, *, show_group=False)')
|
'(exc, /, value=<implicit>, *, show_group=False, **kwargs)')
|
||||||
|
|
||||||
|
|
||||||
class PurePythonExceptionFormattingMixin:
|
class PurePythonExceptionFormattingMixin:
|
||||||
|
|
|
@ -155,7 +155,7 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
|
||||||
return list(te.format(chain=chain, colorize=colorize))
|
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.
|
"""Format the exception part of a traceback.
|
||||||
|
|
||||||
The return value is a list of strings, each ending in a newline.
|
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
|
:exc:`BaseExceptionGroup`, the nested exceptions are included as
|
||||||
well, recursively, with indentation relative to their nesting depth.
|
well, recursively, with indentation relative to their nesting depth.
|
||||||
"""
|
"""
|
||||||
|
colorize = kwargs.get("colorize", False)
|
||||||
if value is _sentinel:
|
if value is _sentinel:
|
||||||
value = exc
|
value = exc
|
||||||
te = TracebackException(type(value), value, None, compact=True)
|
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.
|
# -- not official API but folk probably use these two functions.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue