Skip to content

Commit 5b718e7

Browse files
miss-islingtonzvynambv
authored
[3.13] gh-121790: Fix interactive console initialization (GH-121793) (GH-121822)
(cherry picked from commit e5c7216) Co-authored-by: Milan Oberkirch <milan.oberkirch@geops.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 0794220 commit 5b718e7

File tree

6 files changed

+85
-42
lines changed

6 files changed

+85
-42
lines changed

Lib/_pyrepl/main.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
2525
if not CAN_USE_PYREPL:
26-
if not os.environ.get('PYTHON_BASIC_REPL', None) and FAIL_REASON:
26+
if not os.getenv('PYTHON_BASIC_REPL') and FAIL_REASON:
2727
from .trace import trace
2828
trace(FAIL_REASON)
2929
print(FAIL_REASON, file=sys.stderr)
@@ -51,5 +51,7 @@ def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
5151
if not hasattr(sys, "ps2"):
5252
sys.ps2 = "... "
5353

54+
from .console import InteractiveColoredConsole
5455
from .simple_interact import run_multiline_interactive_console
55-
run_multiline_interactive_console(namespace)
56+
console = InteractiveColoredConsole(namespace, filename="<stdin>")
57+
run_multiline_interactive_console(console)

Lib/_pyrepl/readline.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
TYPE_CHECKING = False
5959

6060
if TYPE_CHECKING:
61-
from typing import Any
61+
from typing import Any, Mapping
6262

6363

6464
MoreLinesCallable = Callable[[str], bool]
@@ -559,7 +559,7 @@ def stub(*args: object, **kwds: object) -> None:
559559
# ____________________________________________________________
560560

561561

562-
def _setup(namespace: dict[str, Any]) -> None:
562+
def _setup(namespace: Mapping[str, Any]) -> None:
563563
global raw_input
564564
if raw_input is not None:
565565
return # don't run _setup twice
@@ -575,7 +575,9 @@ def _setup(namespace: dict[str, Any]) -> None:
575575
_wrapper.f_in = f_in
576576
_wrapper.f_out = f_out
577577

578-
# set up namespace in rlcompleter
578+
# set up namespace in rlcompleter, which requires it to be a bona fide dict
579+
if not isinstance(namespace, dict):
580+
namespace = dict(namespace)
579581
_wrapper.config.readline_completer = RLCompleter(namespace).complete
580582

581583
# this is not really what readline.c does. Better than nothing I guess

Lib/_pyrepl/simple_interact.py

+3-11
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,9 @@
2727

2828
import _sitebuiltins
2929
import linecache
30-
import builtins
3130
import sys
3231
import code
33-
from types import ModuleType
3432

35-
from .console import InteractiveColoredConsole
3633
from .readline import _get_reader, multiline_input
3734

3835
TYPE_CHECKING = False
@@ -82,17 +79,12 @@ def _clear_screen():
8279

8380

8481
def run_multiline_interactive_console(
85-
namespace: dict[str, Any],
82+
console: code.InteractiveConsole,
83+
*,
8684
future_flags: int = 0,
87-
console: code.InteractiveConsole | None = None,
8885
) -> None:
8986
from .readline import _setup
90-
_setup(namespace)
91-
92-
if console is None:
93-
console = InteractiveColoredConsole(
94-
namespace, filename="<stdin>"
95-
)
87+
_setup(console.locals)
9688
if future_flags:
9789
console.compile.compiler.flags |= future_flags
9890

Lib/asyncio/__main__.py

+9-18
Original file line numberDiff line numberDiff line change
@@ -97,30 +97,16 @@ def run(self):
9797
exec(startup_code, console.locals)
9898

9999
ps1 = getattr(sys, "ps1", ">>> ")
100-
if can_colorize():
100+
if can_colorize() and CAN_USE_PYREPL:
101101
ps1 = f"{ANSIColors.BOLD_MAGENTA}{ps1}{ANSIColors.RESET}"
102102
console.write(f"{ps1}import asyncio\n")
103103

