Skip to content

Commit dc0b1f8

Browse files
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) <noreply@anthropic.com>
1 parent 89bfac6 commit dc0b1f8

6 files changed

Lines changed: 193 additions & 4 deletions

File tree

‎Doc/library/decimal.rst‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1632,6 +1632,46 @@ are also included in the pure Python version for compatibility.
16321632

16331633
.. versionadded:: 3.8.3
16341634

1635+
The following constants are only available in the C module.
1636+
1637+
.. data:: LIBMPDEC_VERSION
1638+
1639+
The version string of the libmpdec library that was used for building
1640+
the module.
1641+
This may be different from the libmpdec library actually used at runtime,
1642+
which is available as :const:`libmpdec_version`.
1643+
1644+
.. versionadded:: next
1645+
1646+
.. data:: libmpdec_version
1647+
1648+
The version string of the libmpdec library actually loaded by the
1649+
interpreter.
1650+
1651+
.. versionadded:: next
1652+
1653+
.. data:: LIBMPDEC_VERSION_INFO
1654+
1655+
A named tuple containing the three components of the libmpdec library
1656+
version that was used for building the module:
1657+
*major*, *minor*, and *micro*.
1658+
All values are integers.
1659+
The components can also be accessed by name,
1660+
so ``decimal.LIBMPDEC_VERSION_INFO[0]`` is equivalent to
1661+
``decimal.LIBMPDEC_VERSION_INFO.major`` and so on.
1662+
This may be different from the libmpdec library actually used at runtime,
1663+
which is available as :const:`libmpdec_version_info`.
1664+
1665+
.. versionadded:: next
1666+
1667+
.. data:: libmpdec_version_info
1668+
1669+
A named tuple containing the version of the libmpdec library
1670+
actually loaded by the interpreter,
1671+
with the same fields as :const:`LIBMPDEC_VERSION_INFO`.
1672+
1673+
.. versionadded:: next
1674+
16351675

16361676
Rounding modes
16371677
--------------

‎Doc/whatsnew/3.16.rst‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,16 @@ concurrent.futures
347347
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
348348

349349

350+
decimal
351+
-------
352+
353+
* Added constants :const:`~decimal.LIBMPDEC_VERSION`,
354+
:const:`~decimal.libmpdec_version`, :const:`~decimal.LIBMPDEC_VERSION_INFO`,
355+
and :const:`~decimal.libmpdec_version_info` in the C implementation,
356+
which provide information about the version of the libmpdec library in use.
357+
(Contributed by Serhiy Storchaka in :gh:`157475`.)
358+
359+
350360
dbm.gnu
351361
-------
352362

‎Lib/test/pythoninfo.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -758,7 +758,7 @@ def collect_decimal(info_add):
758758
except ImportError:
759759
return
760760

761-
attributes = ('__libmpdec_version__',)
761+
attributes = ('LIBMPDEC_VERSION', 'libmpdec_version')
762762
copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
763763

764764

‎Lib/test/test_decimal.py‎

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4534,8 +4534,13 @@ def test_module_attributes(self):
45344534

45354535
self.assertEqual(C.SPEC_VERSION, P.SPEC_VERSION)
45364536

4537-
self.assertLessEqual(set(dir(C)), set(dir(P)))
4538-
self.assertEqual([n for n in dir(C) if n[:2] != '__'], sorted(P.__all__))
4537+
# Information about the libmpdec library, specific to the C module.
4538+
libmpdec_names = {'LIBMPDEC_VERSION', 'LIBMPDEC_VERSION_INFO',
4539+
'libmpdec_version', 'libmpdec_version_info'}
4540+
self.assertLessEqual(set(dir(C)) - libmpdec_names, set(dir(P)))
4541+
self.assertEqual([n for n in dir(C)
4542+
if n[:2] != '__' and n not in libmpdec_names],
4543+
sorted(P.__all__))
45394544

45404545
def test_context_attributes(self):
45414546

@@ -5059,6 +5064,38 @@ def test_constants(self):
50595064
self.assertEqual(C.DecTraps,
50605065
C.DecErrors|C.DecOverflow|C.DecUnderflow)
50615066

5067+
@requires_cdecimal
5068+
class CVersion(unittest.TestCase):
5069+
"""Information about the libmpdec library in _decimal"""
5070+
5071+
def _test_libmpdec_version(self, v, string):
5072+
self.assertIsInstance(v[:], tuple)
5073+
self.assertEqual(len(v), 3)
5074+
self.assertIsInstance(v[0], int)
5075+
self.assertIsInstance(v[1], int)
5076+
self.assertIsInstance(v[2], int)
5077+
self.assertIsInstance(v.major, int)
5078+
self.assertIsInstance(v.minor, int)
5079+
self.assertIsInstance(v.micro, int)
5080+
self.assertEqual(v[0], v.major)
5081+
self.assertEqual(v[1], v.minor)
5082+
self.assertEqual(v[2], v.micro)
5083+
self.assertGreaterEqual(v.major, 2)
5084+
self.assertGreaterEqual(v.minor, 0)
5085+
self.assertGreaterEqual(v.micro, 0)
5086+
self.assertEqual(string, '%d.%d.%d' % v)
5087+
5088+
def test_libmpdec_version(self):
5089+
if support.verbose:
5090+
print(f'LIBMPDEC_VERSION = {C.LIBMPDEC_VERSION}', flush=True)
5091+
print(f'libmpdec_version = {C.libmpdec_version}', flush=True)
5092+
print(f'LIBMPDEC_VERSION_INFO = {C.LIBMPDEC_VERSION_INFO}', flush=True)
5093+
print(f'libmpdec_version_info = {C.libmpdec_version_info}', flush=True)
5094+
self._test_libmpdec_version(C.LIBMPDEC_VERSION_INFO, C.LIBMPDEC_VERSION)
5095+
self._test_libmpdec_version(C.libmpdec_version_info, C.libmpdec_version)
5096+
self.assertEqual(C.LIBMPDEC_VERSION_INFO[0], C.libmpdec_version_info[0])
5097+
self.assertIs(C.libmpdec_version, C.__libmpdec_version__)
5098+
50625099
@requires_cdecimal
50635100
class CWhitebox(unittest.TestCase):
50645101
"""Whitebox testing for _decimal"""
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Add constants :const:`decimal.LIBMPDEC_VERSION`,
2+
:const:`decimal.libmpdec_version`, :const:`decimal.LIBMPDEC_VERSION_INFO`,
3+
and :const:`decimal.libmpdec_version_info` in the C implementation, which
4+
provide information about the version of the libmpdec library in use.

