From 8c30b2cae8c4a3e6952cfcb881ae1a8d8f366366 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 18 Oct 2025 15:20:47 +0100 Subject: [PATCH 1/9] Commit --- Doc/deprecations/pending-removal-in-3.20.rst | 1 + Doc/library/decimal.rst | 8 ++++++++ Doc/whatsnew/3.15.rst | 1 + Lib/_pydecimal.py | 9 ++++++--- Lib/decimal.py | 9 ++++++++- Lib/test/test_decimal.py | 17 +++++++++++++++++ ...025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst | 2 ++ Modules/_decimal/_decimal.c | 2 +- 8 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index c86979c8ff91e9..1670f72da9d63e 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:`ipaddress` - :mod:`json` - :mod:`logging` (``__date__`` also deprecated) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 0b99a832405549..046606f0fbef43 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1601,6 +1601,14 @@ are also included in the pure Python version for compatibility. .. versionadded:: 3.8.3 +.. data:: SPEC_VERSION + + The highest version of the General Decimal Arithmetic + Specification that this implementation complies with. + See https://speleotrove.com/decimal/ for the specification. + + .. versionadded:: next + Rounding modes -------------- diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d3ae7c21a0358b..9b0d7c0f199809 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:`ipaddress` - :mod:`json` - :mod:`logging` (``__date__`` also deprecated) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 97a629fe92ccec..12eb05fb70f2da 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/ __libmpdec_version__ = "2.4.2" # compatible libmpdec version import math as _math diff --git a/Lib/decimal.py b/Lib/decimal.py index 530bdfb38953d9..814f1e4db4874e 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -100,10 +100,17 @@ try: from _decimal import * - from _decimal import __version__ # noqa: F401 from _decimal import __libmpdec_version__ # noqa: F401 except ImportError: import _pydecimal import sys _pydecimal.__doc__ = __doc__ sys.modules[__name__] = _pydecimal + +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return "1.70" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 08a8f4c3b36bd6..781caf17c6a5cc 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -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..3cad5bc810379f 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7891,7 +7891,7 @@ _decimal_exec(PyObject *m) } /* Add specification version number */ - CHECK_INT(PyModule_AddStringConstant(m, "__version__", "1.70")); + CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", "1.70")); CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version())); return 0; From d583f55e4c6ca4bff1b64f48d2266e5001a95daa Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 18 Oct 2025 16:15:32 +0100 Subject: [PATCH 2/9] Commit --- Lib/_pydecimal.py | 8 ++++++++ Lib/decimal.py | 10 +--------- Lib/test/test_decimal.py | 2 +- Modules/_decimal/_decimal.c | 23 +++++++++++++++++++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index 12eb05fb70f2da..cda7536087b786 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -6402,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 "1.70" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/decimal.py b/Lib/decimal.py index 814f1e4db4874e..3c2bbdd89179b2 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -97,20 +97,12 @@ 1 >>> """ - try: from _decimal import * from _decimal import __libmpdec_version__ # noqa: F401 + from _decimal import __getattr__ # noqa: F401 except ImportError: import _pydecimal import sys _pydecimal.__doc__ = __doc__ sys.modules[__name__] = _pydecimal - -def __getattr__(name): - if name == "__version__": - from warnings import _deprecated - - _deprecated("__version__", remove=(3, 20)) - return "1.70" # Do not change - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 781caf17c6a5cc..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__)) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 3cad5bc810379f..e23d2de061d8cb 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7566,12 +7566,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("1.7"); + } + + 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 } }; From 61a43b90775f4968483f46164c9f3760f2d7cec4 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 18 Oct 2025 16:16:43 +0100 Subject: [PATCH 3/9] Commit --- Lib/decimal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/decimal.py b/Lib/decimal.py index 3c2bbdd89179b2..cf13050bfeff53 100644 --- a/Lib/decimal.py +++ b/Lib/decimal.py @@ -97,6 +97,7 @@ 1 >>> """ + try: from _decimal import * from _decimal import __libmpdec_version__ # noqa: F401 From 0a2a9fbf486e899130328b940108bea36862dcbe Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Sat, 18 Oct 2025 23:05:36 +0100 Subject: [PATCH 4/9] Correct version number Co-authored-by: Victor Stinner --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index e23d2de061d8cb..3f081c5e306598 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -7580,7 +7580,7 @@ decimal_getattr(PyObject *self, PyObject *args) 1) < 0) { return NULL; } - return PyUnicode_FromString("1.7"); + return PyUnicode_FromString("1.70"); } PyErr_Format(PyExc_AttributeError, "module 'decimal' has no attribute %R", name); From 9b43d7fc71e0bbd5a6e96ba66f69021b6ce2d230 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Sun, 19 Oct 2025 08:40:47 +0100 Subject: [PATCH 5/9] Apply suggestions from code review Co-authored-by: Sergey B Kirpichev --- Doc/library/decimal.rst | 2 +- Lib/_pydecimal.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index 046606f0fbef43..d2778c71b5a99c 100644 --- a/Doc/library/decimal.rst +++ b/Doc/library/decimal.rst @@ -1605,7 +1605,7 @@ are also included in the pure Python version for compatibility. The highest version of the General Decimal Arithmetic Specification that this implementation complies with. - See https://speleotrove.com/decimal/ for the specification. + See https://speleotrove.com/decimal/decarith.html for the specification. .. versionadded:: next diff --git a/Lib/_pydecimal.py b/Lib/_pydecimal.py index cda7536087b786..7858e8dc68e44e 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -56,7 +56,7 @@ __xname__ = __name__ # sys.modules lookup (--without-threads) __name__ = 'decimal' # For pickling SPEC_VERSION = '1.70' # Highest version of the spec this complies with - # See https://speleotrove.com/decimal/ + # See https://speleotrove.com/decimal/decarith.html __libmpdec_version__ = "2.4.2" # compatible libmpdec version import math as _math @@ -6408,5 +6408,5 @@ def __getattr__(name): from warnings import _deprecated _deprecated("__version__", remove=(3, 20)) - return "1.70" # Do not change + return SPEC_VERSION raise AttributeError(f"module {__name__!r} has no attribute {name!r}") From 82ef916249fcd13a392c51d85897dfcef7097243 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 19 Oct 2025 08:55:33 +0100 Subject: [PATCH 6/9] Convert to macro --- Modules/_decimal/_decimal.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 3f081c5e306598..4d89b4036ffc6d 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -58,6 +58,8 @@ #include "clinic/_decimal.c.h" +#define MPD_SPEC_VERSION "1.70" + /*[clinic input] module _decimal class _decimal.Decimal "PyObject *" "&dec_spec" @@ -7580,7 +7582,7 @@ decimal_getattr(PyObject *self, PyObject *args) 1) < 0) { return NULL; } - return PyUnicode_FromString("1.70"); + return PyUnicode_FromString(MPD_SPEC_VERSION); } PyErr_Format(PyExc_AttributeError, "module 'decimal' has no attribute %R", name); @@ -7914,7 +7916,7 @@ _decimal_exec(PyObject *m) } /* Add specification version number */ - CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", "1.70")); + CHECK_INT(PyModule_AddStringConstant(m, "SPEC_VERSION", MPD_SPEC_VERSION)); CHECK_INT(PyModule_AddStringConstant(m, "__libmpdec_version__", mpd_version())); return 0; From 16b7bc869d5253ce8771b76859d59c2e49d19555 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 19 Oct 2025 09:03:59 +0100 Subject: [PATCH 7/9] Add comment --- Modules/_decimal/_decimal.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 4d89b4036ffc6d..44917ed7357cc1 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -58,7 +58,8 @@ #include "clinic/_decimal.c.h" -#define MPD_SPEC_VERSION "1.70" +#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 From 56a75e0d807a47f4df29f16c0567693bff9454d6 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sun, 19 Oct 2025 10:09:42 +0100 Subject: [PATCH 8/9] Relocate constant in doc --- Doc/library/decimal.rst | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/Doc/library/decimal.rst b/Doc/library/decimal.rst index d2778c71b5a99c..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. +---------------------------------+---------------------+-------------------------------+ @@ -1601,14 +1610,6 @@ are also included in the pure Python version for compatibility. .. versionadded:: 3.8.3 -.. 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 - Rounding modes -------------- From ba3295bbac65f0244513336274aedaaceedc742b Mon Sep 17 00:00:00 2001 From: Stan Ulbrych <89152624+StanFromIreland@users.noreply.github.com> Date: Mon, 20 Oct 2025 13:33:48 +0100 Subject: [PATCH 9/9] Apply suggestions from code review Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Doc/deprecations/pending-removal-in-3.20.rst | 2 +- Doc/whatsnew/3.15.rst | 2 +- Lib/_pydecimal.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index d5bb293bcff22b..c0feda1968258d 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -8,7 +8,7 @@ Pending removal in Python 3.20 - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` - - :mod:`decimal` (Use :data:`decimal.SPEC_VERSION` instead) + - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`imaplib` - :mod:`ipaddress` - :mod:`json` diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index 29733874daeb23..25427058be1f0b 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -825,7 +825,7 @@ New deprecations - :mod:`argparse` - :mod:`csv` - :mod:`!ctypes.macholib` - - :mod:`decimal` (Use :data:`decimal.SPEC_VERSION` instead) + - :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 7858e8dc68e44e..ef889ea0cc834c 100644 --- a/Lib/_pydecimal.py +++ b/Lib/_pydecimal.py @@ -50,7 +50,7 @@ 'HAVE_CONTEXTVAR', # Highest version of the spec this module complies with - 'SPEC_VERSION' + 'SPEC_VERSION', ] __xname__ = __name__ # sys.modules lookup (--without-threads)