summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/readline.rst29
-rw-r--r--Lib/test/test_readline.py40
-rw-r--r--Misc/NEWS2
-rw-r--r--Modules/readline.c37
4 files changed, 106 insertions, 2 deletions
diff --git a/Doc/library/readline.rst b/Doc/library/readline.rst
index 692310b..3864f0d 100644
--- a/Doc/library/readline.rst
+++ b/Doc/library/readline.rst
@@ -59,6 +59,14 @@ The :mod:`readline` module defines the following functions:
Save a readline history file. The default filename is :file:`~/.history`.
+.. function:: append_history_file(nelements[, filename])
+
+ Append the last *nelements* of history to a file. The default filename is
+ :file:`~/.history`. The file must already exist.
+
+ .. versionadded:: 3.5
+
+
.. function:: clear_history()
Clear the current history. (Note: this function is not available if the
@@ -209,6 +217,26 @@ from the user's :envvar:`PYTHONSTARTUP` file. ::
This code is actually automatically run when Python is run in
:ref:`interactive mode <tut-interactive>` (see :ref:`rlcompleter-config`).
+The following example achieves the same goal but supports concurrent interactive
+sessions, by only appending the new history. ::
+
+ import atexit
+ import os
+ import realine
+ histfile = os.path.join(os.path.expanduser("~"), ".python_history")
+
+ try:
+ readline.read_history_file(histfile)
+ h_len = readline.get_history_length()
+ except FileNotFoundError:
+ open(histfile, 'wb').close()
+ h_len = 0
+
+ def save(prev_h_len, histfile):
+ new_h_len = readline.get_history_length()
+ readline.append_history_file(new_h_len - prev_h_len, histfile)
+ atexit.register(save, h_len, histfile)
+
The following example extends the :class:`code.InteractiveConsole` class to
support history save/restore. ::
@@ -234,4 +262,3 @@ support history save/restore. ::
def save_history(self, histfile):
readline.write_history_file(histfile)
-
diff --git a/Lib/test/test_readline.py b/Lib/test/test_readline.py
index d2a11f2..e5be02e 100644
--- a/Lib/test/test_readline.py
+++ b/Lib/test/test_readline.py
@@ -2,8 +2,9 @@
Very minimal unittests for parts of the readline module.
"""
import os
+import tempfile
import unittest
-from test.support import run_unittest, import_module
+from test.support import run_unittest, import_module, unlink
from test.script_helper import assert_python_ok
# Skip tests if there is no readline module
@@ -42,6 +43,43 @@ class TestHistoryManipulation (unittest.TestCase):
self.assertEqual(readline.get_current_history_length(), 1)
+ def test_write_read_append(self):
+ hfile = tempfile.NamedTemporaryFile(delete=False)
+ hfile.close()
+ hfilename = hfile.name
+ self.addCleanup(unlink, hfilename)
+
+ # test write-clear-read == nop
+ readline.clear_history()
+ readline.add_history("first line")
+ readline.add_history("second line")
+ readline.write_history_file(hfilename)
+
+ readline.clear_history()
+ self.assertEqual(readline.get_current_history_length(), 0)
+
+ readline.read_history_file(hfilename)
+ self.assertEqual(readline.get_current_history_length(), 2)
+ self.assertEqual(readline.get_history_item(1), "first line")
+ self.assertEqual(readline.get_history_item(2), "second line")
+
+ # test append
+ readline.append_history_file(1, hfilename)
+ readline.clear_history()
+ readline.read_history_file(hfilename)
+ self.assertEqual(readline.get_current_history_length(), 3)
+ self.assertEqual(readline.get_history_item(1), "first line")
+ self.assertEqual(readline.get_history_item(2), "second line")
+ self.assertEqual(readline.get_history_item(3), "second line")
+
+ # test 'no such file' behaviour
+ os.unlink(hfilename)
+ with self.assertRaises(FileNotFoundError):
+ readline.append_history_file(1, hfilename)
+
+ # write_history_file can create the target
+ readline.write_history_file(hfilename)
+
class TestReadline(unittest.TestCase):
diff --git a/Misc/NEWS b/Misc/NEWS
index 2662e60..75260e0 100644
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -191,6 +191,8 @@ Core and Builtins
Library
-------
+- Issue #22940: Add readline.append_history_file.
+
- Issue #19676: Added the "namereplace" error handler.
- Issue #22788: Add *context* parameter to logging.handlers.HTTPHandler.
diff --git a/Modules/readline.c b/Modules/readline.c
index f349d3b..b5a0eba 100644
--- a/Modules/readline.c
+++ b/Modules/readline.c
@@ -237,6 +237,41 @@ Save a readline history file.\n\
The default filename is ~/.history.");
+/* Exported function to save part of a readline history file */
+
+static PyObject *
+append_history_file(PyObject *self, PyObject *args)
+{
+ int nelements;
+ PyObject *filename_obj = Py_None, *filename_bytes;
+ char *filename;
+ int err;
+ if (!PyArg_ParseTuple(args, "i|O:append_history_file", &nelements, &filename_obj))
+ return NULL;
+ if (filename_obj != Py_None) {
+ if (!PyUnicode_FSConverter(filename_obj, &filename_bytes))
+ return NULL;
+ filename = PyBytes_AsString(filename_bytes);
+ } else {
+ filename_bytes = NULL;
+ filename = NULL;
+ }
+ errno = err = append_history(nelements, filename);
+ if (!err && _history_length >= 0)
+ history_truncate_file(filename, _history_length);
+ Py_XDECREF(filename_bytes);
+ errno = err;
+ if (errno)
+ return PyErr_SetFromErrno(PyExc_IOError);
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(doc_append_history_file,
+"append_history_file(nelements[, filename]) -> None\n\
+Append the last nelements of the history list to file.\n\
+The default filename is ~/.history.");
+
+
/* Set history length */
static PyObject*
@@ -747,6 +782,8 @@ static struct PyMethodDef readline_methods[] =
METH_VARARGS, doc_read_history_file},
{"write_history_file", write_history_file,
METH_VARARGS, doc_write_history_file},
+ {"append_history_file", append_history_file,
+ METH_VARARGS, doc_append_history_file},
{"get_history_item", get_history_item,
METH_VARARGS, doc_get_history_item},
{"get_current_history_length", (PyCFunction)get_current_history_length,