‎Modules/_decimal/_decimal.c‎

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7756,6 +7756,104 @@ cfunc_noargs(PyTypeObject *t, const char *name)
77567756
return NULL;
77577757
}
77587758

7759+
PyDoc_STRVAR(libmpdec_version_info__doc__,
7760+
"decimal.libmpdec_version_info\n\
7761+
\n\
7762+
libmpdec version information as a named tuple.");
7763+
7764+
static PyStructSequence_Field libmpdec_version_info_fields[] = {
7765+
{"major", "Major release number"},
7766+
{"minor", "Minor release number"},
7767+
{"micro", "Micro release number"},
7768+
{0}
7769+
};
7770+
7771+
static PyStructSequence_Desc libmpdec_version_info_desc = {
7772+
"decimal.libmpdec_version_info", /* name */
7773+
libmpdec_version_info__doc__, /* doc */
7774+
libmpdec_version_info_fields, /* fields */
7775+
3
7776+
};
7777+
7778+
static PyObject *
7779+
make_libmpdec_version_info(PyTypeObject *type, int major, int minor, int micro)
7780+
{
7781+
PyObject *version;
7782+
int pos = 0;
7783+
7784+
version = PyStructSequence_New(type);
7785+
if (version == NULL) {
7786+
return NULL;
7787+
}
7788+
7789+
#define SetItem(VALUE) \
7790+
PyStructSequence_SET_ITEM(version, pos++, VALUE); \
7791+
if (PyErr_Occurred()) { \
7792+
Py_DECREF(version); \
7793+
return NULL; \
7794+
}
7795+
7796+
SetItem(PyLong_FromLong(major))
7797+
SetItem(PyLong_FromLong(minor))
7798+
SetItem(PyLong_FromLong(micro))
7799+
#undef SetItem
7800+
7801+
return version;
7802+
}
7803+
7804+
static PyObject *
7805+
parse_libmpdec_version_info(PyTypeObject *type, const char *version)
7806+
{
7807+
int major, minor, micro;
7808+
if (sscanf(version, "%d.%d.%d", &major, &minor, &micro) != 3) {
7809+
PyErr_Format(PyExc_RuntimeError,
7810+
"unexpected libmpdec version string %s", version);
7811+
return NULL;
7812+
}
7813+
return make_libmpdec_version_info(type, major, minor, micro);
7814+
}
7815+
7816+
static int
7817+
add_version_constants(PyObject *m)
7818+
{
7819+
const char *version = mpd_version();
7820+
if (PyModule_AddStringConstant(m, "LIBMPDEC_VERSION", MPD_VERSION) < 0) {
7821+
return -1;
7822+
}
7823+
PyObject *obj = PyUnicode_FromString(version);
7824+
if (obj == NULL) {
7825+
return -1;
7826+
}
7827+
if (PyModule_AddObjectRef(m, "libmpdec_version", obj) < 0 ||
7828+
PyModule_AddObjectRef(m, "__libmpdec_version__", obj) < 0)
7829+
{
7830+
Py_DECREF(obj);
7831+
return -1;
7832+
}
7833+
Py_DECREF(obj);
7834+
PyTypeObject *version_type;
7835+
version_type = PyStructSequence_NewType(&libmpdec_version_info_desc);
7836+
if (version_type == NULL) {
7837+
return -1;
7838+
}
7839+
if (PyModule_Add(m, "LIBMPDEC_VERSION_INFO",
7840+
make_libmpdec_version_info(version_type, MPD_MAJOR_VERSION,
7841+
MPD_MINOR_VERSION,
7842+
MPD_MICRO_VERSION)) < 0)
7843+
{
7844+
Py_DECREF(version_type);
7845+
return -1;
7846+
}
7847+
if (PyModule_Add(m, "libmpdec_version_info",
7848+
parse_libmpdec_version_info(version_type, version)) < 0)
7849+
{
7850+
Py_DECREF(version_type);
7851+
return -1;
7852+
}
7853+
Py_DECREF(version_type);
7854+
return 0;
7855+
}
7856+
77597857
static int minalloc_is_set = 0;
77607858

77617859
static int
@@ -8004,7 +8102,7 @@ _decimal_exec(PyObject *m)
80048102

80058103
/* Add specification version number */
80068104
CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", MPD_SPEC_VERSION));
8007-
CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version()));
8105+
CHECK_INT(add_version_constants(m));
80088106

80098107
return 0;
80108108

0 commit comments

Comments
 (0)