Skip to content

Commit ae0d287

Browse files
authored
gh-145035: Allows removing the _pyrepl module to completely disable the modern REPL (GH-145159)
1 parent 2d35f9b commit ae0d287

File tree

11 files changed

+121
-35
lines changed

11 files changed

+121
-35
lines changed

Lib/_sitebuiltins.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,17 @@ def __repr__(self):
6565
return "Type %s() to see the full %s text" % ((self.__name,)*2)
6666

6767
def __call__(self):
68-
from _pyrepl.pager import get_pager
68+
try:
69+
from _pyrepl.pager import get_pager
70+
except ModuleNotFoundError:
71+
try:
72+
from pydoc import get_pager
73+
except ModuleNotFoundError:
74+
def get_pager():
75+
def _print(text, title=None):
76+
print(text)
77+
return _print
78+
6979
self.__setup()
7080

7181
pager = get_pager()

Lib/asyncio/__main__.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,16 @@
1212
import types
1313
import warnings
1414

15-
from _colorize import get_theme
16-
from _pyrepl.console import InteractiveColoredConsole
15+
try:
16+
from _colorize import get_theme
17+
from _pyrepl.console import InteractiveColoredConsole as InteractiveConsole
18+
except ModuleNotFoundError:
19+
from code import InteractiveConsole
1720

1821
from . import futures
1922

2023

21-
class AsyncIOInteractiveConsole(InteractiveColoredConsole):
24+
class AsyncIOInteractiveConsole(InteractiveConsole):
2225

2326
def __init__(self, locals, loop):
2427
super().__init__(locals, filename="<stdin>")
@@ -185,7 +188,10 @@ def interrupt(self) -> None:
185188
if os.getenv('PYTHON_BASIC_REPL'):
186189
CAN_USE_PYREPL = False
187190
else:
188-
from _pyrepl.main import CAN_USE_PYREPL
191+
try:
192+
from _pyrepl.main import CAN_USE_PYREPL
193+
except ModuleNotFoundError:
194+
CAN_USE_PYREPL = False
189195

190196
return_code = 0
191197
loop = asyncio.new_event_loop()

Lib/pdb.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,16 @@
9797
import selectors
9898
import threading
9999
import _colorize
100-
import _pyrepl.utils
101100

102101
from contextlib import ExitStack, closing, contextmanager
103102
from types import CodeType
104103
from warnings import deprecated
105104

105+
try:
106+
import _pyrepl.utils
107+
except ModuleNotFoundError:
108+
_pyrepl = None
109+
106110

107111
class Restart(Exception):
108112
"""Causes a debugger to be restarted for the debugged python program."""
@@ -1097,7 +1101,7 @@ def handle_command_def(self, line):
10971101
return False
10981102

10991103
def _colorize_code(self, code):
1100-
if self.colorize:
1104+
if self.colorize and _pyrepl:
11011105
colors = list(_pyrepl.utils.gen_colors(code))
11021106
chars, _ = _pyrepl.utils.disp_str(code, colors=colors, force_color=True)
11031107
code = "".join(chars)

Lib/pydoc.py

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -78,20 +78,41 @@ class or function within a module or module in a package. If the
7878
from reprlib import Repr
7979
from traceback import format_exception_only
8080

