https://github.com/python/cpython/commit/84897d2e30b1b478f3de4fb9a7cd41fc2363da80
commit: 84897d2e30b1b478f3de4fb9a7cd41fc2363da80
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-07-19T12:04:30Z
summary:
gh-153903: Use `@ctypes.util.wrap_dll_function()` with pythonapi (#154030)
Use the `@ctypes.util.wrap_dll_function()` decorator with
ctypes.pythonapi to wrap Python C API functions in the test suite.
files:
M Lib/test/test_capi/test_misc.py
M Lib/test/test_code.py
M Lib/test/test_ctypes/test_refcounts.py
M Lib/test/test_exceptions.py
M Lib/test/test_frame.py
M Lib/test/test_free_threading/test_capi.py
M Lib/test/test_free_threading/test_code.py
M Lib/test/test_gc.py
M Lib/test/test_threading.py
diff --git a/Lib/test/test_capi/test_misc.py b/Lib/test/test_capi/test_misc.py
index 6d84f0b8c305dfb..7d668843d07debc 100644
--- a/Lib/test/test_capi/test_misc.py
+++ b/Lib/test/test_capi/test_misc.py
@@ -3043,22 +3043,37 @@ def test_pack_version(self):
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)
diff --git a/Lib/test/test_code.py b/Lib/test/test_code.py
index bd2fa1d7b70421a..631e5fd2499b13e 100644
--- a/Lib/test/test_code.py
+++ b/Lib/test/test_code.py
@@ -1540,21 +1540,26 @@ async def afunc():
[(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 @@ def myfree(ptr):
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
diff --git a/Lib/test/test_ctypes/test_refcounts.py
b/Lib/test/test_ctypes/test_refcounts.py
index 1815649ceb5fff9..d79937277e7df69 100644
--- a/Lib/test/test_ctypes/test_refcounts.py
+++ b/Lib/test/test_ctypes/test_refcounts.py
@@ -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
diff --git a/Lib/test/test_exceptions.py b/Lib/test/test_exceptions.py
index cc7faef93e1af7c..6e458017298dffe 100644
--- a/Lib/test/test_exceptions.py
+++ b/Lib/test/test_exceptions.py
@@ -440,10 +440,16 @@ def test_WindowsError(self):
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
diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py
index fc50e16575da2b9..a3a329cb578a8dc 100644
--- a/Lib/test/test_frame.py
+++ b/Lib/test/test_frame.py
@@ -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)
diff --git a/Lib/test/test_free_threading/test_capi.py
b/Lib/test/test_free_threading/test_capi.py
index 146d7cfc97adb7d..b0be94629af009d 100644
--- a/Lib/test/test_free_threading/test_capi.py
+++ b/Lib/test/test_free_threading/test_capi.py
@@ -1,4 +1,4 @@
-import ctypes
+import ctypes.util
import sys
import unittest
@@ -6,9 +6,9 @@
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
[email protected]_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 @@ def test_pyimport_addmoduleref_thread_safe(self):
results = []
def worker():
- module = _PyImport_AddModuleRef(module_name_bytes)
+ module = PyImport_AddModuleRef(module_name_bytes)
results.append(module)
for _ in range(NUM_ITERS):
diff --git a/Lib/test/test_free_threading/test_code.py
b/Lib/test/test_free_threading/test_code.py
index 2fc5eea3773c399..121f00c1d7a92d9 100644
--- a/Lib/test/test_free_threading/test_code.py
+++ b/Lib/test/test_free_threading/test_code.py
@@ -12,25 +12,28 @@
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).
diff --git a/Lib/test/test_gc.py b/Lib/test/test_gc.py
index 3fc084ea6e9c6e9..4c721c01a34f070 100644
--- a/Lib/test/test_gc.py
+++ b/Lib/test/test_gc.py
@@ -1434,6 +1434,11 @@ def test_refcount_errors(self):
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 @@ def test_refcount_errors(self):
# 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
diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py
index 3d01804513bde98..96b43936be92cda 100644
--- a/Lib/test/test_threading.py
+++ b/Lib/test/test_threading.py
@@ -326,9 +326,13 @@ def f(mutex):
@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 @@ def test_finalize_running_thread(self):
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 @@ def test_finalize_running_thread(self):
# 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)
_______________________________________________
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]