]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-145035: Allows removing the _pyrepl module to completely disable the modern REPL...
authorSteve Dower <steve.dower@python.org>
Tue, 10 Mar 2026 13:58:32 +0000 (13:58 +0000)
committerGitHub <noreply@github.com>
Tue, 10 Mar 2026 13:58:32 +0000 (14:58 +0100)
Lib/_sitebuiltins.py
Lib/asyncio/__main__.py
Lib/pdb.py
Lib/pydoc.py
Lib/site.py
Lib/test/support/__init__.py
Lib/test/test_pyclbr.py
Lib/test/test_pyrepl/__init__.py
Lib/test/test_repl.py
Misc/NEWS.d/next/Library/2026-02-23-21-28-12.gh-issue-145035.J5UjS6.rst [new file with mode: 0644]
Modules/main.c

index 81b36efc6c285f0143e2555237d5e24a2b0dbaa6..84551e3546eb6eef796f6d39a40dea05c02df1b5 100644 (file)
@@ -65,7 +65,17 @@ class _Printer(object):
             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()
index 44667efc52255640f6ff8549aa30a5de8589cfdb..0bf3bdded40200a9b053de92f711f9260c78d68a 100644 (file)
@@ -12,13 +12,16 @@ import threading
 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 @@ if __name__ == '__main__':
     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()
index b5d8f827827415ef38552dccf439ac51477f9ac0..7b08d2bb70183dd5b56cbbc19429447ec244e66c 100644 (file)
@@ -97,12 +97,16 @@ import linecache
 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 @@ class Pdb(bdb.Bdb, cmd.Cmd):
         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)
index 69c83e085113c901b33f34d6a5e8ffaca28460b6..a1a6aad434ddf4dedd020d00c8b19a512b8bf3e9 100644 (file)
@@ -78,20 +78,41 @@ from collections import deque
 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
index 5f09a7dd8c91c02918d3cffe004a50f312885c6a..30015b3f26b4b3a3493e3fad22e8a17a055075b3 100644 (file)
@@ -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:
index d4d3c7f1aefa6606a331bb5fd79493317b91ae5b..3da662b0c4d50a6451ab99f3f7d393ae2b06ddc8 100644 (file)
@@ -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,
index 79ef178f3807f410dbb961585104031f278672e1..b5ec41b17f793b6f053debd8c930cd7a58f87348 100644 (file)
@@ -252,7 +252,8 @@ class PyclbrTest(TestCase):
                 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')
index 2f37bff6df8b4a4008678be3d1f7622e33aee323..1534d63352cc555f891ff8a4f7cef1033f938388 100644 (file)
@@ -3,6 +3,9 @@ import sys
 from test.support import import_helper, load_package_tests
 
 
+import_helper.import_module("_pyrepl")
+
+
 if sys.platform != "win32":
     import_helper.import_module("termios")
 
index 40965835bcec00cb7a0ee20b62d037baeaf294fa..27cd125078ea69c7acdcec2e311888d99f5b9392 100644 (file)
@@ -426,6 +426,13 @@ class TestAsyncioREPL(unittest.TestCase):
         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 (file)
index 0000000..b20da3b
--- /dev/null
@@ -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.
index 95ba541d98c2e84175975930ab332aa88b93df8f..7731fa0c7c67178218492b7dacbdf9d28997d3f5 100644 (file)
@@ -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;
 }