Skip to content

Commit 543a6b7

Browse files
gh-157480: Add structured version info for dbm.ndbm
Add constants which provide information about the version of the underlying library in use: GDBM_VERSION_INFO, gdbm_version_info and gdbm_version if it is GDBM, or BDB_VERSION, bdb_version, BDB_VERSION_INFO and bdb_version_info if it is Berkeley DB. dbm.ndbm.library is now 'ndbm' instead of 'GNU gdbm' for a classic NDBM library. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent a844747 commit 543a6b7

8 files changed

Lines changed: 327 additions & 3 deletions

File tree

Doc/library/dbm.rst

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,69 @@ This module can be used with the "classic" NDBM interface or the
382382

383383
.. data:: library
384384

385-
Name of the NDBM implementation library used.
385+
Name of the NDBM implementation library used:
386+
``'GNU gdbm'``, ``'Berkeley DB'`` or ``'ndbm'``.
387+
388+
.. versionchanged:: next
389+
The value is ``'ndbm'`` for a classic NDBM library.
390+
It was ``'GNU gdbm'`` before.
391+
392+
393+
.. data:: GDBM_VERSION_INFO
394+
gdbm_version_info
395+
gdbm_version
396+
397+
Information about the GDBM library in use,
398+
with the same meaning as the constants of the same names in :mod:`dbm.gnu`.
399+
Only available if :const:`library` is ``'GNU gdbm'``.
400+
401+
.. versionadded:: next
402+
403+
404+
.. data:: BDB_VERSION
405+
406+
The version string of the Berkeley DB library that was used for building
407+
the module, like ``'Berkeley DB 5.3.28: (September 9, 2013)'``.
408+
This may be different from the Berkeley DB library actually used at runtime,
409+
which is available as :const:`bdb_version`.
410+
Only available if :const:`library` is ``'Berkeley DB'``.
411+
412+
.. versionadded:: next
413+
414+
415+
.. data:: bdb_version
416+
417+
The version string of the Berkeley DB library actually loaded by the
418+
interpreter.
419+
Only available if :const:`library` is ``'Berkeley DB'``.
420+
421+
.. versionadded:: next
422+
423+
424+
.. data:: BDB_VERSION_INFO
425+
426+
A named tuple containing the three components of the Berkeley DB library
427+
version that was used for building the module:
428+
*major*, *minor*, and *patch*.
429+
All values are integers.
430+
The components can also be accessed by name,
431+
so ``dbm.ndbm.BDB_VERSION_INFO[0]`` is equivalent to
432+
``dbm.ndbm.BDB_VERSION_INFO.major`` and so on.
433+
This may be different from the Berkeley DB library actually used at runtime,
434+
which is available as :const:`bdb_version_info`.
435+
Only available if :const:`library` is ``'Berkeley DB'``.
436+
437+
.. versionadded:: next
438+
439+
440+
.. data:: bdb_version_info
441+
442+
A named tuple containing the version of the Berkeley DB library
443+
actually loaded by the interpreter,
444+
with the same fields as :const:`BDB_VERSION_INFO`.
445+
Only available if :const:`library` is ``'Berkeley DB'``.
446+
447+
.. versionadded:: next
386448

387449

388450
.. function:: open(filename, flag="r", mode=0o666, /)

Doc/whatsnew/3.16.rst

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,20 @@ concurrent.futures
340340
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
341341

342342

343+
dbm.ndbm
344+
--------
345+
346+
* Added constants which provide information about the version of the
347+
underlying library in use: :const:`~dbm.ndbm.GDBM_VERSION_INFO`,
348+
:const:`~dbm.ndbm.gdbm_version_info` and :const:`~dbm.ndbm.gdbm_version`
349+
if it is GDBM, or :const:`~dbm.ndbm.BDB_VERSION`,
350+
:const:`~dbm.ndbm.bdb_version`, :const:`~dbm.ndbm.BDB_VERSION_INFO` and
351+
:const:`~dbm.ndbm.bdb_version_info` if it is Berkeley DB.
352+
:const:`dbm.ndbm.library` is now ``'ndbm'`` instead of ``'GNU gdbm'``
353+
for a classic NDBM library.
354+
(Contributed by Serhiy Storchaka in :gh:`157480`.)
355+
356+
343357
difflib
344358
-------
345359

Lib/test/pythoninfo.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -935,6 +935,17 @@ def collect_cc(info_add):
935935
info_add('CC.version', text)
936936

937937

938+
def collect_ndbm(info_add):
939+
try:
940+
import _dbm
941+
except ImportError:
942+
return
943+
944+
attributes = ('library', 'GDBM_VERSION_INFO', 'gdbm_version',
945+
'BDB_VERSION', 'bdb_version')
946+
copy_attributes(info_add, _dbm, 'ndbm.%s', attributes)
947+
948+
938949
def collect_gdbm(info_add):
939950
try:
940951
from _gdbm import _GDBM_VERSION
@@ -1356,6 +1367,7 @@ def collect_info(info):
13561367
collect_expat,
13571368
collect_fips,
13581369
collect_gdb,
1370+
collect_ndbm,
13591371
collect_gdbm,
13601372
collect_get_config,
13611373
collect_locale,

