Skip to content

Commit 84897d2

Browse files
authored
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.
1 parent 5380589 commit 84897d2

9 files changed

Lines changed: 122 additions & 66 deletions

File tree

Lib/test/test_capi/test_misc.py

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3043,22 +3043,37 @@ def test_pack_version(self):
30433043

30443044
def test_pack_full_version_ctypes(self):
30453045
ctypes = import_helper.import_module('ctypes')
3046-
ctypes_func = ctypes.pythonapi.Py_PACK_FULL_VERSION
3047-
ctypes_func.restype = ctypes.c_uint32
3048-
ctypes_func.argtypes = [ctypes.c_int] * 5
3046+
import ctypes.util # noqa: F811
3047+
3048+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
3049+
def Py_PACK_FULL_VERSION(
3050+
x: ctypes.c_int,
3051+
y: ctypes.c_int,
3052+
z: ctypes.c_int,
3053+
level: ctypes.c_int,
3054+
serial: ctypes.c_int,
3055+
) -> ctypes.c_uint32:
3056+
pass
3057+
30493058
for *args, expected in self.full_cases:
30503059
with self.subTest(hexversion=hex(expected)):
3051-
result = ctypes_func(*args)
3060+
result = Py_PACK_FULL_VERSION(*args)
30523061
self.assertEqual(result, expected)
30533062

30543063
def test_pack_version_ctypes(self):
30553064
ctypes = import_helper.import_module('ctypes')
3056-
ctypes_func = ctypes.pythonapi.Py_PACK_VERSION
3057-
ctypes_func.restype = ctypes.c_uint32
3058-
ctypes_func.argtypes = [ctypes.c_int] * 2
3065+
import ctypes.util # noqa: F811
3066+
3067+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
3068+
def Py_PACK_VERSION(
3069+
x: ctypes.c_int,
3070+
y: ctypes.c_int,
3071+
) -> ctypes.c_uint32:
3072+
pass
3073+
30593074
for *args, expected in self.xy_cases:
30603075
with self.subTest(hexversion=hex(expected)):
3061-
result = ctypes_func(*args)
3076+
result = Py_PACK_VERSION(*args)
30623077
self.assertEqual(result, expected)
30633078

30643079

Lib/test/test_code.py

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1540,29 +1540,34 @@ async def afunc():
15401540
[(1,1,3)])
15411541

15421542
if check_impl_detail(cpython=True) and ctypes is not None:
1543-
py = ctypes.pythonapi
1544-
freefunc = ctypes.CFUNCTYPE(None,ctypes.c_voidp)
1545-
1546-
RequestCodeExtraIndex = py.PyUnstable_Eval_RequestCodeExtraIndex
1547-
RequestCodeExtraIndex.argtypes = (freefunc,)
1548-
RequestCodeExtraIndex.restype = ctypes.c_ssize_t
1549-
1550-
SetExtra = py.PyUnstable_Code_SetExtra
1551-
SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
1552-
SetExtra.restype = ctypes.c_int
1553-
1554-
GetExtra = py.PyUnstable_Code_GetExtra
1555-
GetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t,
1556-
ctypes.POINTER(ctypes.c_voidp))
1557-
GetExtra.restype = ctypes.c_int
1543+
import ctypes.util
1544+
freefunc = ctypes.CFUNCTYPE(None, ctypes.c_voidp)
1545+
1546+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
1547+
def PyUnstable_Eval_RequestCodeExtraIndex(free: freefunc) -> ctypes.c_ssize_t:
1548+
pass
1549+
1550+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
1551+
def PyUnstable_Code_SetExtra(code: ctypes.py_object,
1552+
index: ctypes.c_ssize_t,
1553+
extra: ctypes.c_voidp) -> ctypes.c_int:
1554+
pass
1555+
SetExtra = PyUnstable_Code_SetExtra
1556+
1557+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
1558+
def PyUnstable_Code_GetExtra(code: ctypes.py_object,
1559+
index: ctypes.c_ssize_t,
1560+
extra: ctypes.POINTER(ctypes.c_voidp)) -> ctypes.c_int:
1561+
pass
1562+
GetExtra = PyUnstable_Code_GetExtra
15581563

15591564
LAST_FREED = None
15601565
def myfree(ptr):
15611566
global LAST_FREED
15621567
LAST_FREED = ptr
15631568

15641569
FREE_FUNC = freefunc(myfree)
1565-
FREE_INDEX = RequestCodeExtraIndex(FREE_FUNC)
1570+
FREE_INDEX = PyUnstable_Eval_RequestCodeExtraIndex(FREE_FUNC)
15661571
# Make sure myfree sticks around at least as long as the interpreter,
15671572
# since we (currently) can't unregister the function and leaving a
15681573
# dangling pointer will cause a crash on deallocation of code objects if

