]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153903: Use `@ctypes.util.wrap_dll_function()` with pythonapi (#154030)
authorVictor Stinner <vstinner@python.org>
Sun, 19 Jul 2026 12:04:30 +0000 (14:04 +0200)
committerGitHub <noreply@github.com>
Sun, 19 Jul 2026 12:04:30 +0000 (12:04 +0000)
Use the `@ctypes.util.wrap_dll_function()` decorator with
ctypes.pythonapi to wrap Python C API functions in the test suite.

Lib/test/test_capi/test_misc.py
Lib/test/test_code.py
Lib/test/test_ctypes/test_refcounts.py
Lib/test/test_exceptions.py
Lib/test/test_frame.py
Lib/test/test_free_threading/test_capi.py
Lib/test/test_free_threading/test_code.py
Lib/test/test_gc.py
Lib/test/test_threading.py

index 6d84f0b8c305dfb90c9aa350c481b97e2018cbcc..7d668843d07debc348fabaf6e81e42c2847cb6cf 100644 (file)
@@ -3043,22 +3043,37 @@ class TestVersions(unittest.TestCase):
 
     def test_pack_full_version_ctypes(self):
         ctypes = import_helper.import_module('ctypes')
-        ctypes_func = ctypes.pythonapi.Py_PACK_FULL_VERSION
-        ctypes_func.restype = ctypes.c_uint32
-        ctypes_func.argtypes = [ctypes.c_int] * 5
+        import ctypes.util  # noqa: F811
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def Py_PACK_FULL_VERSION(
+            x: ctypes.c_int,
+            y: ctypes.c_int,
+            z: ctypes.c_int,
+            level: ctypes.c_int,
+            serial: ctypes.c_int,
+        ) -> ctypes.c_uint32:
+            pass
+
         for *args, expected in self.full_cases:
             with self.subTest(hexversion=hex(expected)):
-                result = ctypes_func(*args)
+                result = Py_PACK_FULL_VERSION(*args)
                 self.assertEqual(result, expected)
 
     def test_pack_version_ctypes(self):
         ctypes = import_helper.import_module('ctypes')
-        ctypes_func = ctypes.pythonapi.Py_PACK_VERSION
-        ctypes_func.restype = ctypes.c_uint32
-        ctypes_func.argtypes = [ctypes.c_int] * 2
+        import ctypes.util  # noqa: F811
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def Py_PACK_VERSION(
+            x: ctypes.c_int,
+            y: ctypes.c_int,
+        ) -> ctypes.c_uint32:
+            pass
+
         for *args, expected in self.xy_cases:
             with self.subTest(hexversion=hex(expected)):
-                result = ctypes_func(*args)
+                result = Py_PACK_VERSION(*args)
                 self.assertEqual(result, expected)
 
 
index bd2fa1d7b70421a610e3bab7f20b8f9e10f603c6..631e5fd2499b13ecea439acb4d33add25d96955e 100644 (file)
@@ -1540,21 +1540,26 @@ class CodeLocationTest(unittest.TestCase):
             [(1,1,3)])
 
 if check_impl_detail(cpython=True) and ctypes is not None:
-    py = ctypes.pythonapi
-    freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
-
-    RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
-    RequestCodeExtraIndex.argtypes = (freefunc,)
-    RequestCodeExtraIndex.restype = ctypes.c_ssize_t
-
-    SetExtra = py.PyUnstable_Code_SetExtra
-    SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
-    SetExtra.restype = ctypes.c_int
-
-    GetExtra = py.PyUnstable_Code_GetExtra
-    GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
-                         ctypes.POINTER(ctypes.c_voidp))
-    GetExtra.restype = ctypes.c_int
+    import ctypes.util
+    freefunc = ctypes.CFUNCTYPE(None, ctypes.c_voidp)
+
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Eval_RequestCodeExtraIndex(free: freefunc) -> ctypes.c_ssize_t:
+        pass
+
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Code_SetExtra(code: ctypes.py_object,
+                                 index: ctypes.c_ssize_t,
+                                 extra: ctypes.c_voidp) -> ctypes.c_int:
+        pass
+    SetExtra = PyUnstable_Code_SetExtra
+
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Code_GetExtra(code: ctypes.py_object,
+                                 index: ctypes.c_ssize_t,
+                                 extra: ctypes.POINTER(ctypes.c_voidp)) -> ctypes.c_int:
+        pass
+    GetExtra = PyUnstable_Code_GetExtra
 
     LAST_FREED = None
     def myfree(ptr):
@@ -1562,7 +1567,7 @@ if check_impl_detail(cpython=True) and ctypes is not None:
         LAST_FREED = ptr
 
     FREE_FUNC = freefunc(myfree)
-    FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
+    FREE_INDEX = PyUnstable_Eval_RequestCodeExtraIndex(FREE_FUNC)
     # Make sure myfree sticks around at least as long as the interpreter,
     # since we (currently) can't unregister the function and leaving a
     # dangling pointer will cause a crash on deallocation of code objects if
