From 38ad651b678da098400a8df8c133ebf5ced12965 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 15 Dec 2025 13:30:23 +0200 Subject: [PATCH] gh-76007: Deprecate `__version__` attribute in `ctypes` (#142679) --- Doc/deprecations/pending-removal-in-3.20.rst | 1 + Doc/whatsnew/3.15.rst | 1 + Lib/ctypes/__init__.py | 15 +++++++----- Lib/test/test_ctypes/__init__.py | 17 +++++++++++++ ...5-12-13-21-19-28.gh-issue-76007.6fs_gT.rst | 1 + Modules/_ctypes/_ctypes.c | 1 - Modules/_ctypes/callproc.c | 24 +++++++++++++++++++ 7 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst diff --git a/Doc/deprecations/pending-removal-in-3.20.rst b/Doc/deprecations/pending-removal-in-3.20.rst index 1e517531c953..4a6a6c3c43a7 100644 --- a/Doc/deprecations/pending-removal-in-3.20.rst +++ b/Doc/deprecations/pending-removal-in-3.20.rst @@ -7,6 +7,7 @@ Pending removal in Python 3.20 - :mod:`argparse` - :mod:`csv` + - :mod:`ctypes` - :mod:`!ctypes.macholib` - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`http.server` diff --git a/Doc/whatsnew/3.15.rst b/Doc/whatsnew/3.15.rst index d9a34fe920d1..ccf6c76f1e0f 100644 --- a/Doc/whatsnew/3.15.rst +++ b/Doc/whatsnew/3.15.rst @@ -1032,6 +1032,7 @@ New deprecations - :mod:`argparse` - :mod:`csv` + - :mod:`ctypes` - :mod:`!ctypes.macholib` - :mod:`decimal` (use :data:`decimal.SPEC_VERSION` instead) - :mod:`http.server` diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index ab5b656e6e5d..aec92f3aee24 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -5,12 +5,9 @@ import sys as _sys import sysconfig as _sysconfig import types as _types -__version__ = "1.1.0" - from _ctypes import Union, Structure, Array from _ctypes import _Pointer from _ctypes import CFuncPtr as _CFuncPtr -from _ctypes import __version__ as _ctypes_version from _ctypes import RTLD_LOCAL, RTLD_GLOBAL from _ctypes import ArgumentError from _ctypes import SIZEOF_TIME_T @@ -18,9 +15,6 @@ from _ctypes import CField from struct import calcsize as _calcsize -if __version__ != _ctypes_version: - raise Exception("Version number mismatch", __version__, _ctypes_version) - if _os.name == "nt": from _ctypes import COMError, CopyComPointer, FormatError @@ -673,3 +667,12 @@ else: raise SystemError(f"Unexpected sizeof(time_t): {SIZEOF_TIME_T=}") _reset_cache() + + +def __getattr__(name): + if name == "__version__": + from warnings import _deprecated + + _deprecated("__version__", remove=(3, 20)) + return "1.1.0" # Do not change + raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/Lib/test/test_ctypes/__init__.py b/Lib/test/test_ctypes/__init__.py index eb9126cbe180..d848beb1ff18 100644 --- a/Lib/test/test_ctypes/__init__.py +++ b/Lib/test/test_ctypes/__init__.py @@ -1,4 +1,5 @@ import os +import unittest from test import support from test.support import import_helper @@ -6,5 +7,21 @@ from test.support import import_helper # skip tests if the _ctypes extension was not built import_helper.import_module('ctypes') + +class TestModule(unittest.TestCase): + def test_deprecated__version__(self): + import ctypes + import _ctypes + + for mod in (ctypes, _ctypes): + with self.subTest(mod=mod): + with self.assertWarnsRegex( + DeprecationWarning, + "'__version__' is deprecated and slated for removal in Python 3.20", + ) as cm: + getattr(mod, "__version__") + self.assertEqual(cm.filename, __file__) + + def load_tests(*args): return support.load_package_tests(os.path.dirname(__file__), *args) diff --git a/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst b/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst new file mode 100644 index 000000000000..99f73bb094c6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-13-21-19-28.gh-issue-76007.6fs_gT.rst @@ -0,0 +1 @@ +Deprecate ``__version__`` from :mod:`ctypes`. Patch by Hugo van Kemenade. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 91fd23d413de..774ac71ce9ec 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -6334,7 +6334,6 @@ _ctypes_add_objects(PyObject *mod) MOD_ADD("FUNCFLAG_USE_ERRNO", PyLong_FromLong(FUNCFLAG_USE_ERRNO)); MOD_ADD("FUNCFLAG_USE_LASTERROR", PyLong_FromLong(FUNCFLAG_USE_LASTERROR)); MOD_ADD("FUNCFLAG_PYTHONAPI", PyLong_FromLong(FUNCFLAG_PYTHONAPI)); - MOD_ADD("__version__", PyUnicode_FromString("1.1.0")); MOD_ADD("_memmove_addr", PyLong_FromVoidPtr(memmove)); MOD_ADD("_memset_addr", PyLong_FromVoidPtr(memset)); diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index a8c16547e4b2..9a1c1ff8bb9c 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -1990,8 +1990,32 @@ buffer_info(PyObject *self, PyObject *arg) } +static PyObject * +_ctypes_getattr(PyObject *Py_UNUSED(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.1.0"); // Do not change + } + + PyErr_Format(PyExc_AttributeError, + "module '_ctypes' has no attribute %R", name); + return NULL; +} + PyMethodDef _ctypes_module_methods[] = { + {"__getattr__", _ctypes_getattr, METH_VARARGS}, {"get_errno", get_errno, METH_NOARGS}, {"set_errno", set_errno, METH_VARARGS}, {"_unpickle", unpickle, METH_VARARGS }, -- 2.47.3