From 18aa905918b2cd007c6caf3b478e06d7892bbe54 Mon Sep 17 00:00:00 2001 From: Tian Gao Date: Tue, 20 Oct 2026 15:37:54 -0700 Subject: [PATCH] gh-141295: Persist pdb command history with PyREPL --- Doc/library/pdb.rst | 7 +++++++ Doc/using/cmdline.rst | 11 ++++++++++ Lib/pdb.py | 21 +++++++++++++++++++ Lib/test/test_pdb.py | 20 +++++++++++++++++- ...0-00-00-00.gh-issue-141295.pdb-history.rst | 4 ++++ Misc/python.man | 4 ++++ Python/initconfig.c | 1 + 7 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-141295.pdb-history.rst diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index 6a401a1af7eba38..dd6fd488a5f1533 100644 --- a/Doc/library/pdb.rst +++ b/Doc/library/pdb.rst @@ -75,6 +75,13 @@ The debugger's prompt is ``(Pdb)``, which is the indicator that you are in debug arguments of the ``p`` command. +.. versionadded:: 3.16 + When using PyREPL, debugger commands are saved in ``.python_history.pdb`` in the + user's home directory and loaded for subsequent sessions. If + :envvar:`PYTHON_HISTORY` is set, its value with ``.pdb`` appended is used + instead. Set :envvar:`PYTHON_HISTORY_PDB` to use a different history file. + + .. _pdb-cli: Command-line interface diff --git a/Doc/using/cmdline.rst b/Doc/using/cmdline.rst index e0ad7945a8ecef2..9ae056b26d9680f 100644 --- a/Doc/using/cmdline.rst +++ b/Doc/using/cmdline.rst @@ -1371,6 +1371,17 @@ conflict. .. versionadded:: 3.13 +.. envvar:: PYTHON_HISTORY_PDB + + Set the location of the command history file used by :mod:`pdb` with + PyREPL. By default, it is the value of :envvar:`PYTHON_HISTORY` with ``.pdb`` + appended, or ``.python_history.pdb`` in the user's home directory if + :envvar:`PYTHON_HISTORY` is unset or empty. + An empty value also selects the default. This variable is ignored with + :option:`-E` or :option:`-I`. + + .. versionadded:: 3.16 + .. envvar:: PYTHON_GIL If this variable is set to ``1``, the global interpreter lock (GIL) will be diff --git a/Lib/pdb.py b/Lib/pdb.py index e03f4fcf62cf3fa..c05cc338d0d005b 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -86,6 +86,7 @@ import typing import inspect import weakref +import warnings import builtins import tempfile import textwrap @@ -404,6 +405,22 @@ def __init__(self, pdb_instance, stdin, stdout, prompt): ) ) self.readline_wrapper.get_reader().gen_colors = self.gen_colors + self.history_file = self._get_history_file() + self.readline_wrapper.set_history_length(0) + try: + self.readline_wrapper.read_history_file(self.history_file) + except OSError: + pass + + @staticmethod + def _get_history_file(): + """Return the pdb history path, defaulting to Python's history plus .pdb.""" + if not sys.flags.ignore_environment: + history = os.environ.get("PYTHON_HISTORY_PDB") + if history: + return history + from site import gethistoryfile + return gethistoryfile() + '.pdb' def readline(self): @@ -430,6 +447,10 @@ def more_lines(text): '... ' + ' ' * (len(self.prompt) - 4) ) + '\n' ) + try: + self.readline_wrapper.append_history_file(self.history_file) + except (FileNotFoundError, PermissionError, OSError) as e: + warnings.warn(f"failed to open the history file for writing: {e}") return multiline except EOFError: return 'EOF' diff --git a/Lib/test/test_pdb.py b/Lib/test/test_pdb.py index 8876b3f5ce37a39..45e63c59f45be12 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -5296,9 +5296,27 @@ def test_interact_completion(self): @unittest.skipIf(not pdb._pyrepl_available(), "pyrepl is not available") class PdbTestReadlinePyREPL(PdbTestReadline): + @classmethod + def setUpClass(cls): + super().setUpClass() + directory = cls.enterClassContext(os_helper.temp_dir()) + cls.history_file = os.path.join(directory, 'history') + def _run_pty(self, script, input): # Override the env to make sure pyrepl is used in this test class - return super()._run_pty(script, input, env={**os.environ}) + env = os.environ | {'PYTHON_HISTORY_PDB': self.history_file} + return super()._run_pty(script, input, env=env) + + def test_history(self): + script = "import pdb; pdb.Pdb().set_trace()" + os_helper.unlink(self.history_file) + self._run_pty(script, b'p 6 * 7\ncontinue\n') + with open(self.history_file, encoding='utf-8') as file: + self.assertEqual(file.read().splitlines(), ['p 6 * 7', 'continue']) + + # Recall 'continue', then 'p 6 * 7' in a new debugger process. + output = self._run_pty(script, b'\x1bOA\x1bOA\ncontinue\n') + self.assertIn('42', output) def test_pyrepl_used(self): script = textwrap.dedent(""" diff --git a/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-141295.pdb-history.rst b/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-141295.pdb-history.rst new file mode 100644 index 000000000000000..7662a0327e9f51a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-20-00-00-00.gh-issue-141295.pdb-history.rst @@ -0,0 +1,4 @@ +Save and restore :mod:`pdb` command history when using PyREPL. The new +:envvar:`PYTHON_HISTORY_PDB` environment variable overrides the default +history path: :envvar:`PYTHON_HISTORY` with ``.pdb`` appended, or +``.python_history.pdb`` in the user's home directory. diff --git a/Misc/python.man b/Misc/python.man index 238d32099710173..44aac78ed9acb1a 100644 --- a/Misc/python.man +++ b/Misc/python.man @@ -568,6 +568,10 @@ on. Setting it to 0 forces the GIL off. Only available in builds configured with \fB\-\-disable\-gil\fP. .IP This is equivalent to the \fB\-X gil\fR option. +.IP PYTHON_HISTORY_PDB +Set the location of the pdb command history file when using PyREPL +(by default, PYTHON_HISTORY with .pdb appended, or .python_history.pdb in +the user's home directory). .IP PYTHON_HISTORY This environment variable can be used to set the location of a history file (on Unix, it is \fI~/.python_history\fP by default). diff --git a/Python/initconfig.c b/Python/initconfig.c index 363739a1a1b43c9..99dd431296d6887 100644 --- a/Python/initconfig.c +++ b/Python/initconfig.c @@ -525,6 +525,7 @@ static const char usage_envvars[] = " set to an integer in the range [0,4294967295] to get hash\n" " values with a predictable seed.\n" "#E{PYTHON_HISTORY} : the location of a .python_history file.\n" +"#E{PYTHON_HISTORY_PDB}: the location of a .python_history.pdb file used by PyREPL.\n" "#E{PYTHONHOME} : alternate directory (or #D).\n" " The default module search path uses #H.\n" "#E{PYTHONIOENCODING}: encoding[:errors] used for stdin/stdout/stderr\n"