Lib/test/test_dbm_ndbm.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from test import support
12
from test.support import import_helper
23
from test.support import os_helper
34
import_helper.import_module("dbm.ndbm") #skip if not supported
@@ -17,6 +18,53 @@ def tearDown(self):
1718
for suffix in ['', '.pag', '.dir', '.db']:
1819
os_helper.unlink(self.filename + suffix)
1920

21+
def _test_version_info(self, v):
22+
self.assertIsInstance(v[:], tuple)
23+
self.assertEqual(len(v), 3)
24+
self.assertIsInstance(v[0], int)
25+
self.assertIsInstance(v[1], int)
26+
self.assertIsInstance(v[2], int)
27+
self.assertIsInstance(v.major, int)
28+
self.assertIsInstance(v.minor, int)
29+
self.assertIsInstance(v.patch, int)
30+
self.assertEqual(v[0], v.major)
31+
self.assertEqual(v[1], v.minor)
32+
self.assertEqual(v[2], v.patch)
33+
self.assertGreaterEqual(v.major, 1)
34+
self.assertGreaterEqual(v.minor, 0)
35+
self.assertGreaterEqual(v.patch, 0)
36+
37+
def test_library_version(self):
38+
library = dbm.ndbm.library
39+
if support.verbose:
40+
print(f'library = {library!r}', flush=True)
41+
self.assertIsInstance(library, str)
42+
if library == 'GNU gdbm':
43+
prefix = 'GDBM'
44+
elif library == 'Berkeley DB':
45+
prefix = 'BDB'
46+
else:
47+
self.assertEqual(library, 'ndbm')
48+
self.assertNotHasAttr(dbm.ndbm, 'GDBM_VERSION_INFO')
49+
self.assertNotHasAttr(dbm.ndbm, 'BDB_VERSION_INFO')
50+
return
51+
V = getattr(dbm.ndbm, f'{prefix}_VERSION_INFO')
52+
v = getattr(dbm.ndbm, f'{prefix.lower()}_version_info')
53+
version = getattr(dbm.ndbm, f'{prefix.lower()}_version')
54+
if support.verbose:
55+
print(f'{prefix}_VERSION_INFO = {V}', flush=True)
56+
print(f'{prefix.lower()}_version_info = {v}', flush=True)
57+
print(f'{prefix.lower()}_version = {version!r}', flush=True)
58+
self._test_version_info(V)
59+
self._test_version_info(v)
60+
self.assertEqual(V[0], v[0])
61+
self.assertIsInstance(version, str)
62+
if library == 'GNU gdbm':
63+
self.assertStartsWith(version, 'GDBM version %d.%d' % v[:2])
64+
else:
65+
self.assertIsInstance(dbm.ndbm.BDB_VERSION, str)
66+
self.assertIn('%d.%d.%d' % v[:3], version)
67+
2068
def test_keys(self):
2169
self.d = dbm.ndbm.open(self.filename, 'c')
2270
self.assertEqual(self.d.keys(), [])
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Add constants in :mod:`dbm.ndbm` which provide information about the version
2+
of the underlying library in use: :const:`~dbm.ndbm.GDBM_VERSION_INFO`,
3+
:const:`~dbm.ndbm.gdbm_version_info` and :const:`~dbm.ndbm.gdbm_version` if
4+
it is GDBM, or :const:`~dbm.ndbm.BDB_VERSION`, :const:`~dbm.ndbm.bdb_version`,
5+
:const:`~dbm.ndbm.BDB_VERSION_INFO` and :const:`~dbm.ndbm.bdb_version_info` if
6+
it is Berkeley DB.
7+
:const:`dbm.ndbm.library` is now ``'ndbm'`` instead of ``'GNU gdbm'`` for a
8+
classic NDBM library.

Modules/_dbmmodule.c

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,12 @@
2828
static const char which_dbm[] = "GNU gdbm";
2929
#elif defined(USE_NDBM)
3030
#include <ndbm.h>
31+
#ifdef _GDBM_H_
32+
/* ndbm.h is the GDBM compatibility interface. */
3133
static const char which_dbm[] = "GNU gdbm";
34+
#else
35+
static const char which_dbm[] = "ndbm";
36+
#endif
3237
#elif defined(USE_BERKDB)
3338
#ifndef DB_DBM_HSEARCH
3439
#define DB_DBM_HSEARCH 1
@@ -628,6 +633,136 @@ static PyMethodDef dbmmodule_methods[] = {
628633
{ 0, 0 },
629634
};
630635

