diff --git a/Doc/library/dbm.rst b/Doc/library/dbm.rst index 646981e8692cc5..423d3d517c57d2 100644 --- a/Doc/library/dbm.rst +++ b/Doc/library/dbm.rst @@ -253,6 +253,38 @@ functionality like crash tolerance. A string of characters the *flag* parameter of :meth:`~dbm.gnu.open` supports. +.. data:: GDBM_VERSION_INFO + + A named tuple containing the three components of the GDBM library + version that was used for building the module: + *major*, *minor*, and *patch*. + All values are integers. + The components can also be accessed by name, + so ``dbm.gnu.GDBM_VERSION_INFO[0]`` is equivalent to + ``dbm.gnu.GDBM_VERSION_INFO.major`` and so on. + This may be different from the GDBM library actually used at runtime, + which is available as :const:`gdbm_version_info`. + + .. versionadded:: next + + +.. data:: gdbm_version_info + + A named tuple containing the version of the GDBM library + actually loaded by the interpreter, + with the same fields as :const:`GDBM_VERSION_INFO`. + + .. versionadded:: next + + +.. data:: gdbm_version + + The release string of the GDBM library actually loaded by the interpreter, + like ``'GDBM version 1.26. 30/07/2025'``. + + .. versionadded:: next + + .. function:: open(filename, flag="r", mode=0o666, /) Open a GDBM database and return a :class:`!gdbm` object. diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst index 53983637f520c8..1d6a3e10407e29 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -340,6 +340,15 @@ concurrent.futures (Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.) +dbm.gnu +------- + +* Added constants :const:`~dbm.gnu.GDBM_VERSION_INFO`, + :const:`~dbm.gnu.gdbm_version_info` and :const:`~dbm.gnu.gdbm_version`, + which provide information about the version of the GDBM library in use. + (Contributed by Serhiy Storchaka in :gh:`157478`.) + + difflib ------- diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index b59e2acb9376f1..52176de7d6126f 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -937,11 +937,12 @@ def collect_cc(info_add): def collect_gdbm(info_add): try: - from _gdbm import _GDBM_VERSION + import _gdbm except ImportError: return - info_add('gdbm.GDBM_VERSION', '.'.join(map(str, _GDBM_VERSION))) + attributes = ('GDBM_VERSION_INFO', 'gdbm_version') + copy_attributes(info_add, _gdbm, 'gdbm.%s', attributes) def collect_get_config(info_add): diff --git a/Lib/test/test_dbm_gnu.py b/Lib/test/test_dbm_gnu.py index 66268c42a300b5..6980a702694803 100644 --- a/Lib/test/test_dbm_gnu.py +++ b/Lib/test/test_dbm_gnu.py @@ -13,12 +13,7 @@ class TestGdbm(unittest.TestCase): @staticmethod def setUpClass(): if support.verbose: - try: - from _gdbm import _GDBM_VERSION as version - except ImportError: - pass - else: - print(f"gdbm version: {version}") + print(f"gdbm version: {gdbm.gdbm_version}") def setUp(self): self.g = None @@ -29,6 +24,35 @@ def tearDown(self): unlink(filename) @cpython_only + def _test_gdbm_version(self, v): + self.assertIsInstance(v[:], tuple) + self.assertEqual(len(v), 3) + self.assertIsInstance(v[0], int) + self.assertIsInstance(v[1], int) + self.assertIsInstance(v[2], int) + self.assertIsInstance(v.major, int) + self.assertIsInstance(v.minor, int) + self.assertIsInstance(v.patch, int) + self.assertEqual(v[0], v.major) + self.assertEqual(v[1], v.minor) + self.assertEqual(v[2], v.patch) + self.assertGreaterEqual(v.major, 1) + self.assertGreaterEqual(v.minor, 0) + self.assertGreaterEqual(v.patch, 0) + + @unittest.skipUnless(hasattr(gdbm, 'GDBM_VERSION_INFO'), + 'requires gdbm >= 1.9') + def test_gdbm_version(self): + if support.verbose: + print(f'GDBM_VERSION_INFO = {gdbm.GDBM_VERSION_INFO}', flush=True) + print(f'gdbm_version_info = {gdbm.gdbm_version_info}', flush=True) + self._test_gdbm_version(gdbm.GDBM_VERSION_INFO) + self._test_gdbm_version(gdbm.gdbm_version_info) + self.assertEqual(gdbm.GDBM_VERSION_INFO[0], gdbm.gdbm_version_info[0]) + v = gdbm.gdbm_version_info + self.assertIsInstance(gdbm.gdbm_version, str) + self.assertStartsWith(gdbm.gdbm_version, 'GDBM version %d.%d' % v[:2]) + def test_disallow_instantiation(self): # Ensure that the type disallows instantiation (bpo-43916) self.g = gdbm.open(filename, 'c') diff --git a/Misc/NEWS.d/next/Library/2026-09-14-10-22-36.gh-issue-157478.F4cBKN.rst b/Misc/NEWS.d/next/Library/2026-09-14-10-22-36.gh-issue-157478.F4cBKN.rst new file mode 100644 index 00000000000000..462e8c4d53a7f8 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-14-10-22-36.gh-issue-157478.F4cBKN.rst @@ -0,0 +1,4 @@ +Add constants :const:`dbm.gnu.GDBM_VERSION_INFO`, +:const:`dbm.gnu.gdbm_version_info` and :const:`dbm.gnu.gdbm_version`, which +provide information about the version of the GDBM library in use. +The private constant ``_gdbm._GDBM_VERSION`` has been removed. diff --git a/Modules/_gdbmmodule.c b/Modules/_gdbmmodule.c index 20d482021656a5..47920edcb5c0ac 100644 --- a/Modules/_gdbmmodule.c +++ b/Modules/_gdbmmodule.c @@ -855,6 +855,85 @@ static PyMethodDef _gdbm_module_methods[] = { { 0, 0 }, }; +PyDoc_STRVAR(gdbm_version_info__doc__, +"_gdbm.gdbm_version_info\n\ +\n\ +GDBM version information as a named tuple."); + +static PyStructSequence_Field gdbm_version_info_fields[] = { + {"major", "Major release number"}, + {"minor", "Minor release number"}, + {"patch", "Patch release number"}, + {0} +}; + +static PyStructSequence_Desc gdbm_version_info_desc = { + "_gdbm.gdbm_version_info", /* name */ + gdbm_version_info__doc__, /* doc */ + gdbm_version_info_fields, /* fields */ + 3 +}; + +static PyObject * +make_gdbm_version_info(PyTypeObject *type, int major, int minor, int patch) +{ + PyObject *version; + int pos = 0; + + version = PyStructSequence_New(type); + if (version == NULL) { + return NULL; + } + +#define SetItem(VALUE) \ + PyStructSequence_SET_ITEM(version, pos++, VALUE); \ + if (PyErr_Occurred()) { \ + Py_DECREF(version); \ + return NULL; \ + } + + SetItem(PyLong_FromLong(major)) + SetItem(PyLong_FromLong(minor)) + SetItem(PyLong_FromLong(patch)) +#undef SetItem + + return version; +} + +static int +add_version_constants(PyObject *module) +{ + if (PyModule_AddStringConstant(module, "gdbm_version", gdbm_version) < 0) { + return -1; + } +#if defined(GDBM_VERSION_MAJOR) && defined(GDBM_VERSION_MINOR) && \ + defined(GDBM_VERSION_PATCH) + PyTypeObject *version_type; + version_type = PyStructSequence_NewType(&gdbm_version_info_desc); + if (version_type == NULL) { + return -1; + } + if (PyModule_Add(module, "GDBM_VERSION_INFO", + make_gdbm_version_info(version_type, GDBM_VERSION_MAJOR, + GDBM_VERSION_MINOR, + GDBM_VERSION_PATCH)) < 0) + { + Py_DECREF(version_type); + return -1; + } + if (PyModule_Add(module, "gdbm_version_info", + make_gdbm_version_info(version_type, gdbm_version_number[0], + gdbm_version_number[1], + gdbm_version_number[2])) < 0) + { + Py_DECREF(version_type); + return -1; + } + Py_DECREF(version_type); +#endif + return 0; +} + static int _gdbm_exec(PyObject *module) { @@ -876,14 +955,9 @@ _gdbm_exec(PyObject *module) return -1; } -#if defined(GDBM_VERSION_MAJOR) && defined(GDBM_VERSION_MINOR) && \ - defined(GDBM_VERSION_PATCH) - PyObject *obj = Py_BuildValue("iii", GDBM_VERSION_MAJOR, - GDBM_VERSION_MINOR, GDBM_VERSION_PATCH); - if (PyModule_Add(module, "_GDBM_VERSION", obj) < 0) { + if (add_version_constants(module) < 0) { return -1; } -#endif return 0; }