81-
from _pyrepl.pager import (get_pager, pipe_pager,
82-
plain_pager, tempfile_pager, tty_pager)
83-
84-
# Expose plain() as pydoc.plain()
85-
from _pyrepl.pager import plain # noqa: F401
86-
87-
88-
# --------------------------------------------------------- old names
89-
90-
getpager = get_pager
91-
pipepager = pipe_pager
92-
plainpager = plain_pager
93-
tempfilepager = tempfile_pager
94-
ttypager = tty_pager
81+
try:
82+
from _pyrepl.pager import (get_pager, pipe_pager,
83+
plain_pager, tempfile_pager, tty_pager)
84+
85+
# Expose plain() as pydoc.plain()
86+
from _pyrepl.pager import plain # noqa: F401
87+
88+
# --------------------------------------------------------- old names
89+
getpager = get_pager
90+
pipepager = pipe_pager
91+
plainpager = plain_pager
92+
tempfilepager = tempfile_pager
93+
ttypager = tty_pager
94+
95+
except ModuleNotFoundError:
96+
# Minimal alternatives for cases where _pyrepl is absent.
97+
98+
def plain(text: str) -> str:
99+
"""Remove boldface formatting from text."""
100+
return re.sub('.\b', '', text)
101+
102+
def plain_pager(text: str, title: str = '') -> None:
103+
"""Simply print unformatted text. This is the ultimate fallback."""
104+
encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
105+
text = text.encode(encoding, 'backslashreplace').decode(encoding)
106+
text = plain(text)
107+
sys.stdout.write(text)
108+
109+
def get_pager():
110+
"""Unconditionally return the plain pager, since _pyrepl is absent."""
111+
return plain_pager
112+
113+
# --------------------------------------------------------- old names
114+
getpager = get_pager
115+
plainpager = plain_pager
95116

96117

97118
# --------------------------------------------------------- common routines

Lib/site.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,8 @@ def register_readline():
529529
import _pyrepl.unix_console
530530
console_errors = _pyrepl.unix_console._error
531531
from _pyrepl.main import CAN_USE_PYREPL
532+
except ModuleNotFoundError:
533+
CAN_USE_PYREPL = False
532534
finally:
533535
sys.path = original_path
534536
except ImportError:

Lib/test/support/__init__.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3023,6 +3023,13 @@ def force_color(color: bool):
30233023
import _colorize
30243024
from .os_helper import EnvironmentVarGuard
30253025

3026+
if color:
3027+
try:
3028+
import _pyrepl # noqa: F401
3029+
except ModuleNotFoundError:
3030+
# Can't force enable color without _pyrepl, so just skip.
3031+
raise unittest.SkipTest("_pyrepl is missing")
3032+
30263033
with (
30273034
swap_attr(_colorize, "can_colorize", lambda *, file=None: color),
30283035
EnvironmentVarGuard() as env,

Lib/test/test_pyclbr.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,8 @@ def test_others(self):
252252
ignore=('Union', '_ModuleTarget', '_ScriptTarget', '_ZipTarget', 'curframe_locals',
253253
'_InteractState', 'rlcompleter'),
254254
)
255-
cm('pydoc', ignore=('input', 'output',)) # properties
255+
cm('pydoc', ignore=('input', 'output', # properties
256+
'getpager', 'plainpager', )) # aliases
256257

257258
# Tests for modules inside packages
258259
cm('email.parser')

Lib/test/test_pyrepl/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
from test.support import import_helper, load_package_tests
44

55

6+
import_helper.import_module("_pyrepl")
7+
8+
69
if sys.platform != "win32":
710
import_helper.import_module("termios")
811

Lib/test/test_repl.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,13 @@ def test_toplevel_contextvars_async(self):
426426
p = spawn_asyncio_repl()
427427
p.stdin.write(user_input)
428428
user_input2 = "async def set_var(): var.set('ok')\n"
429+
try:
430+
import _pyrepl # noqa: F401
431+
except ModuleNotFoundError:
432+
# If we're going to be forced into the regular REPL, then we need an
433+
# extra newline here. Omit it by default to catch any breakage to
434+
# the new REPL's behavior.
435+
user_input2 += "\n"
429436
p.stdin.write(user_input2)
430437
user_input3 = "await set_var()\n"
431438
p.stdin.write(user_input3)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Allows omitting the internal library ``_pyrepl`` with limited loss of
2+
functionality. This allows complete removal of the modern REPL, which is an
3+
unsupported configuration, but still desirable for some distributions.

0 commit comments

Comments
 (0)