https://github.com/python/cpython/commit/dc0b1f8dfb1e7610b6f0c9e268f0945708d88c24
commit: dc0b1f8dfb1e7610b6f0c9e268f0945708d88c24
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-09-29T13:40:33Z
summary:

gh-157475: Add structured version info for decimal (GH-157476)

Add constants decimal.LIBMPDEC_VERSION, decimal.libmpdec_version,
decimal.LIBMPDEC_VERSION_INFO, and decimal.libmpdec_version_info in
the C implementation, which provide information about the version of
the libmpdec library in use.

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

files:
A Misc/NEWS.d/next/Library/2026-09-14-09-28-35.gh-issue-157475.dvuniU.rst
M Doc/library/decimal.rst
M Doc/whatsnew/3.16.rst
M Lib/test/pythoninfo.py
M Lib/test/test_decimal.py
M Modules/_decimal/_decimal.c

diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst
index ecd973a070b3df..3b7c30d89236ac 100644
--- a/Doc/library/decimal.rst
+++ b/Doc/library/decimal.rst
@@ -1632,6 +1632,46 @@ are also included in the pure Python version for 
compatibility.
 
    .. versionadded:: 3.8.3
 
+The following constants are only available in the C module.
+
+.. data:: LIBMPDEC_VERSION
+
+   The version string of the libmpdec library that was used for building
+   the module.
+   This may be different from the libmpdec library actually used at runtime,
+   which is available as :const:`libmpdec_version`.
+
+   .. versionadded:: next
+
+.. data:: libmpdec_version
+
+   The version string of the libmpdec library actually loaded by the
+   interpreter.
+
+   .. versionadded:: next
+
+.. data:: LIBMPDEC_VERSION_INFO
+
+   A named tuple containing the three components of the libmpdec library
+   version that was used for building the module:
+   *major*, *minor*, and *micro*.
+   All values are integers.
+   The components can also be accessed by name,
+   so ``decimal.LIBMPDEC_VERSION_INFO[0]`` is equivalent to
+   ``decimal.LIBMPDEC_VERSION_INFO.major`` and so on.
+   This may be different from the libmpdec library actually used at runtime,
+   which is available as :const:`libmpdec_version_info`.
+
+   .. versionadded:: next
+
+.. data:: libmpdec_version_info
+
+   A named tuple containing the version of the libmpdec library
+   actually loaded by the interpreter,
+   with the same fields as :const:`LIBMPDEC_VERSION_INFO`.
+
+   .. versionadded:: next
+
 
 Rounding modes
 --------------
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index bb30c856b9880a..29bd7dcd749ebc 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -347,6 +347,16 @@ concurrent.futures
   (Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
 
 
+decimal
+-------
+
+* Added constants :const:`~decimal.LIBMPDEC_VERSION`,
+  :const:`~decimal.libmpdec_version`, :const:`~decimal.LIBMPDEC_VERSION_INFO`,
+  and :const:`~decimal.libmpdec_version_info` in the C implementation,
+  which provide information about the version of the libmpdec library in use.
+  (Contributed by Serhiy Storchaka in :gh:`157475`.)
+
+
 dbm.gnu
 -------
 
diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py
index b6658d1bab3136..100e61a20211c2 100644
--- a/Lib/test/pythoninfo.py
+++ b/Lib/test/pythoninfo.py
@@ -758,7 +758,7 @@ def collect_decimal(info_add):
     except ImportError:
         return
 
-    attributes = ('__libmpdec_version__',)
+    attributes = ('LIBMPDEC_VERSION', 'libmpdec_version')
     copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
 
 
diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py
index 7524822632bae7..65bca438078634 100644
--- a/Lib/test/test_decimal.py
+++ b/Lib/test/test_decimal.py
@@ -4534,8 +4534,13 @@ def test_module_attributes(self):
 
         self.assertEqual(C.SPEC_VERSION, P.SPEC_VERSION)
 
-        self.assertLessEqual(set(dir(C)), set(dir(P)))
-        self.assertEqual([n for n in dir(C) if n[:2] != '__'], 
sorted(P.__all__))
+        # Information about the libmpdec library, specific to the C module.
+        libmpdec_names = {'LIBMPDEC_VERSION', 'LIBMPDEC_VERSION_INFO',
+                          'libmpdec_version', 'libmpdec_version_info'}
+        self.assertLessEqual(set(dir(C)) - libmpdec_names, set(dir(P)))
+        self.assertEqual([n for n in dir(C)
+                          if n[:2] != '__' and n not in libmpdec_names],
+                         sorted(P.__all__))
 
     def test_context_attributes(self):
 
@@ -5059,6 +5064,38 @@ def test_constants(self):
         self.assertEqual(C.DecTraps,
                          C.DecErrors|C.DecOverflow|C.DecUnderflow)
 
+@requires_cdecimal
+class CVersion(unittest.TestCase):
+    """Information about the libmpdec library in _decimal"""
+
+    def _test_libmpdec_version(self, v, string):
+        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.micro, int)
+        self.assertEqual(v[0], v.major)
+        self.assertEqual(v[1], v.minor)
+        self.assertEqual(v[2], v.micro)
+        self.assertGreaterEqual(v.major, 2)
+        self.assertGreaterEqual(v.minor, 0)
+        self.assertGreaterEqual(v.micro, 0)
+        self.assertEqual(string, '%d.%d.%d' % v)
+
+    def test_libmpdec_version(self):
+        if support.verbose:
+            print(f'LIBMPDEC_VERSION = {C.LIBMPDEC_VERSION}', flush=True)
+            print(f'libmpdec_version = {C.libmpdec_version}', flush=True)
+            print(f'LIBMPDEC_VERSION_INFO = {C.LIBMPDEC_VERSION_INFO}', 
flush=True)
+            print(f'libmpdec_version_info = {C.libmpdec_version_info}', 
flush=True)
+        self._test_libmpdec_version(C.LIBMPDEC_VERSION_INFO, 
C.LIBMPDEC_VERSION)
+        self._test_libmpdec_version(C.libmpdec_version_info, 
C.libmpdec_version)
+        self.assertEqual(C.LIBMPDEC_VERSION_INFO[0], 
C.libmpdec_version_info[0])
+        self.assertIs(C.libmpdec_version, C.__libmpdec_version__)
+
 @requires_cdecimal
 class CWhitebox(unittest.TestCase):
     """Whitebox testing for _decimal"""
