Skip to content

Commit fcc44e6

Browse files
committed
gh-118893: Evaluate all statements in the new REPL separately
1 parent c4f9823 commit fcc44e6

File tree

2 files changed

+82
-5
lines changed

2 files changed

+82
-5
lines changed

Lib/_pyrepl/simple_interact.py

Lines changed: 22 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
@@ -77,6 +78,26 @@ def __init__(
7778
def showtraceback(self):
7879
super().showtraceback(colorize=self.can_colorize)
7980

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

81102
def run_multiline_interactive_console(
82103
mainmodule: ModuleType | None= None, future_flags: int = 0
@@ -144,10 +165,7 @@ def more_lines(unicodetext: str) -> bool:
144165

145166
input_name = f"<python-input-{input_n}>"
146167
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]
168+
more = console.push(_strip_final_indent(statement), filename=input_name, _symbol="single") # type: ignore[call-arg]
151169
assert not more
152170
input_n += 1
153171
except KeyboardInterrupt:

Lib/test/test_pyrepl.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import itertools
22
import os
33
import rlcompleter
4-
import sys
54
import tempfile
65
import unittest
76
from code import InteractiveConsole
87
from functools import partial
98
from unittest import TestCase
109
from unittest.mock import MagicMock, patch
10+
from textwrap import dedent
11+
import contextlib
12+
import io
1113

1214
from test.support import requires
1315
from test.support.import_helper import import_module
@@ -1002,5 +1004,62 @@ def test_up_arrow_after_ctrl_r(self):
10021004
self.assert_screen_equals(reader, "")
10031005

10041006

1007+
class TestSimpleInteract(unittest.TestCase):
1008+
def test_multiple_statements(self):
1009+
namespace = {}
1010+
code = dedent("""\
1011+
class A:
1012+
def foo(self):
1013+
1014+
1015+
pass
1016+
1017+
class B:
1018+
def bar(self):
1019+
pass
1020+
1021+
a = 1
1022+
a
1023+
""")
1024+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
1025+
with (
1026+
patch.object(InteractiveColoredConsole, "showsyntaxerror") as showsyntaxerror,
1027+
patch.object(InteractiveColoredConsole, "runsource", wraps=console.runsource) as runsource,
1028+
):
1029+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
1030+
self.assertFalse(more)
1031+
showsyntaxerror.assert_not_called()
1032+
1033+
1034+
def test_multiple_statements_output(self):
1035+
namespace = {}
1036+
code = dedent("""\
1037+
b = 1
1038+
b
1039+
a = 1
1040+
a
1041+
""")
1042+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
1043+
f = io.StringIO()
1044+
with contextlib.redirect_stdout(f):
1045+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
1046+
self.assertFalse(more)
1047+
self.assertEqual(f.getvalue(), "1\n")
1048+
1049+
def test_empty(self):
1050+
namespace = {}
1051+
code = ""
1052+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
1053+
f = io.StringIO()
1054+
with contextlib.redirect_stdout(f):
1055+
more = console.push(code, filename="<stdin>", _symbol="single") # type: ignore[call-arg]
1056+
self.assertFalse(more)
1057+
self.assertEqual(f.getvalue(), "")
1058+
1059+
1060+
if __name__ == '__main__':
1061+
unittest.main()
1062+
1063+
10051064
if __name__ == '__main__':
10061065
unittest.main()

0 commit comments

Comments
 (0)