Lib/test/test_ctypes/test_refcounts.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,9 +127,9 @@ class PyObjectRestypeTest(unittest.TestCase):
127127
def test_restype_py_object_with_null_return(self):
128128
# Test that a function which returns a NULL PyObject *
129129
# without setting an exception does not crash.
130-
PyErr_Occurred = ctypes.pythonapi.PyErr_Occurred
131-
PyErr_Occurred.argtypes = []
132-
PyErr_Occurred.restype = ctypes.py_object
130+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
131+
def PyErr_Occurred() -> ctypes.py_object:
132+
pass
133133

134134
# At this point, there's no exception set, so PyErr_Occurred
135135
# returns NULL. Given the restype is py_object, the

Lib/test/test_exceptions.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,16 @@ def test_WindowsError(self):
440440
def test_windows_message(self):
441441
"""Should fill in unknown error code in Windows error message"""
442442
ctypes = import_module('ctypes')
443+
import ctypes.util # noqa: F811
444+
445+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
446+
def PyErr_SetFromWindowsErr(ierr: ctypes.c_int) -> ctypes.py_object:
447+
pass
448+
443449
# this error code has no message, Python formats it as hexadecimal
444450
code = 3765269347
445-
with self.assertRaisesRegex(OSError, 'Windows Error 0x%x' % code):
446-
ctypes.pythonapi.PyErr_SetFromWindowsErr(code)
451+
with self.assertRaisesRegex(OSError, f'Windows Error 0x{code:x}'):
452+
PyErr_SetFromWindowsErr(code)
447453

448454
def testAttributes(self):
449455
# test that exception attributes are happy

Lib/test/test_frame.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -821,28 +821,37 @@ class TestFrameCApi(unittest.TestCase):
821821
def test_basic(self):
822822
x = 1
823823
ctypes = import_helper.import_module('ctypes')
824-
PyEval_GetFrameLocals = ctypes.pythonapi.PyEval_GetFrameLocals
825-
PyEval_GetFrameLocals.restype = ctypes.py_object
824+
import ctypes.util # noqa: F811
825+
826+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
827+
def PyEval_GetFrameLocals() -> ctypes.py_object:
828+
pass
829+
830+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
831+
def PyEval_GetFrameGlobals() -> ctypes.py_object:
832+
pass
833+
834+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
835+
def PyEval_GetFrameBuiltins() -> ctypes.py_object:
836+
pass
837+
838+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
839+
def PyFrame_GetLocals(frame: ctypes.py_object) -> ctypes.py_object:
840+
pass
841+
826842
frame_locals = PyEval_GetFrameLocals()
827843
self.assertTrue(type(frame_locals), dict)
828844
self.assertEqual(frame_locals['x'], 1)
829845
frame_locals['x'] = 2
830846
self.assertEqual(x, 1)
831847

832-
PyEval_GetFrameGlobals = ctypes.pythonapi.PyEval_GetFrameGlobals
833-
PyEval_GetFrameGlobals.restype = ctypes.py_object
834848
frame_globals = PyEval_GetFrameGlobals()
835849
self.assertTrue(type(frame_globals), dict)
836850
self.assertIs(frame_globals, globals())
837851

838-
PyEval_GetFrameBuiltins = ctypes.pythonapi.PyEval_GetFrameBuiltins
839-
PyEval_GetFrameBuiltins.restype = ctypes.py_object
840852
frame_builtins = PyEval_GetFrameBuiltins()
841853
self.assertEqual(frame_builtins, __builtins__)
842854

843-
PyFrame_GetLocals = ctypes.pythonapi.PyFrame_GetLocals
844-
PyFrame_GetLocals.argtypes = [ctypes.py_object]
845-
PyFrame_GetLocals.restype = ctypes.py_object
846855
frame = sys._getframe()
847856
f_locals = PyFrame_GetLocals(frame)
848857
self.assertTrue(f_locals['x'], 1)

Lib/test/test_free_threading/test_capi.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
import ctypes
1+
import ctypes.util
22
import sys
33
import unittest
44

55
from test.support import threading_helper
66
from test.support.threading_helper import run_concurrently
77

88

9-
_PyImport_AddModuleRef = ctypes.pythonapi.PyImport_AddModuleRef
10-
_PyImport_AddModuleRef.argtypes = (ctypes.c_char_p,)
11-
_PyImport_AddModuleRef.restype = ctypes.py_object
9+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
10+
def PyImport_AddModuleRef(name: ctypes.c_char_p) -> ctypes.py_object:
11+
pass
1212

1313

1414
@threading_helper.requires_working_threading()
@@ -26,7 +26,7 @@ def test_pyimport_addmoduleref_thread_safe(self):
2626
results = []
2727

2828
def worker():
29-
module = _PyImport_AddModuleRef(module_name_bytes)
29+
module = PyImport_AddModuleRef(module_name_bytes)
3030
results.append(module)
3131

3232
for _ in range(NUM_ITERS):

Lib/test/test_free_threading/test_code.py

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,28 @@
1212
from test.support.threading_helper import run_concurrently
1313

