Skip to content

Commit 89bfac6

Browse files
gh-157480: Add structured version info for dbm.ndbm (GH-157484)
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 3736342 commit 89bfac6

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
@@ -414,7 +414,69 @@ This module can be used with the "classic" NDBM interface or the
414414

415415
.. data:: library
416416

417-
Name of the NDBM implementation library used.
417+
Name of the NDBM implementation library used:
418+
``'GNU gdbm'``, ``'Berkeley DB'`` or ``'ndbm'``.
419+
420+
.. versionchanged:: next
421+
The value is ``'ndbm'`` for a classic NDBM library.
422+
It was ``'GNU gdbm'`` before.
423+
424+
425+
.. data:: GDBM_VERSION_INFO
426+
gdbm_version_info
427+
gdbm_version
428+
429+
Information about the GDBM library in use,
430+
with the same meaning as the constants of the same names in :mod:`dbm.gnu`.
431+
Only available if :const:`library` is ``'GNU gdbm'``.
432+
433+
.. versionadded:: next
434+
435+
436+
.. data:: BDB_VERSION
437+
438+
The version string of the Berkeley DB library that was used for building
439+
the module, like ``'Berkeley DB 5.3.28: (September 9, 2013)'``.
440+
This may be different from the Berkeley DB library actually used at runtime,
441+
which is available as :const:`bdb_version`.
442+
Only available if :const:`library` is ``'Berkeley DB'``.
443+
444+
.. versionadded:: next
445+
446+
447+
.. data:: bdb_version
448+
449+
The version string of the Berkeley DB library actually loaded by the
450+
interpreter.
451+
Only available if :const:`library` is ``'Berkeley DB'``.
452+
453+
.. versionadded:: next
454+
455+
456+
.. data:: BDB_VERSION_INFO
457+
458+
A named tuple containing the three components of the Berkeley DB library
459+
version that was used for building the module:
460+
*major*, *minor*, and *patch*.
461+
All values are integers.
462+
The components can also be accessed by name,
463+
so ``dbm.ndbm.BDB_VERSION_INFO[0]`` is equivalent to
464+
``dbm.ndbm.BDB_VERSION_INFO.major`` and so on.
465+
This may be different from the Berkeley DB library actually used at runtime,
466+
which is available as :const:`bdb_version_info`.
467+
Only available if :const:`library` is ``'Berkeley DB'``.
468+
469+
.. versionadded:: next
470+
471+
472+
.. data:: bdb_version_info
473+
474+
A named tuple containing the version of the Berkeley DB library
475+
actually loaded by the interpreter,
476+
with the same fields as :const:`BDB_VERSION_INFO`.
477+
Only available if :const:`library` is ``'Berkeley DB'``.
478+
479+
.. versionadded:: next
418480

419481

420482
.. 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
@@ -356,6 +356,20 @@ dbm.gnu
356356
(Contributed by Serhiy Storchaka in :gh:`157478`.)
357357

358358

359+
dbm.ndbm
360+
--------
361+
362+
* Added constants which provide information about the version of the
363+
underlying library in use: :const:`~dbm.ndbm.GDBM_VERSION_INFO`,
364+
:const:`~dbm.ndbm.gdbm_version_info` and :const:`~dbm.ndbm.gdbm_version`
365+
if it is GDBM, or :const:`~dbm.ndbm.BDB_VERSION`,
366+
:const:`~dbm.ndbm.bdb_version`, :const:`~dbm.ndbm.BDB_VERSION_INFO` and
367+
:const:`~dbm.ndbm.bdb_version_info` if it is Berkeley DB.
368+
:const:`dbm.ndbm.library` is now ``'ndbm'`` instead of ``'GNU gdbm'``
369+
for a classic NDBM library.
370+
(Contributed by Serhiy Storchaka in :gh:`157480`.)
371+
372+
359373
difflib
360374
-------
361375

‎Lib/test/pythoninfo.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,17 @@ def collect_cc(info_add):
945945
info_add('CXX.version', version)
946946

947947

948+
def collect_ndbm(info_add):
949+
try:
950+
import _dbm
951+
except ImportError:
952+
return
953+
954+
attributes = ('library', 'GDBM_VERSION_INFO', 'gdbm_version',
955+
'BDB_VERSION', 'bdb_version')
956+
copy_attributes(info_add, _dbm, 'ndbm.%s', attributes)
957+
958+
948959
def collect_gdbm(info_add):
949960
try:
950961
import _gdbm
@@ -1368,6 +1379,7 @@ def collect_info(info):
13681379
collect_expat,
13691380
collect_fips,
13701381
collect_gdb,
1382+
collect_ndbm,
13711383
collect_gdbm,
13721384
collect_get_config,
13731385
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
@@ -634,6 +639,136 @@ static PyMethodDef dbmmodule_methods[] = {
634639
{ 0, 0 },
635640
};
636641

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

0 commit comments

Comments
 (0)