https://github.com/python/cpython/commit/00026d19c272d1cf3527027bd6f9de910ff45070
commit: 00026d19c272d1cf3527027bd6f9de910ff45070
branch: main
author: Stan Ulbrych <[email protected]>
committer: vstinner <[email protected]>
date: 2025-10-26T12:01:04+01:00
summary:

gh-76007: Deprecate `__version__` attribute in `decimal` (#140302)

Co-authored-by: Victor Stinner <[email protected]>
Co-authored-by: Sergey B Kirpichev <[email protected]>
Co-authored-by: Hugo van Kemenade <[email protected]>

files:
A Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst
M Doc/deprecations/pending-removal-in-3.20.rst
M Doc/library/decimal.rst
M Doc/whatsnew/3.15.rst
M Lib/_pydecimal.py
M Lib/decimal.py
M Lib/test/test_decimal.py
M Modules/_decimal/_decimal.c

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 54222db73e6848..85b4c12544a0c9 100644
--- a/Doc/whatsnew/3.15.rst
+++ b/Doc/whatsnew/3.15.rst
@@ -851,6 +851,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;

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to