index 1815649ceb5fff94a39cb9bc884702ff3f284649..d79937277e7df696503bb591de9a94454e2a8ca2 100644 (file)
@@ -127,9 +127,9 @@ class PyObjectRestypeTest(unittest.TestCase):
     def test_restype_py_object_with_null_return(self):
         # Test that a function which returns a NULL PyObject *
         # without setting an exception does not crash.
-        PyErr_Occurred = ctypes.pythonapi.PyErr_Occurred
-        PyErr_Occurred.argtypes = []
-        PyErr_Occurred.restype = ctypes.py_object
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyErr_Occurred() -> ctypes.py_object:
+            pass
 
         # At this point, there's no exception set, so PyErr_Occurred
         # returns NULL. Given the restype is py_object, the
index cc7faef93e1af7c9fb49a694f736ce1b6e1f8082..6e458017298dffe267549ce0db2d817422b2bbba 100644 (file)
@@ -440,10 +440,16 @@ class ExceptionTests(unittest.TestCase):
     def test_windows_message(self):
         """Should fill in unknown error code in Windows error message"""
         ctypes = import_module('ctypes')
+        import ctypes.util  # noqa: F811
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyErr_SetFromWindowsErr(ierr: ctypes.c_int) -> ctypes.py_object:
+            pass
+
         # this error code has no message, Python formats it as hexadecimal
         code = 3765269347
-        with self.assertRaisesRegex(OSError, 'Windows Error 0x%x' % code):
-            ctypes.pythonapi.PyErr_SetFromWindowsErr(code)
+        with self.assertRaisesRegex(OSError, f'Windows Error 0x{code:x}'):
+            PyErr_SetFromWindowsErr(code)
 
     def testAttributes(self):
         # test that exception attributes are happy
index fc50e16575da2b997b2fa966a39b65b93dc943a6..a3a329cb578a8dc288b69d752d9e7dd02afdb0c7 100644 (file)
@@ -821,28 +821,37 @@ class TestFrameCApi(unittest.TestCase):
     def test_basic(self):
         x = 1
         ctypes = import_helper.import_module('ctypes')
-        PyEval_GetFrameLocals = ctypes.pythonapi.PyEval_GetFrameLocals
-        PyEval_GetFrameLocals.restype = ctypes.py_object
+        import ctypes.util  # noqa: F811
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyEval_GetFrameLocals() -> ctypes.py_object:
+            pass
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyEval_GetFrameGlobals() -> ctypes.py_object:
+            pass
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyEval_GetFrameBuiltins() -> ctypes.py_object:
+            pass
+
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyFrame_GetLocals(frame: ctypes.py_object) -> ctypes.py_object:
+            pass
+
         frame_locals = PyEval_GetFrameLocals()
         self.assertTrue(type(frame_locals), dict)
         self.assertEqual(frame_locals['x'], 1)
         frame_locals['x'] = 2
         self.assertEqual(x, 1)
 
-        PyEval_GetFrameGlobals = ctypes.pythonapi.PyEval_GetFrameGlobals
-        PyEval_GetFrameGlobals.restype = ctypes.py_object
         frame_globals = PyEval_GetFrameGlobals()
         self.assertTrue(type(frame_globals), dict)
         self.assertIs(frame_globals, globals())
 
-        PyEval_GetFrameBuiltins = ctypes.pythonapi.PyEval_GetFrameBuiltins
-        PyEval_GetFrameBuiltins.restype = ctypes.py_object
         frame_builtins = PyEval_GetFrameBuiltins()
         self.assertEqual(frame_builtins, __builtins__)
 
-        PyFrame_GetLocals = ctypes.pythonapi.PyFrame_GetLocals
-        PyFrame_GetLocals.argtypes = [ctypes.py_object]
-        PyFrame_GetLocals.restype = ctypes.py_object
         frame = sys._getframe()
         f_locals = PyFrame_GetLocals(frame)
         self.assertTrue(f_locals['x'], 1)
index 146d7cfc97adb7d2690dfc01ad22138925f195e7..b0be94629af009da9901d758a71106ae1ea1bf69 100644 (file)
@@ -1,4 +1,4 @@
-import ctypes
+import ctypes.util
 import sys
 import unittest
 
@@ -6,9 +6,9 @@ from test.support import threading_helper
 from test.support.threading_helper import run_concurrently
 
 
-_PyImport_AddModuleRef = ctypes.pythonapi.PyImport_AddModuleRef
-_PyImport_AddModuleRef.argtypes = (ctypes.c_char_p,)
-_PyImport_AddModuleRef.restype = ctypes.py_object
+@ctypes.util.wrap_dll_function(ctypes.pythonapi)
+def PyImport_AddModuleRef(name: ctypes.c_char_p) -> ctypes.py_object:
+    pass
 
 
 @threading_helper.requires_working_threading()
@@ -26,7 +26,7 @@ class TestImportCAPI(unittest.TestCase):
         results = []
 
         def worker():
-            module = _PyImport_AddModuleRef(module_name_bytes)
+            module = PyImport_AddModuleRef(module_name_bytes)
             results.append(module)
 
         for _ in range(NUM_ITERS):