1414
if ctypes is not None:
15-
capi = ctypes.pythonapi
15+
import ctypes.util
1616

1717
freefunc = ctypes.CFUNCTYPE(None, ctypes.c_voidp)
1818

19-
RequestCodeExtraIndex = capi.PyUnstable_Eval_RequestCodeExtraIndex
20-
RequestCodeExtraIndex.argtypes = (freefunc,)
21-
RequestCodeExtraIndex.restype = ctypes.c_ssize_t
22-
23-
SetExtra = capi.PyUnstable_Code_SetExtra
24-
SetExtra.argtypes = (ctypes.py_object, ctypes.c_ssize_t, ctypes.c_voidp)
25-
SetExtra.restype = ctypes.c_int
26-
27-
GetExtra = capi.PyUnstable_Code_GetExtra
28-
GetExtra.argtypes = (
29-
ctypes.py_object,
30-
ctypes.c_ssize_t,
31-
ctypes.POINTER(ctypes.c_voidp),
32-
)
33-
GetExtra.restype = ctypes.c_int
19+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
20+
def PyUnstable_Eval_RequestCodeExtraIndex(free: freefunc) -> ctypes.c_ssize_t:
21+
pass
22+
RequestCodeExtraIndex = PyUnstable_Eval_RequestCodeExtraIndex
23+
24+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
25+
def PyUnstable_Code_SetExtra(code: ctypes.py_object,
26+
index: ctypes.c_ssize_t,
27+
extra: ctypes.c_voidp) -> ctypes.c_int:
28+
pass
29+
SetExtra = PyUnstable_Code_SetExtra
30+
31+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
32+
def PyUnstable_Code_GetExtra(code: ctypes.py_object,
33+
index: ctypes.c_ssize_t,
34+
extra: ctypes.POINTER(ctypes.c_voidp)) -> ctypes.c_int:
35+
pass
36+
GetExtra = PyUnstable_Code_GetExtra
3437

3538
# Note: each call to RequestCodeExtraIndex permanently allocates a slot
3639
# (the counter is monotonically increasing), up to MAX_CO_EXTRA_USERS (255).

Lib/test/test_gc.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1434,6 +1434,11 @@ def test_refcount_errors(self):
14341434
import subprocess
14351435
code = textwrap.dedent('''
14361436
from test.support import gc_collect, SuppressCrashReport
1437+
import ctypes.util
1438+
1439+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
1440+
def Py_DecRef(o: ctypes.py_object) -> None:
1441+
pass
14371442
14381443
a = [1, 2, 3]
14391444
b = [a, a]
@@ -1445,8 +1450,7 @@ def test_refcount_errors(self):
14451450
# Simulate the refcount of "a" being too low (compared to the
14461451
# references held on it by live data), but keeping it above zero
14471452
# (to avoid deallocating it):
1448-
import ctypes
1449-
ctypes.pythonapi.Py_DecRef(ctypes.py_object(a))
1453+
Py_DecRef(a)
14501454
del a
14511455
del b
14521456

Lib/test/test_threading.py

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,13 @@ def f(mutex):
326326
@cpython_only
327327
def test_PyThreadState_SetAsyncExc(self):
328328
ctypes = import_module("ctypes")
329+
import ctypes.util # noqa: F811
329330

330-
set_async_exc = ctypes.pythonapi.PyThreadState_SetAsyncExc
331-
set_async_exc.argtypes = (ctypes.c_ulong, ctypes.py_object)
331+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
332+
def PyThreadState_SetAsyncExc(id: ctypes.c_ulong,
333+
exc: ctypes.py_object) -> ctypes.c_int:
334+
pass
335+
set_async_exc = PyThreadState_SetAsyncExc
332336

333337
class AsyncExc(Exception):
334338
pass
@@ -485,7 +489,17 @@ def test_finalize_running_thread(self):
485489
import_module("ctypes")
486490

487491
rc, out, err = assert_python_failure("-c", """if 1:
488-
import ctypes, sys, time, _thread
492+
import ctypes.util, sys, time, _thread
493+
494+
PyGILState_STATE = ctypes.c_int # enum
495+
496+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
497+
def PyGILState_Ensure() -> PyGILState_STATE:
498+
pass
499+
500+
@ctypes.util.wrap_dll_function(ctypes.pythonapi)
501+
def PyGILState_Release(oldstate: PyGILState_STATE) -> None:
502+
pass
489503
490504
# This lock is used as a simple event variable.
491505
ready = _thread.allocate_lock()
@@ -494,8 +508,8 @@ def test_finalize_running_thread(self):
494508
# Module globals are cleared before __del__ is run
495509
# So we save the functions in class dict
496510
class C:
497-
ensure = ctypes.pythonapi.PyGILState_Ensure
498-
release = ctypes.pythonapi.PyGILState_Release
511+
ensure = PyGILState_Ensure
512+
release = PyGILState_Release
499513
def __del__(self):
500514
state = self.ensure()
501515
self.release(state)

0 commit comments

Comments
 (0)