https://github.com/python/cpython/commit/ae0d2875bcfa111383e39d9ba0d24c1c317ee597
commit: ae0d2875bcfa111383e39d9ba0d24c1c317ee597
branch: main
author: Steve Dower <[email protected]>
committer: ambv <[email protected]>
date: 2026-03-10T14:58:32+01:00
summary:

gh-145035: Allows removing the _pyrepl module to completely disable the modern 
REPL (GH-145159)

files:
A Misc/NEWS.d/next/Library/2026-02-23-21-28-12.gh-issue-145035.J5UjS6.rst
M Lib/_sitebuiltins.py
M Lib/asyncio/__main__.py
M Lib/pdb.py
M Lib/pydoc.py
M Lib/site.py
M Lib/test/support/__init__.py
M Lib/test/test_pyclbr.py
M Lib/test/test_pyrepl/__init__.py
M Lib/test/test_repl.py
M Modules/main.c

diff --git a/Lib/_sitebuiltins.py b/Lib/_sitebuiltins.py
index 81b36efc6c285f..84551e3546eb6e 100644
--- a/Lib/_sitebuiltins.py
+++ b/Lib/_sitebuiltins.py
@@ -65,7 +65,17 @@ def __repr__(self):
             return "Type %s() to see the full %s text" % ((self.__name,)*2)
 
     def __call__(self):
-        from _pyrepl.pager import get_pager
+        try:
+            from _pyrepl.pager import get_pager
+        except ModuleNotFoundError:
+            try:
+                from pydoc import get_pager
+            except ModuleNotFoundError:
+                def get_pager():
+                    def _print(text, title=None):
+                        print(text)
+                    return _print
+
         self.__setup()
 
         pager = get_pager()
diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py
index 44667efc522556..0bf3bdded40200 100644
--- a/Lib/asyncio/__main__.py
+++ b/Lib/asyncio/__main__.py
@@ -12,13 +12,16 @@
 import types
 import warnings
 
-from _colorize import get_theme
-from _pyrepl.console import InteractiveColoredConsole
+try:
+    from _colorize import get_theme
+    from _pyrepl.console import InteractiveColoredConsole as InteractiveConsole
+except ModuleNotFoundError:
+    from code import InteractiveConsole
 
 from . import futures
 
 
-class AsyncIOInteractiveConsole(InteractiveColoredConsole):
+class AsyncIOInteractiveConsole(InteractiveConsole):
 
     def __init__(self, locals, loop):
         super().__init__(locals, filename="<stdin>")
@@ -185,7 +188,10 @@ def interrupt(self) -> None:
     if os.getenv('PYTHON_BASIC_REPL'):
         CAN_USE_PYREPL = False
     else:
-        from _pyrepl.main import CAN_USE_PYREPL
+        try:
+            from _pyrepl.main import CAN_USE_PYREPL
+        except ModuleNotFoundError:
+            CAN_USE_PYREPL = False
 
     return_code = 0
     loop = asyncio.new_event_loop()
diff --git a/Lib/pdb.py b/Lib/pdb.py
index b5d8f827827415..7b08d2bb70183d 100644
--- a/Lib/pdb.py
+++ b/Lib/pdb.py
@@ -97,12 +97,16 @@
 import selectors
 import threading
 import _colorize
-import _pyrepl.utils
 
 from contextlib import ExitStack, closing, contextmanager
 from types import CodeType
 from warnings import deprecated
 
+try:
+    import _pyrepl.utils
+except ModuleNotFoundError:
+    _pyrepl = None
+
 
 class Restart(Exception):
     """Causes a debugger to be restarted for the debugged python program."""
@@ -1097,7 +1101,7 @@ def handle_command_def(self, line):
         return False
 
     def _colorize_code(self, code):
-        if self.colorize:
+        if self.colorize and _pyrepl:
             colors = list(_pyrepl.utils.gen_colors(code))
             chars, _ = _pyrepl.utils.disp_str(code, colors=colors, 
force_color=True)
             code = "".join(chars)
