Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a463cd8

Browse filesBrowse files
miss-islingtonpablogsalambv
authored
[3.13] gh-118893: Evaluate all statements in the new REPL separately (GH-119318) (#119408)
(cherry picked from commit a3e4fec) Co-authored-by: Pablo Galindo Salgado <Pablogsal@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent eafd633 commit a463cd8
Copy full SHA for a463cd8

File tree

5 files changed

+128
-9
lines changed
Filter options

5 files changed

+128
-9
lines changed

‎Lib/_pyrepl/simple_interact.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/simple_interact.py
+29-4Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import linecache
3131
import sys
3232
import code
33+
import ast
3334
from types import ModuleType
3435

3536
from .readline import _get_reader, multiline_input
@@ -74,9 +75,36 @@ def __init__(
7475
super().__init__(locals=locals, filename=filename, local_exit=local_exit) # type: ignore[call-arg]
7576
self.can_colorize = _colorize.can_colorize()
7677

78+
def showsyntaxerror(self, filename=None):
79+
super().showsyntaxerror(colorize=self.can_colorize)
80+
7781
def showtraceback(self):
7882
super().showtraceback(colorize=self.can_colorize)
7983

84+
def runsource(self, source, filename="<input>", symbol="single"):
85+
try:
86+
tree = ast.parse(source)
87+
except (OverflowError, SyntaxError, ValueError):
88+
self.showsyntaxerror(filename)
89+
return False
90+
if tree.body:
91+
*_, last_stmt = tree.body
92+
for stmt in tree.body:
93+
wrapper = ast.Interactive if stmt is last_stmt else ast.Module
94+
the_symbol = symbol if stmt is last_stmt else "exec"
95+
item = wrapper([stmt])
96+
try:
97+
code = compile(item, filename, the_symbol)
98+
except (OverflowError, ValueError):
99+
self.showsyntaxerror(filename)
100+
return False
101+
102+
if code is None:
103+
return True
104+
105+
self.runcode(code)
106+
return False
107+
80108

81109
def run_multiline_interactive_console(
82110
mainmodule: ModuleType | None= None, future_flags: int = 0
@@ -144,10 +172,7 @@ def more_lines(unicodetext: str) -> bool:
144172

145173
input_name = f"<python-input-{input_n}>"
146174
linecache._register_code(input_name, statement, "<stdin>") # type: ignore[attr-defined]
147-
symbol = "single" if not contains_pasted_code else "exec"
148-
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol=symbol) # type: ignore[call-arg]
149-
if contains_pasted_code and more:
150-
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
175+
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
151176
assert not more
152177
input_n += 1
153178
except KeyboardInterrupt:

‎Lib/code.py

Copy file name to clipboardExpand all lines: Lib/code.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ def runcode(self, code):
9494
except:
9595
self.showtraceback()
9696

97-
def showsyntaxerror(self, filename=None):
97+
def showsyntaxerror(self, filename=None, **kwargs):
9898
"""Display the syntax error that just occurred.
9999
100100
This doesn't display a stack trace because there isn't one.
@@ -106,6 +106,7 @@ def showsyntaxerror(self, filename=None):
106106
The output is written by self.write(), below.
107107
108108
"""
109+
colorize = kwargs.pop('colorize', False)
109110
type, value, tb = sys.exc_info()
110111
sys.last_exc = value
111112
sys.last_type = type
@@ -123,7 +124,7 @@ def showsyntaxerror(self, filename=None):
123124
value = SyntaxError(msg, (filename, lineno, offset, line))
124125
sys.last_exc = sys.last_value = value
125126
if sys.excepthook is sys.__excepthook__:
126-
lines = traceback.format_exception_only(type, value)
127+
lines = traceback.format_exception_only(type, value, colorize=colorize)
127128
self.write(''.join(lines))
128129
else:
129130
# If someone has set sys.excepthook, we let that take precedence

‎Lib/test/test_pyrepl/test_interact.py

Copy file name to clipboard
+92Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import contextlib
2+
import io
3+
import unittest
4+
from unittest.mock import patch
5+
from textwrap import dedent
6+
7+
from test.support import force_not_colorized
8+
9+
from _pyrepl.simple_interact import InteractiveColoredConsole
10+
11+
12+
class TestSimpleInteract(unittest.TestCase):
13+
def test_multiple_statements(self):
14+
namespace = {}
15+
code = dedent("""\
16+
class A:
17+
def foo(self):
18+
19+
20+
pass
21+
22+
class B:
23+
def bar(self):
24+
pass
25+
26+
a = 1
27+
a
28+
""")
29+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
30+
with (
31+
patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror,
32+
patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource,
33+
):
34+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
35+
self.assertFalse(more)
36+
showsyntaxerror.assert_not_called()
37+
38+
39+
def test_multiple_statements_output(self):
40+
namespace = {}
41+
code = dedent("""\
42+
b = 1
43+
b
44+
a = 1
45+
a
46+
""")
47+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
48+
f = io.StringIO()
49+
with contextlib.redirect_stdout(f):
50+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
51+
self.assertFalse(more)
52+
self.assertEqual(f.getvalue(), "1\n")
53+
54+
def test_empty(self):
55+
namespace = {}
56+
code = ""
57+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
58+
f = io.StringIO()
59+
with contextlib.redirect_stdout(f):
60+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
61+
self.assertFalse(more)
62+
self.assertEqual(f.getvalue(), "")
63+
64+
def test_runsource_compiles_and_runs_code(self):
65+
console = InteractiveColoredConsole()
66+
source = "print('Hello, world!')"
67+
with patch.object(console, "runcode") as mock_runcode:
68+
console.runsource(source)
69+
mock_runcode.assert_called_once()
70+
71+
def test_runsource_returns_false_for_successful_compilation(self):
72+
console = InteractiveColoredConsole()
73+
source = "print('Hello, world!')"
74+
result = console.runsource(source)
75+
self.assertFalse(result)
76+
77+
@force_not_colorized
78+
def test_runsource_returns_false_for_failed_compilation(self):
79+
console = InteractiveColoredConsole()
80+
source = "print('Hello, world!'"
81+
f = io.StringIO()
82+
with contextlib.redirect_stderr(f):
83+
result = console.runsource(source)
84+
self.assertFalse(result)
85+
self.assertIn('SyntaxError', f.getvalue())
86+
87+
def test_runsource_shows_syntax_error_for_failed_compilation(self):
88+
console = InteractiveColoredConsole()
89+
source = "print('Hello, world!'"
90+
with patch.object(console, "showsyntaxerror") as mock_showsyntaxerror:
91+
console.runsource(source)
92+
mock_showsyntaxerror.assert_called_once()

‎Lib/test/test_traceback.py

Copy file name to clipboardExpand all lines: Lib/test/test_traceback.py
+1-1Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -543,7 +543,7 @@ def test_signatures(self):
543543

544544
self.assertEqual(
545545
str(inspect.signature(traceback.format_exception_only)),
546-
'(exc, /, value=<implicit>, *, show_group=False)')
546+
'(exc, /, value=<implicit>, *, show_group=False, **kwargs)')
547547

548548

549549
class PurePythonExceptionFormattingMixin:

‎Lib/traceback.py

Copy file name to clipboardExpand all lines: Lib/traceback.py
+3-2Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ def format_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \
155155
return list(te.format(chain=chain, colorize=colorize))
156156

157157

158-
def format_exception_only(exc, /, value=_sentinel, *, show_group=False):
158+
def format_exception_only(exc, /, value=_sentinel, *, show_group=False, **kwargs):
159159
"""Format the exception part of a traceback.
160160
161161
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):
170170
:exc:`BaseExceptionGroup`, the nested exceptions are included as
171171
well, recursively, with indentation relative to their nesting depth.
172172
"""
173+
colorize = kwargs.get("colorize", False)
173174
if value is _sentinel:
174175
value = exc
175176
te = TracebackException(type(value), value, None, compact=True)
176-
return list(te.format_exception_only(show_group=show_group))
177+
return list(te.format_exception_only(show_group=show_group, colorize=colorize))
177178

178179

179180
# -- not official API but folk probably use these two functions.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.