104-
try:
105-
import errno
106-
if os.getenv("PYTHON_BASIC_REPL"):
107-
raise RuntimeError("user environment requested basic REPL")
108-
if not os.isatty(sys.stdin.fileno()):
109-
return_code = errno.ENOTTY
110-
raise OSError(return_code, "tty required", "stdin")
111-
112-
# This import will fail on operating systems with no termios.
104+
if CAN_USE_PYREPL:
113105
from _pyrepl.simple_interact import (
114-
check,
115106
run_multiline_interactive_console,
116107
)
117-
if err := check():
118-
raise RuntimeError(err)
119-
except Exception as e:
120-
console.interact(banner="", exitmsg="")
121-
else:
122108
try:
123-
run_multiline_interactive_console(console=console)
109+
run_multiline_interactive_console(console)
124110
except SystemExit:
125111
# expected via the `exit` and `quit` commands
126112
pass
@@ -129,6 +115,8 @@ def run(self):
129115
console.showtraceback()
130116
console.write("Internal error, ")
131117
return_code = 1
118+
else:
119+
console.interact(banner="", exitmsg="")
132120
finally:
133121
warnings.filterwarnings(
134122
'ignore',
@@ -139,7 +127,10 @@ def run(self):
139127

140128

141129
if __name__ == '__main__':
142-
CAN_USE_PYREPL = True
130+
if os.getenv('PYTHON_BASIC_REPL'):
131+
CAN_USE_PYREPL = False
132+
else:
133+
from _pyrepl.main import CAN_USE_PYREPL
143134

144135
return_code = 0
145136
loop = asyncio.new_event_loop()

Lib/site.py

+1-4
Original file line numberDiff line numberDiff line change
@@ -517,10 +517,7 @@ def register_readline():
517517
pass
518518

519519
if readline.get_current_history_length() == 0:
520-
try:
521-
from _pyrepl.main import CAN_USE_PYREPL
522-
except ImportError:
523-
CAN_USE_PYREPL = False
520+
from _pyrepl.main import CAN_USE_PYREPL
524521
# If no history was loaded, default to .python_history,
525522
# or PYTHON_HISTORY.
526523
# The guard is necessary to avoid doubling history size at

Lib/test/test_repl.py

+63-4
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,27 @@
11
"""Test the interactive interpreter."""
22

33
import os
4+
import select
45
import subprocess
56
import sys
67
import unittest
78
from textwrap import dedent
89
from test import support
9-
from test.support import cpython_only, has_subprocess_support, SuppressCrashReport
10-
from test.support.script_helper import assert_python_failure, kill_python, assert_python_ok
10+
from test.support import (
11+
cpython_only,
12+
has_subprocess_support,
13+
os_helper,
14+
SuppressCrashReport,
15+
SHORT_TIMEOUT,
16+
)
17+
from test.support.script_helper import kill_python
1118
from test.support.import_helper import import_module
1219

20+
try:
21+
import pty
22+
except ImportError:
23+
pty = None
24+
1325

1426
if not has_subprocess_support:
1527
raise unittest.SkipTest("test module requires subprocess")
@@ -195,9 +207,56 @@ def bar(x):
195207
expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'<stdin>\')"
196208
self.assertIn(expected, output, expected)
197209

198-
def test_asyncio_repl_no_tty_fails(self):
199-
assert assert_python_failure("-m", "asyncio")
210+
def test_asyncio_repl_reaches_python_startup_script(self):
211+
with os_helper.temp_dir() as tmpdir:
212+
script = os.path.join(tmpdir, "pythonstartup.py")
213+
with open(script, "w") as f:
214+
f.write("print('pythonstartup done!')" + os.linesep)
215+
f.write("exit(0)" + os.linesep)
216+
217+
env = os.environ.copy()
218+
env["PYTHONSTARTUP"] = script
219+
subprocess.check_call(
220+
[sys.executable, "-m", "asyncio"],
221+
stdout=subprocess.PIPE,
222+
stderr=subprocess.PIPE,
223+
env=env,
224+
timeout=SHORT_TIMEOUT,
225+
)
226+
227+
@unittest.skipUnless(pty, "requires pty")
228+
def test_asyncio_repl_is_ok(self):
229+
m, s = pty.openpty()
230+
cmd = [sys.executable, "-m", "asyncio"]
231+
proc = subprocess.Popen(
232+
cmd,
233+
stdin=s,
234+
stdout=s,
235+
stderr=s,
236+
text=True,
237+
close_fds=True,
238+
env=os.environ,
239+
)
240+
os.close(s)
241+
os.write(m, b"await asyncio.sleep(0)\n")
242+
os.write(m, b"exit()\n")
243+
output = []
244+
while select.select([m], [], [], SHORT_TIMEOUT)[0]:
245+
try:
246+
data = os.read(m, 1024).decode("utf-8")
247+
if not data:
248+
break
249+
except OSError:
250+
break
251+
output.append(data)
252+
os.close(m)
253+
try:
254+
exit_code = proc.wait(timeout=SHORT_TIMEOUT)
255+
except subprocess.TimeoutExpired:
256+
proc.kill()
257+
exit_code = proc.wait()
200258

259+
self.assertEqual(exit_code, 0)
201260

202261
class TestInteractiveModeSyntaxErrors(unittest.TestCase):
203262

0 commit comments

Comments
 (0)