diff --git a/Lib/pydoc.py b/Lib/pydoc.py
index 69c83e085113c9..a1a6aad434ddf4 100644
--- a/Lib/pydoc.py
+++ b/Lib/pydoc.py
@@ -78,20 +78,41 @@ class or function within a module or module in a package.  
If the
 from reprlib import Repr
 from traceback import format_exception_only
 
-from _pyrepl.pager import (get_pager, pipe_pager,
-                           plain_pager, tempfile_pager, tty_pager)
-
-# Expose plain() as pydoc.plain()
-from _pyrepl.pager import plain  # noqa: F401
-
-
-# --------------------------------------------------------- old names
-
-getpager = get_pager
-pipepager = pipe_pager
-plainpager = plain_pager
-tempfilepager = tempfile_pager
-ttypager = tty_pager
+try:
+    from _pyrepl.pager import (get_pager, pipe_pager,
+                               plain_pager, tempfile_pager, tty_pager)
+
+    # Expose plain() as pydoc.plain()
+    from _pyrepl.pager import plain  # noqa: F401
+
+    # --------------------------------------------------------- old names
+    getpager = get_pager
+    pipepager = pipe_pager
+    plainpager = plain_pager
+    tempfilepager = tempfile_pager
+    ttypager = tty_pager
+
+except ModuleNotFoundError:
+    # Minimal alternatives for cases where _pyrepl is absent.
+
+    def plain(text: str) -> str:
+        """Remove boldface formatting from text."""
+        return re.sub('.\b', '', text)
+
+    def plain_pager(text: str, title: str = '') -> None:
+        """Simply print unformatted text.  This is the ultimate fallback."""
+        encoding = getattr(sys.stdout, 'encoding', None) or 'utf-8'
+        text = text.encode(encoding, 'backslashreplace').decode(encoding)
+        text = plain(text)
+        sys.stdout.write(text)
+
+    def get_pager():
+        """Unconditionally return the plain pager, since _pyrepl is absent."""
+        return plain_pager
+
+    # --------------------------------------------------------- old names
+    getpager = get_pager
+    plainpager = plain_pager
 
 
 # --------------------------------------------------------- common routines
diff --git a/Lib/site.py b/Lib/site.py
index 5f09a7dd8c91c0..30015b3f26b4b3 100644
--- a/Lib/site.py
+++ b/Lib/site.py
@@ -529,6 +529,8 @@ def register_readline():
                     import _pyrepl.unix_console
                     console_errors = _pyrepl.unix_console._error
                 from _pyrepl.main import CAN_USE_PYREPL
+            except ModuleNotFoundError:
+                CAN_USE_PYREPL = False
             finally:
                 sys.path = original_path
     except ImportError:
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index d4d3c7f1aefa66..3da662b0c4d50a 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -3023,6 +3023,13 @@ def force_color(color: bool):
     import _colorize
     from .os_helper import EnvironmentVarGuard
 
+    if color:
+        try:
+            import _pyrepl  # noqa: F401
+        except ModuleNotFoundError:
+            # Can't force enable color without _pyrepl, so just skip.
+            raise unittest.SkipTest("_pyrepl is missing")
+
     with (
         swap_attr(_colorize, "can_colorize", lambda *, file=None: color),
         EnvironmentVarGuard() as env,
diff --git a/Lib/test/test_pyclbr.py b/Lib/test/test_pyclbr.py
index 79ef178f3807f4..b5ec41b17f793b 100644
--- a/Lib/test/test_pyclbr.py
+++ b/Lib/test/test_pyclbr.py
@@ -252,7 +252,8 @@ def test_others(self):
                 ignore=('Union', '_ModuleTarget', '_ScriptTarget', 
'_ZipTarget', 'curframe_locals',
                         '_InteractState', 'rlcompleter'),
             )
-        cm('pydoc', ignore=('input', 'output',))  # properties
+        cm('pydoc', ignore=('input', 'output',  # properties
+                            'getpager', 'plainpager', )) # aliases
 
         # Tests for modules inside packages
         cm('email.parser')