index 2fc5eea3773c39931b3f73f276a1282fadbedf93..121f00c1d7a92d9553faf1c6b4c87a94b7f32e2c 100644 (file)
@@ -12,25 +12,28 @@ from test.support import threading_helper
 from test.support.threading_helper import run_concurrently
 
 if ctypes is not None:
-    capi = ctypes.pythonapi
+    import ctypes.util
 
     freefunc = ctypes.CFUNCTYPE(None, ctypes.c_voidp)
 
-    RequestCodeExtraIndex = capi.PyUnstable_Eval_RequestCodeExtraIndex
-    RequestCodeExtraIndex.argtypes = (freefunc,)
-    RequestCodeExtraIndex.restype = ctypes.c_ssize_t
-
-    SetExtra = capi.PyUnstable_Code_SetExtra
-    SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
-    SetExtra.restype = ctypes.c_int
-
-    GetExtra = capi.PyUnstable_Code_GetExtra
-    GetExtra.argtypes = (
-        ctypes.py_object,
-        ctypes.c_ssize_t,
-        ctypes.POINTER(ctypes.c_voidp),
-    )
-    GetExtra.restype = ctypes.c_int
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Eval_RequestCodeExtraIndex(free: freefunc) -> ctypes.c_ssize_t:
+        pass
+    RequestCodeExtraIndex = PyUnstable_Eval_RequestCodeExtraIndex
+
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Code_SetExtra(code: ctypes.py_object,
+                                 index: ctypes.c_ssize_t,
+                                 extra: ctypes.c_voidp) -> ctypes.c_int:
+        pass
+    SetExtra = PyUnstable_Code_SetExtra
+
+    @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+    def PyUnstable_Code_GetExtra(code: ctypes.py_object,
+                                 index: ctypes.c_ssize_t,
+                                 extra: ctypes.POINTER(ctypes.c_voidp)) -> ctypes.c_int:
+        pass
+    GetExtra = PyUnstable_Code_GetExtra
 
 # Note: each call to RequestCodeExtraIndex permanently allocates a slot
 # (the counter is monotonically increasing), up to MAX_CO_EXTRA_USERS (255).
index 3fc084ea6e9c6e9940347b2ba677a91cdb83fe5f..4c721c01a34f0708fda4940aea90aafa92dd777a 100644 (file)
@@ -1434,6 +1434,11 @@ class GCCallbackTests(unittest.TestCase):
         import subprocess
         code = textwrap.dedent('''
             from test.support import gc_collect, SuppressCrashReport
+            import ctypes.util
+
+            @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+            def Py_DecRef(o: ctypes.py_object) -> None:
+                pass
 
             a = [1, 2, 3]
             b = [a, a]
@@ -1445,8 +1450,7 @@ class GCCallbackTests(unittest.TestCase):
             # Simulate the refcount of "a" being too low (compared to the
             # references held on it by live data), but keeping it above zero
             # (to avoid deallocating it):
-            import ctypes
-            ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
+            Py_DecRef(a)
             del a
             del b
 
index 3d01804513bde982dd5265974393f19b4e8661f5..96b43936be92cda3c7700b775d34a3bb29c9df8b 100644 (file)
@@ -326,9 +326,13 @@ class ThreadTests(BaseTestCase):
     @cpython_only
     def test_PyThreadState_SetAsyncExc(self):
         ctypes = import_module("ctypes")
+        import ctypes.util  # noqa: F811
 
-        set_async_exc = ctypes.pythonapi.PyThreadState_SetAsyncExc
-        set_async_exc.argtypes = (ctypes.c_ulong, ctypes.py_object)
+        @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+        def PyThreadState_SetAsyncExc(id: ctypes.c_ulong,
+                                      exc: ctypes.py_object) -> ctypes.c_int:
+            pass
+        set_async_exc = PyThreadState_SetAsyncExc
 
         class AsyncExc(Exception):
             pass
@@ -485,7 +489,17 @@ class ThreadTests(BaseTestCase):
         import_module("ctypes")
 
         rc, out, err = assert_python_failure("-c", """if 1:
-            import ctypes, sys, time, _thread
+            import ctypes.util, sys, time, _thread
+
+            PyGILState_STATE = ctypes.c_int  # enum
+
+            @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+            def PyGILState_Ensure() -> PyGILState_STATE:
+                pass
+
+            @ctypes.util.wrap_dll_function(ctypes.pythonapi)
+            def PyGILState_Release(oldstate: PyGILState_STATE) -> None:
+                pass
 
             # This lock is used as a simple event variable.
             ready = _thread.allocate_lock()
@@ -494,8 +508,8 @@ class ThreadTests(BaseTestCase):
             # Module globals are cleared before __del__ is run
             # So we save the functions in class dict
             class C:
-                ensure = ctypes.pythonapi.PyGILState_Ensure
-                release = ctypes.pythonapi.PyGILState_Release
+                ensure = PyGILState_Ensure
+                release = PyGILState_Release
                 def __del__(self):
                     state = self.ensure()
                     self.release(state)