Skip to content

Commit 92cd026

Browse files
gh-157485: Add structured version info for readline
Add constants readline.READLINE_VERSION_INFO, readline.readline_version_info and readline.readline_version, which provide information about the version of the Readline library in use. The private names _READLINE_VERSION, _READLINE_RUNTIME_VERSION and _READLINE_LIBRARY_VERSION are kept for backward compatibility. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a844747 commit 92cd026

6 files changed

Lines changed: 176 additions & 43 deletions

File tree

Doc/library/readline.rst

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,38 @@ Readline library in general.
5757

5858
.. versionadded:: 3.13
5959

60+
.. data:: READLINE_VERSION_INFO
61+
62+
A named tuple containing the two components of the Readline library
63+
version that was used for building the module: *major* and *minor*.
64+
Both values are integers.
65+
The components can also be accessed by name,
66+
so ``readline.READLINE_VERSION_INFO[0]`` is equivalent to
67+
``readline.READLINE_VERSION_INFO.major`` and so on.
68+
This may be different from the Readline library actually used at runtime,
69+
which is available as :const:`readline_version_info`.
70+
71+
With the ``editline`` backend, this is the version of the Readline
72+
interface emulated by libedit, not the version of libedit.
73+
74+
.. versionadded:: next
75+
76+
.. data:: readline_version_info
77+
78+
A named tuple containing the version of the Readline library
79+
actually loaded by the interpreter,
80+
with the same fields as :const:`READLINE_VERSION_INFO`.
81+
82+
.. versionadded:: next
83+
84+
.. data:: readline_version
85+
86+
The version string of the Readline library actually loaded by the
87+
interpreter, like ``'8.3'``.
88+
With the ``editline`` backend, this is ``'EditLine wrapper'``.
89+
90+
.. versionadded:: next
91+
6092
Init file
6193
---------
6294

Doc/whatsnew/3.16.rst

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,16 @@ pydoc
514514
(Contributed by Serhiy Storchaka in :gh:`153906`.)
515515

516516

517+
readline
518+
--------
519+
520+
* Added constants :const:`~readline.READLINE_VERSION_INFO`,
521+
:const:`~readline.readline_version_info` and
522+
:const:`~readline.readline_version`, which provide information about
523+
the version of the Readline library in use.
524+
(Contributed by Serhiy Storchaka in :gh:`157485`.)
525+
526+
517527
re
518528
--
519529

Lib/test/pythoninfo.py

Lines changed: 5 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -427,27 +427,13 @@ def collect_readline(info_add):
427427
except ImportError:
428428
return
429429

430-
def format_attr(attr, value):
431-
if isinstance(value, int):
432-
return "%#x" % value
433-
else:
434-
return value
435-
436430
attributes = (
437-
"_READLINE_VERSION",
438-
"_READLINE_RUNTIME_VERSION",
439-
"_READLINE_LIBRARY_VERSION",
431+
"backend",
432+
"READLINE_VERSION_INFO",
433+
"readline_version_info",
434+
"readline_version",
440435
)
441-
copy_attributes(info_add, readline, 'readline.%s', attributes,
442-
formatter=format_attr)
443-
444-
if not hasattr(readline, "_READLINE_LIBRARY_VERSION"):
445-
# _READLINE_LIBRARY_VERSION has been added to CPython 3.7
446-
doc = getattr(readline, '__doc__', '')
447-
if 'libedit readline' in doc:
448-
info_add('readline.library', 'libedit readline')
449-
elif 'GNU readline' in doc:
450-
info_add('readline.library', 'GNU readline')
436+
copy_attributes(info_add, readline, 'readline.%s', attributes)
451437

452438

453439
def run_command(cmd, check=True, **kwargs):

Lib/test/test_readline.py

Lines changed: 35 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,14 @@
1919
# Skip tests if there is no readline module
2020
readline = import_module('readline')
2121

22-
if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
23-
is_editline = ("EditLine wrapper" in readline._READLINE_LIBRARY_VERSION)
24-
else:
25-
is_editline = readline.backend == "editline"
22+
is_editline = readline.backend == "editline"
2623

