]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-76007: Deprecate `__version__` attribute in `decimal` (#140302)
authorStan Ulbrych <89152624+StanFromIreland@users.noreply.github.com>
Sun, 26 Oct 2025 11:01:04 +0000 (11:01 +0000)
committerGitHub <noreply@github.com>
Sun, 26 Oct 2025 11:01:04 +0000 (12:01 +0100)
Co-authored-by: Victor Stinner <vstinner@python.org>
Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com>
Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com>
Doc/deprecations/pending-removal-in-3.20.rst
Doc/library/decimal.rst
Doc/whatsnew/3.15.rst
Lib/_pydecimal.py
Lib/decimal.py
Lib/test/test_decimal.py
Misc/NEWS.d/next/Library/2025-10-18-15-20-25.gh-issue-76007.SNUzRq.rst [new file with mode: 0644]
Modules/_decimal/_decimal.c

index 21a561e7952afdce08d1969039fbb3c9eeba371b..c0feda1968258dcbea84f8ba09d04f236a8c0601 100644 (file)
@@ -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`
index 0b99a8324055494164283282dc52d9199f0da724..985153b5443f5ca61fc087f46ee24d6de58ecf3f 100644 (file)
@@ -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.
 
 +---------------------------------+---------------------+-------------------------------+
index 54222db73e6848c3f9f242739c8864b9d3083ed2..85b4c12544a0c9c9f8fca0c66e79e2754d07938f 100644 (file)
@@ -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`
index 97a629fe92ccec470f8649e0f81f030eddaab8a8..ef889ea0cc834c113618f4bbe3a950a46bfc136f 100644 (file)
@@ -47,13 +47,16 @@ __all__ = [
     '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 @@ _PyHASH_NAN = sys.hash_info.nan
 # _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}")
index 530bdfb38953d995e715f02fca97f9160c4fab2c..cf13050bfeff531e05d3e05d8244d2efc34ab493 100644 (file)
@@ -100,8 +100,8 @@ NaN
 
 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
index 08a8f4c3b36bd677656f54688edee651ff348435..b520b062ebc6858d63b4be4e81dd5f5e1eef5f6f 100644 (file)
@@ -4474,7 +4474,7 @@ class CheckAttributes(unittest.TestCase):
         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 @@ class SignatureTest(unittest.TestCase):
         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 (file)
index 0000000..6a91fc4
--- /dev/null
@@ -0,0 +1,2 @@
+:mod:`decimal`: Deprecate ``__version__`` and replace with
+:data:`decimal.SPEC_VERSION`.
index 4e2a49531263605001fa75035eff69562cf0c0c2..44917ed7357cc1a0b195da0d731462bc64a1d980 100644 (file)
@@ -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;