https://github.com/python/cpython/commit/0f5abe6d8d1cf30a865973e07a85c3da2ee20dfb
commit: 0f5abe6d8d1cf30a865973e07a85c3da2ee20dfb
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-29T15:43:01+03:00
summary:

gh-157478: Add structured version info for dbm.gnu (GH-157479)

Add constants dbm.gnu.GDBM_VERSION_INFO, dbm.gnu.gdbm_version_info and
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.

Co-authored-by: Claude Opus 5 (1M context) <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-09-14-10-22-36.gh-issue-157478.F4cBKN.rst
M Doc/library/dbm.rst
M Doc/whatsnew/3.16.rst
M Lib/test/pythoninfo.py
M Lib/test/test_dbm_gnu.py
M Modules/_gdbmmodule.c

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 c61190822234ea..0bdd55d8221402 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -347,6 +347,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 1f0753b9839cdd..2885ad25d8ac6a 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -960,11 +960,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 47c02b72de2a76..bdc2d96d8324b0 100644
--- a/Modules/_gdbmmodule.c
+++ b/Modules/_gdbmmodule.c
@@ -862,6 +862,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)
 {
@@ -883,14 +962,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;
 }
 

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to