diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 21a561e7952afd..c0feda1968258d 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -8,6 +8,7 @@ Pending removal in Python 3.20 - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` + - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`imaplib` - :mod:`ipaddress` - :mod:`json` diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 0b99a832405549..985153b5443f5c 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1569,7 +1569,16 @@ In addition to the three supplied contexts, new contexts can be created with the Constants --------- -The constants in this section are only relevant for the C module. They +.. data:: SPEC_VERSION + + The highest version of the General Decimal Arithmetic + Specification that this implementation complies with. + See https://speleotrove.com/decimal/decarith.html for the specification. + + .. versionadded:: next + + +The following constants are only relevant for the C module. They are also included in the pure Python version for compatibility. +---------------------------------+---------------------+-------------------------------+ diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index b360769b7e50fb..25427058be1f0b 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -825,6 +825,7 @@ New deprecations - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` + - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`imaplib` - :mod:`ipaddress` - :mod:`json` diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 97a629fe92ccec..ef889ea0cc834c 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -47,13 +47,16 @@ 'HAVE_THREADS', # C version: compile time choice that enables the coroutine local context - 'HAVE_CONTEXTVAR' + 'HAVE_CONTEXTVAR', + + # Highest version of the spec this module complies with + 'SPEC_VERSION', ] __xname__ = __name__ # sys.modules lookup (--without-threads) __name__ = 'decimal' # For pickling -__version__ = '1.70' # Highest version of the spec this complies with - # See http://speleotrove.com/decimal/ +SPEC_VERSION = '1.70' # Highest version of the spec this complies with + # See https://speleotrove.com/decimal/decarith.html __libmpdec_version__ = "2.4.2" # compatible libmpdec version import math as _math @@ -6399,3 +6402,11 @@ def _format_number(is_negative, intpart, fracpart, exp, spec): # _PyHASH_10INV is the inverse of 10 modulo the prime _PyHASH_MODULUS _PyHASH_10INV = pow(10, _PyHASH_MODULUS - 2, _PyHASH_MODULUS) del sys + +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return SPEC_VERSION + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/decimal.py b/Lib/decimal.py index 530bdfb38953d9..cf13050bfeff53 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -100,8 +100,8 @@ try: from _decimal import * - from _decimal import __version__ # noqa: F401 from _decimal import __libmpdec_version__ # noqa: F401 + from _decimal import __getattr__ # noqa: F401 except ImportError: import _pydecimal import sys diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 08a8f4c3b36bd6..b520b062ebc685 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4474,7 +4474,7 @@ def test_module_attributes(self): self.assertTrue(C.HAVE_THREADS is True or C.HAVE_THREADS is False) self.assertTrue(P.HAVE_THREADS is True or P.HAVE_THREADS is False) - self.assertEqual(C.__version__, P.__version__) + 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__)) @@ -5929,6 +5929,23 @@ def doit(ty): doit('Context') +class TestModule: + def test_deprecated__version__(self): + with self.assertWarnsRegex( + DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + ) as cm: + getattr(self.decimal, "__version__") + self.assertEqual(cm.filename, __file__) + + +@requires_cdecimal +class CTestModule(TestModule, unittest.TestCase): + decimal = C +class PyTestModule(TestModule, unittest.TestCase): + decimal = P + + def load_tests(loader, tests, pattern): if TODO_TESTS is not None: # Run only Arithmetic tests diff --git a/Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst b/Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst new file mode 100644 index 00000000000000..6a91fc41b0ab0c --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst @@ -0,0 +1,2 @@ +:mod:`decimal`: Deprecate ``__version__`` and replace with +:data:`decimal.SPEC_VERSION`. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 4e2a4953126360..44917ed7357cc1 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -58,6 +58,9 @@ #include "clinic/_decimal.c.h" +#define MPD_SPEC_VERSION "1.70" // Highest version of the spec this complies with + // See https://speleotrove.com/decimal/decarith.html + /*[clinic input] module _decimal class _decimal.Decimal "PyObject *" "&dec_spec" @@ -7566,12 +7569,35 @@ static PyType_Spec context_spec = { }; +static PyObject * +decimal_getattr(PyObject *self, PyObject *args) +{ + PyObject *name; + if (!PyArg_UnpackTuple(args, "__getattr__", 1, 1, &name)) { + return NULL; + } + + if (PyUnicode_Check(name) && PyUnicode_EqualToUTF8(name, "__version__")) { + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + 1) < 0) { + return NULL; + } + return PyUnicode_FromString(MPD_SPEC_VERSION); + } + + PyErr_Format(PyExc_AttributeError, "module 'decimal' has no attribute %R", name); + return NULL; +} + + static PyMethodDef _decimal_methods [] = { _DECIMAL_GETCONTEXT_METHODDEF _DECIMAL_SETCONTEXT_METHODDEF _DECIMAL_LOCALCONTEXT_METHODDEF _DECIMAL_IEEECONTEXT_METHODDEF + {"__getattr__", decimal_getattr, METH_VARARGS, "Module __getattr__"}, { NULL, NULL, 1, NULL } }; @@ -7891,7 +7917,7 @@ _decimal_exec(PyObject *m) } /* Add specification version number */ - CHECK_INT(PyModule_AddStringConstant(m, "__version__", "1.70")); + CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", MPD_SPEC_VERSION)); CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version())); return 0;