diff --git 
a/Misc/NEWS.d/next/Library/2026-09-14-09-28-35.gh-issue-157475.dvuniU.rst 
b/Misc/NEWS.d/next/Library/2026-09-14-09-28-35.gh-issue-157475.dvuniU.rst
new file mode 100644
index 00000000000000..d395a381022fca
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-09-14-09-28-35.gh-issue-157475.dvuniU.rst
@@ -0,0 +1,4 @@
+Add constants :const:`decimal.LIBMPDEC_VERSION`,
+:const:`decimal.libmpdec_version`, :const:`decimal.LIBMPDEC_VERSION_INFO`,
+and :const:`decimal.libmpdec_version_info` in the C implementation, which
+provide information about the version of the libmpdec library in use.
diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c
index 20e4f5bea88464..1f7da94b2971e1 100644
--- a/Modules/_decimal/_decimal.c
+++ b/Modules/_decimal/_decimal.c
@@ -7756,6 +7756,104 @@ cfunc_noargs(PyTypeObject *t, const char *name)
     return NULL;
 }
 
+PyDoc_STRVAR(libmpdec_version_info__doc__,
+"decimal.libmpdec_version_info\n\
+\n\
+libmpdec version information as a named tuple.");
+
+static PyStructSequence_Field libmpdec_version_info_fields[] = {
+    {"major", "Major release number"},
+    {"minor", "Minor release number"},
+    {"micro", "Micro release number"},
+    {0}
+};
+
+static PyStructSequence_Desc libmpdec_version_info_desc = {
+    "decimal.libmpdec_version_info",    /* name */
+    libmpdec_version_info__doc__,       /* doc */
+    libmpdec_version_info_fields,       /* fields */
+    3
+};
+
+static PyObject *
+make_libmpdec_version_info(PyTypeObject *type, int major, int minor, int micro)
+{
+    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(micro))
+#undef SetItem
+
+    return version;
+}
+
+static PyObject *
+parse_libmpdec_version_info(PyTypeObject *type, const char *version)
+{
+    int major, minor, micro;
+    if (sscanf(version, "%d.%d.%d", &major, &minor, &micro) != 3) {
+        PyErr_Format(PyExc_RuntimeError,
+                     "unexpected libmpdec version string %s", version);
+        return NULL;
+    }
+    return make_libmpdec_version_info(type, major, minor, micro);
+}
+
+static int
+add_version_constants(PyObject *m)
+{
+    const char *version = mpd_version();
+    if (PyModule_AddStringConstant(m, "LIBMPDEC_VERSION", MPD_VERSION) < 0) {
+        return -1;
+    }
+    PyObject *obj = PyUnicode_FromString(version);
+    if (obj == NULL) {
+        return -1;
+    }
+    if (PyModule_AddObjectRef(m, "libmpdec_version", obj) < 0 ||
+        PyModule_AddObjectRef(m, "__libmpdec_version__", obj) < 0)
+    {
+        Py_DECREF(obj);
+        return -1;
+    }
+    Py_DECREF(obj);
+    PyTypeObject *version_type;
+    version_type = PyStructSequence_NewType(&libmpdec_version_info_desc);
+    if (version_type == NULL) {
+        return -1;
+    }
+    if (PyModule_Add(m, "LIBMPDEC_VERSION_INFO",
+            make_libmpdec_version_info(version_type, MPD_MAJOR_VERSION,
+                                       MPD_MINOR_VERSION,
+                                       MPD_MICRO_VERSION)) < 0)
+    {
+        Py_DECREF(version_type);
+        return -1;
+    }
+    if (PyModule_Add(m, "libmpdec_version_info",
+            parse_libmpdec_version_info(version_type, version)) < 0)
+    {
+        Py_DECREF(version_type);
+        return -1;
+    }
+    Py_DECREF(version_type);
+    return 0;
+}
+
 static int minalloc_is_set = 0;
 
 static int
@@ -8004,7 +8102,7 @@ _decimal_exec(PyObject *m)
 
     /* Add specification version number */
     CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", MPD_SPEC_VERSION));
-    CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", 
mpd_version()));
+    CHECK_INT(add_version_constants(m));
 
     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