diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index ecd973a070b3dff..3b7c30d89236acb 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 53983637f520c8a..9f053395f9692a2 100644 --- a/Doc/whatsnew/3.16.rst +++ b/Doc/whatsnew/3.16.rst @@ -340,6 +340,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`.) + + difflib ------- diff --git a/Lib/test/pythoninfo.py b/Lib/test/pythoninfo.py index b59e2acb9376f1e..3088134fddbb630 100644 --- a/Lib/test/pythoninfo.py +++ b/Lib/test/pythoninfo.py @@ -761,7 +761,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 7524822632bae7a..65bca438078634a 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 000000000000000..d395a381022fcaf --- /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 ada9b02d690717f..579043e6f062353 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7744,6 +7744,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, µ) != 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 @@ -7992,7 +8090,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;