diff --git a/Lib/test/test_pyrepl/__init__.py b/Lib/test/test_pyrepl/__init__.py
index 2f37bff6df8b4a..1534d63352cc55 100644
--- a/Lib/test/test_pyrepl/__init__.py
+++ b/Lib/test/test_pyrepl/__init__.py
@@ -3,6 +3,9 @@
 from test.support import import_helper, load_package_tests
 
 
+import_helper.import_module("_pyrepl")
+
+
 if sys.platform != "win32":
     import_helper.import_module("termios")
 
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index 40965835bcec00..27cd125078ea69 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -426,6 +426,13 @@ def test_toplevel_contextvars_async(self):
         p = spawn_asyncio_repl()
         p.stdin.write(user_input)
         user_input2 = "async def set_var(): var.set('ok')\n"
+        try:
+            import _pyrepl # noqa: F401
+        except ModuleNotFoundError:
+            # If we're going to be forced into the regular REPL, then we need 
an
+            # extra newline here. Omit it by default to catch any breakage to
+            # the new REPL's behavior.
+            user_input2 += "\n"
         p.stdin.write(user_input2)
         user_input3 = "await set_var()\n"
         p.stdin.write(user_input3)
diff --git 
a/Misc/NEWS.d/next/Library/2026-02-23-21-28-12.gh-issue-145035.J5UjS6.rst 
b/Misc/NEWS.d/next/Library/2026-02-23-21-28-12.gh-issue-145035.J5UjS6.rst
new file mode 100644
index 00000000000000..b20da3b54c0415
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-02-23-21-28-12.gh-issue-145035.J5UjS6.rst
@@ -0,0 +1,3 @@
+Allows omitting the internal library ``_pyrepl`` with limited loss of
+functionality. This allows complete removal of the modern REPL, which is an
+unsupported configuration, but still desirable for some distributions.
diff --git a/Modules/main.c b/Modules/main.c
index 95ba541d98c2e8..7731fa0c7c6717 100644
--- a/Modules/main.c
+++ b/Modules/main.c
@@ -562,13 +562,25 @@ pymain_run_stdin(PyConfig *config)
         return pymain_exit_err_print();
     }
 
-    if (!isatty(fileno(stdin))
-        || _Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) {
-        PyCompilerFlags cf = _PyCompilerFlags_INIT;
-        int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
-        return (run != 0);
+    int run;
+    if (isatty(fileno(stdin))
+        && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) {
+        PyObject *pyrepl = PyImport_ImportModule("_pyrepl");
+        if (pyrepl != NULL) {
+            run = pymain_start_pyrepl(0);
+            Py_DECREF(pyrepl);
+            return run;
+        }
+        if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) {
+            fprintf(stderr, "Could not import _pyrepl.main\n");
+            return pymain_exit_err_print();
+        }
+        PyErr_Clear();
     }
-    return pymain_start_pyrepl(0);
+
+    PyCompilerFlags cf = _PyCompilerFlags_INIT;
+    run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
+    return (run != 0);
 }
 
 
@@ -594,14 +606,24 @@ pymain_repl(PyConfig *config, int *exitcode)
         return;
     }
 
-    if (!isatty(fileno(stdin))
-        || _Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) {
-        PyCompilerFlags cf = _PyCompilerFlags_INIT;
-        int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
-        *exitcode = (run != 0);
-        return;
+    if (isatty(fileno(stdin))
+        && !_Py_GetEnv(config->use_environment, "PYTHON_BASIC_REPL")) {
+        PyObject *pyrepl = PyImport_ImportModule("_pyrepl");
+        if (pyrepl != NULL) {
+            int run = pymain_start_pyrepl(1);
+            *exitcode = (run != 0);
+            Py_DECREF(pyrepl);
+            return;
+        }
+        if (!PyErr_ExceptionMatches(PyExc_ModuleNotFoundError)) {
+            PyErr_Clear();
+            fprintf(stderr, "Could not import _pyrepl.main\n");
+            return;
+        }
+        PyErr_Clear();
     }
-    int run = pymain_start_pyrepl(1);
+    PyCompilerFlags cf = _PyCompilerFlags_INIT;
+    int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
     *exitcode = (run != 0);
     return;
 }

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to