636+
#if defined(GDBM_VERSION_MAJOR) || defined(DB_VERSION_MAJOR)
637+
static PyStructSequence_Field version_info_fields[] = {
638+
{"major", "Major release number"},
639+
{"minor", "Minor release number"},
640+
{"patch", "Patch release number"},
641+
{0}
642+
};
643+
644+
static PyObject *
645+
make_version_info(PyTypeObject *type, int major, int minor, int patch)
646+
{
647+
PyObject *version;
648+
int pos = 0;
649+
650+
version = PyStructSequence_New(type);
651+
if (version == NULL) {
652+
return NULL;
653+
}
654+
655+
#define SetItem(VALUE) \
656+
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
657+
if (PyErr_Occurred()) { \
658+
Py_DECREF(version); \
659+
return NULL; \
660+
}
661+
662+
SetItem(PyLong_FromLong(major))
663+
SetItem(PyLong_FromLong(minor))
664+
SetItem(PyLong_FromLong(patch))
665+
#undef SetItem
666+
667+
return version;
668+
}
669+
#endif
670+
671+
#if defined(GDBM_VERSION_MAJOR)
672+
PyDoc_STRVAR(gdbm_version_info__doc__,
673+
"_dbm.gdbm_version_info\n\
674+
\n\
675+
GDBM version information as a named tuple.");
676+
677+
static PyStructSequence_Desc gdbm_version_info_desc = {
678+
"_dbm.gdbm_version_info", /* name */
679+
gdbm_version_info__doc__, /* doc */
680+
version_info_fields, /* fields */
681+
3
682+
};
683+
684+
static int
685+
add_version_constants(PyObject *module)
686+
{
687+
if (PyModule_AddStringConstant(module, "gdbm_version", gdbm_version) < 0) {
688+
return -1;
689+
}
690+
PyTypeObject *version_type;
691+
version_type = PyStructSequence_NewType(&gdbm_version_info_desc);
692+
if (version_type == NULL) {
693+
return -1;
694+
}
695+
if (PyModule_Add(module, "GDBM_VERSION_INFO",
696+
make_version_info(version_type, GDBM_VERSION_MAJOR,
697+
GDBM_VERSION_MINOR, GDBM_VERSION_PATCH)) < 0)
698+
{
699+
Py_DECREF(version_type);
700+
return -1;
701+
}
702+
if (PyModule_Add(module, "gdbm_version_info",
703+
make_version_info(version_type, gdbm_version_number[0],
704+
gdbm_version_number[1],
705+
gdbm_version_number[2])) < 0)
706+
{
707+
Py_DECREF(version_type);
708+
return -1;
709+
}
710+
Py_DECREF(version_type);
711+
return 0;
712+
}
713+
#elif defined(DB_VERSION_MAJOR)
714+
PyDoc_STRVAR(bdb_version_info__doc__,
715+
"_dbm.bdb_version_info\n\
716+
\n\
717+
Berkeley DB version information as a named tuple.");
718+
719+
static PyStructSequence_Desc bdb_version_info_desc = {
720+
"_dbm.bdb_version_info", /* name */
721+
bdb_version_info__doc__, /* doc */
722+
version_info_fields, /* fields */
723+
3
724+
};
725+
726+
static int
727+
add_version_constants(PyObject *module)
728+
{
729+
int major, minor, patch;
730+
const char *version = db_version(&major, &minor, &patch);
731+
if (PyModule_AddStringConstant(module, "BDB_VERSION", DB_VERSION_STRING) < 0) {
732+
return -1;
733+
}
734+
if (PyModule_AddStringConstant(module, "bdb_version", version) < 0) {
735+
return -1;
736+
}
737+
PyTypeObject *version_type;
738+
version_type = PyStructSequence_NewType(&bdb_version_info_desc);
739+
if (version_type == NULL) {
740+
return -1;
741+
}
742+
if (PyModule_Add(module, "BDB_VERSION_INFO",
743+
make_version_info(version_type, DB_VERSION_MAJOR,
744+
DB_VERSION_MINOR, DB_VERSION_PATCH)) < 0)
745+
{
746+
Py_DECREF(version_type);
747+
return -1;
748+
}
749+
if (PyModule_Add(module, "bdb_version_info",
750+
make_version_info(version_type, major, minor, patch)) < 0)
751+
{
752+
Py_DECREF(version_type);
753+
return -1;
754+
}
755+
Py_DECREF(version_type);
756+
return 0;
757+
}
758+
#else
759+
static int
760+
add_version_constants(PyObject *module)
761+
{
762+
return 0;
763+
}
764+
#endif
765+
631766
static int
632767
_dbm_exec(PyObject *module)
633768
{
@@ -644,6 +779,9 @@ _dbm_exec(PyObject *module)
644779
if (PyModule_AddStringConstant(module, "library", which_dbm) < 0) {
645780
return -1;
646781
}
782+
if (add_version_constants(module) < 0) {
783+
return -1;
784+
}
647785
if (PyModule_AddType(module, (PyTypeObject *)state->dbm_error) < 0) {
648786
return -1;
649787
}

0 commit comments

Comments
 (0)