Skip to content

Commit e508482

Browse files
gh-157475: Add structured version info for decimal
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 a844747 commit e508482

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
@@ -340,6 +340,16 @@ concurrent.futures
340340
(Contributed by xzmeng and Serhiy Storchaka in :gh:`108518`.)
341341

342342

343+
decimal
344+
-------
345+
346+
* Added constants :const:`~decimal.LIBMPDEC_VERSION`,
347+
:const:`~decimal.libmpdec_version`, :const:`~decimal.LIBMPDEC_VERSION_INFO`,
348+
and :const:`~decimal.libmpdec_version_info` in the C implementation,
349+
which provide information about the version of the libmpdec library in use.
350+
(Contributed by Serhiy Storchaka in :gh:`157475`.)
351+
352+
343353
difflib
344354
-------
345355

Lib/test/pythoninfo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -761,7 +761,7 @@ def collect_decimal(info_add):
761761
except ImportError:
762762
return
763763

764-
attributes = ('__libmpdec_version__',)
764+
attributes = ('LIBMPDEC_VERSION', 'libmpdec_version')
765765
copy_attributes(info_add, _decimal, '_decimal.%s', attributes)
766766

767767

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
@@ -7744,6 +7744,104 @@ cfunc_noargs(PyTypeObject *t, const char *name)
77447744
return NULL;
77457745
}
77467746

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

77497847
static int
@@ -7992,7 +8090,7 @@ _decimal_exec(PyObject *m)
79928090

79938091
/* Add specification version number */
79948092
CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", MPD_SPEC_VERSION));
7995-
CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version()));
8093+
CHECK_INT(add_version_constants(m));
79968094

79978095
return 0;
79988096

0 commit comments

Comments
 (0)