2724

2825
def setUpModule():
2926
if verbose:
30-
# Python implementations other than CPython may not have
31-
# these private attributes
32-
if hasattr(readline, "_READLINE_VERSION"):
33-
print(f"readline version: {readline._READLINE_VERSION:#x}")
34-
print(f"readline runtime version: {readline._READLINE_RUNTIME_VERSION:#x}")
35-
if hasattr(readline, "_READLINE_LIBRARY_VERSION"):
36-
print(f"readline library version: {readline._READLINE_LIBRARY_VERSION!r}")
27+
print(f"readline version: {readline.READLINE_VERSION_INFO}")
28+
print(f"readline runtime version: {readline.readline_version_info}")
29+
print(f"readline library version: {readline.readline_version!r}")
3730
print(f"use libedit emulation? {is_editline}")
3831

3932

@@ -210,7 +203,36 @@ def test_append_limited_history(self):
210203

211204
class TestReadline(unittest.TestCase):
212205

213-
@unittest.skipIf(readline._READLINE_VERSION < 0x0601 and not is_editline,
206+
def _test_readline_version(self, v):
207+
self.assertIsInstance(v[:], tuple)
208+
self.assertEqual(len(v), 2)
209+
self.assertIsInstance(v[0], int)
210+
self.assertIsInstance(v[1], int)
211+
self.assertIsInstance(v.major, int)
212+
self.assertIsInstance(v.minor, int)
213+
self.assertEqual(v[0], v.major)
214+
self.assertEqual(v[1], v.minor)
215+
self.assertGreaterEqual(v.major, 4)
216+
self.assertGreaterEqual(v.minor, 0)
217+
218+
def test_readline_version(self):
219+
self._test_readline_version(readline.READLINE_VERSION_INFO)
220+
self._test_readline_version(readline.readline_version_info)
221+
self.assertIsInstance(readline.readline_version, str)
222+
if not is_editline:
223+
self.assertEqual(readline.readline_version,
224+
'%d.%d' % readline.readline_version_info)
225+
# Private names kept for backward compatibility.
226+
self.assertEqual(readline._READLINE_VERSION,
227+
readline.READLINE_VERSION_INFO.major << 8 |
228+
readline.READLINE_VERSION_INFO.minor)
229+
self.assertEqual(readline._READLINE_RUNTIME_VERSION,
230+
readline.readline_version_info.major << 8 |
231+
readline.readline_version_info.minor)
232+
self.assertIs(readline._READLINE_LIBRARY_VERSION,
233+
readline.readline_version)
234+
235+
@unittest.skipIf(readline.READLINE_VERSION_INFO < (6, 1) and not is_editline,
214236
"not supported in this library version")
215237
def test_init(self):
216238
# Issue #19884: Ensure that the ANSI sequence "\033[1034h" is not
@@ -366,7 +388,7 @@ def display(substitution, matches, longest_match_length):
366388
# See https://cnswww.cns.cwru.edu/php/chet/readline/CHANGES
367389
# - editline: history size is broken on OS X 10.11.6.
368390
# Newer versions were not tested yet.
369-
@unittest.skipIf(readline._READLINE_VERSION < 0x600,
391+
@unittest.skipIf(readline.READLINE_VERSION_INFO < (6, 0),
370392
"this readline version does not support history-size")
371393
@unittest.skipIf(is_editline,
372394
"editline history size configuration is broken")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Add constants :const:`readline.READLINE_VERSION_INFO`,
2+
:const:`readline.readline_version_info` and :const:`readline.readline_version`,
3+
which provide information about the version of the Readline library in use.

Modules/readline.c

Lines changed: 91 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1802,6 +1802,96 @@ call_readline(FILE *sys_stdin, FILE *sys_stdout, const char *prompt)
18021802
}
18031803

18041804

1805+
PyDoc_STRVAR(readline_version_info__doc__,
1806+
"readline.readline_version_info\n\
1807+
\n\
1808+
Readline version information as a named tuple.");
1809+
1810+
static PyStructSequence_Field readline_version_info_fields[] = {
1811+
{"major", "Major release number"},
1812+
{"minor", "Minor release number"},
1813+
{0}
1814+
};
1815+
1816+
static PyStructSequence_Desc readline_version_info_desc = {
1817+
"readline.readline_version_info", /* name */
1818+
readline_version_info__doc__, /* doc */
1819+
readline_version_info_fields, /* fields */
1820+
2
1821+
};
1822+
1823+
static PyObject *
1824+
make_readline_version_info(PyTypeObject *type, int number)
1825+
{
1826+
PyObject *version;
1827+
int pos = 0;
1828+
int major = (number >> 8) & 0xff;
1829+
int minor = number & 0xff;
1830+
1831+
version = PyStructSequence_New(type);
1832+
if (version == NULL) {
1833+
return NULL;
1834+
}
1835+
1836+
#define SetItem(VALUE) \
1837+
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
1838+
if (PyErr_Occurred()) { \
1839+
Py_DECREF(version); \
1840+
return NULL; \
1841+
}
1842+
1843+
SetItem(PyLong_FromLong(major))
1844+
SetItem(PyLong_FromLong(minor))
1845+
#undef SetItem
1846+
1847+
return version;
1848+
}
1849+
1850+
static int
1851+
add_version_constants(PyObject *m)
1852+
{
1853+
if (PyModule_AddIntConstant(m, "_READLINE_VERSION",
1854+
RL_READLINE_VERSION) < 0) {
1855+
return -1;
1856+
}
1857+
if (PyModule_AddIntConstant(m, "_READLINE_RUNTIME_VERSION",
1858+
rl_readline_version) < 0) {
1859+
return -1;
1860+
}
1861+
PyObject *obj = PyUnicode_FromString(rl_library_version);
1862+
if (obj == NULL) {
1863+
return -1;
1864+
}
1865+
if (PyModule_AddObjectRef(m, "readline_version", obj) < 0 ||
1866+
PyModule_AddObjectRef(m, "_READLINE_LIBRARY_VERSION", obj) < 0)
1867+
{
1868+
Py_DECREF(obj);
1869+
return -1;
1870+
}
1871+
Py_DECREF(obj);
1872+
PyTypeObject *version_type;
1873+
version_type = PyStructSequence_NewType(&readline_version_info_desc);
1874+
if (version_type == NULL) {
1875+
return -1;
1876+
}
1877+
if (PyModule_Add(m, "READLINE_VERSION_INFO",
1878+
make_readline_version_info(version_type,
1879+
RL_READLINE_VERSION)) < 0)
1880+
{
1881+
Py_DECREF(version_type);
1882+
return -1;
1883+
}
1884+
if (PyModule_Add(m, "readline_version_info",
1885+
make_readline_version_info(version_type,
1886+
rl_readline_version)) < 0)
1887+
{
1888+
Py_DECREF(version_type);
1889+
return -1;
1890+
}
1891+
Py_DECREF(version_type);
1892+
return 0;
1893+
}
1894+
18051895
/* Initialize the module */
18061896

18071897
PyDoc_STRVAR(doc_module,
@@ -1853,17 +1943,7 @@ PyInit_readline(void)
18531943
PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED);
18541944
#endif
18551945

1856-
if (PyModule_AddIntConstant(m, "_READLINE_VERSION",
1857-
RL_READLINE_VERSION) < 0) {
1858-
goto error;
1859-
}
1860-
if (PyModule_AddIntConstant(m, "_READLINE_RUNTIME_VERSION",
1861-
rl_readline_version) < 0) {
1862-
goto error;
1863-
}
1864-
if (PyModule_AddStringConstant(m, "_READLINE_LIBRARY_VERSION",
1865-
rl_library_version) < 0)
1866-
{
1946+
if (add_version_constants(m) < 0) {
18671947
goto error;
18681948
}
18691949

0 commit comments

Comments
 (0)