diff --git a/Doc/library/pdb.rst b/Doc/library/pdb.rst index c48f2319b43c5b9..0f9ac7ca32053eb 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 114634a4469c131..12c4070c5e3744b 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -79,6 +79,7 @@ import codeop import typing import inspect +import warnings import builtins import itertools import linecache @@ -399,6 +400,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): @@ -425,6 +442,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 cfcc8c6df57b948..11b9314b86255f9 100644 --- a/Lib/test/test_pdb.py +++ b/Lib/test/test_pdb.py @@ -5393,9 +5393,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 225ee0a3fe5ff2a..b87d07396fc5147 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"