gh-104533: Use `@ctypes.util.struct` decorator (#154031)

vstinner <[email protected]>
Newsgroups gmane.comp.python.cvs
Message-ID <[email protected]>
https://github.com/python/cpython/commit/ee50ed3ed010e26495300778b30b57da5cadc933
commit: ee50ed3ed010e26495300778b30b57da5cadc933
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-07-19T12:08:56Z
summary:

gh-104533: Use `@ctypes.util.struct` decorator (#154031)

Use also `@ctypes.util.wrap_dll_function()` decorator.

files:
M Lib/test/_test_multiprocessing.py
M Lib/test/support/__init__.py
M Lib/test/test_buffer.py
M Lib/test/test_codecs.py
M Lib/test/test_ctypes/test_refcounts.py
M Lib/test/test_ctypes/test_unicode.py
M Lib/test/test_ctypes/test_win32_com_foreign_func.py
M Lib/test/test_io/utils.py

diff --git a/Lib/test/_test_multiprocessing.py b/Lib/test/_test_multiprocessing.py
index 115a187a8a85882..8f665b3a98a0371 100644
--- a/Lib/test/_test_multiprocessing.py
+++ b/Lib/test/_test_multiprocessing.py
@@ -161,9 +161,10 @@ def wait_for_handle(handle, timeout):
 #
 
 try:
-    from ctypes import Structure, c_int, c_double, c_longlong
+    from ctypes.util import struct as ctypes_struct
+    from ctypes import c_int, c_double, c_longlong
 except ImportError:
-    Structure = object
+    def ctypes_struct(cls): return cls
     c_int = c_double = c_longlong = None
 
 
@@ -4390,12 +4391,11 @@ def test_free_from_gc(self):
 #
 #
 
-class _Foo(Structure):
-    _fields_ = [
-        ('x', c_int),
-        ('y', c_double),
-        ('z', c_longlong,)
-        ]
+@ctypes_struct
+class _Foo:
+    x: c_int
+    y: c_double
+    z: c_longlong
 
 class _TestSharedCTypes(BaseTestCase):
 
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index d17d9a2ecf8d9b0..e5bc2fd9b3d0954 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -236,20 +236,41 @@ def _is_gui_available():
         # if Python is running as a service (such as the buildbot service),
         # gui interaction may be disallowed
         import ctypes
+        import ctypes.util
         import ctypes.wintypes
+
         UOI_FLAGS = 1
         WSF_VISIBLE = 0x0001
-        class USEROBJECTFLAGS(ctypes.Structure):
-            _fields_ = [("fInherit", ctypes.wintypes.BOOL),
-                        ("fReserved", ctypes.wintypes.BOOL),
-                        ("dwFlags", ctypes.wintypes.DWORD)]
-        dll = ctypes.windll.user32
-        h = dll.GetProcessWindowStation()
+
+        @ctypes.util.struct
+        class USEROBJECTFLAGS:
+            fInherit: ctypes.wintypes.BOOL
+            fReserved: ctypes.wintypes.BOOL
+            dwFlags: ctypes.wintypes.DWORD
+
+        user32 = ctypes.windll.user32
+
+        @ctypes.util.wrap_dll_function(user32)
+        def GetProcessWindowStation() -> ctypes.wintypes.HANDLE:
+            ...
+
+        h = GetProcessWindowStation()
         if not h:
             raise ctypes.WinError()
+
+        @ctypes.util.wrap_dll_function(user32)
+        def GetUserObjectInformationW(
+            hObj: ctypes.wintypes.HANDLE,
+            nIndex: ctypes.c_int,
+            pvInfo: ctypes.c_void_p,
+            nLength: ctypes.wintypes.DWORD,
+            lpnLengthNeeded: ctypes.POINTER(ctypes.wintypes.DWORD),
+        ) -> ctypes.wintypes.BOOL:
+            ...
+
         uof = USEROBJECTFLAGS()
         needed = ctypes.wintypes.DWORD()
-        res = dll.GetUserObjectInformationW(h,
+        res = GetUserObjectInformationW(h,
             UOI_FLAGS,
             ctypes.byref(uof),
             ctypes.sizeof(uof),
diff --git a/Lib/test/test_buffer.py b/Lib/test/test_buffer.py
index 3213a4751273431..453dafe2709eb2f 100644
--- a/Lib/test/test_buffer.py
+++ b/Lib/test/test_buffer.py
@@ -37,6 +37,7 @@
 
 try:
     import ctypes
+    import ctypes.util
 except ImportError:
     ctypes = None
 
@@ -2849,8 +2850,11 @@ def test_memoryview_cast_1D_ND(self):
 
         if ctypes:
             # format: "T{>l:x:>d:y:}"
-            class BEPoint(ctypes.BigEndianStructure):
-                _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_double)]
+            @ctypes.util.struct(endian='big')
+            class BEPoint:
+                x: ctypes.c_long
+                y: ctypes.c_double
+
             point = BEPoint(100, 200.1)
             m1 = memoryview(point)
             m2 = m1.cast('B')
@@ -3250,8 +3254,11 @@ def test_memoryview_compare_special_cases(self):
         # Some ctypes format strings are unknown to the struct module.
         if ctypes:
             # format: "T{>l:x:>l:y:}"
-            class BEPoint(ctypes.BigEndianStructure):
-                _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
+            @ctypes.util.struct(endian='big')
+            class BEPoint:
+                x: ctypes.c_long
+                y: ctypes.c_long
+
             point = BEPoint(100, 200)
             a = memoryview(point)
             b = memoryview(point)
@@ -3988,8 +3995,11 @@ def test_memoryview_tobytes(self):
         # Unknown formats are handled: tobytes() purely depends on itemsize.
         if ctypes:
             # format: "T{>l:x:>l:y:}"
-            class BEPoint(ctypes.BigEndianStructure):
-                _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)]
+            @ctypes.util.struct(endian='big')
+            class BEPoint:
+                x: ctypes.c_long
+                y: ctypes.c_long
+
             point = BEPoint(100, 200)
             a = memoryview(point)
             self.assertEqual(a.tobytes(), bytes(point))
diff --git a/Lib/test/test_codecs.py b/Lib/test/test_codecs.py
index c18b203f42f59f3..31704955df3e14e 100644
--- a/Lib/test/test_codecs.py
+++ b/Lib/test/test_codecs.py
@@ -36,21 +36,25 @@ def check(input, expect):
         self.assertEqual(coder(input), (expect, len(input)))
     return check
 
-# On small versions of Windows like Windows IoT or Windows Nano Server not all codepages are present
+# On small versions of Windows like Windows IoT or Windows Nano Server,
+# not all codepages are present
 def is_code_page_present(cp):
-    from ctypes import POINTER, WINFUNCTYPE, WinDLL, Structure
+    from ctypes import POINTER, WINFUNCTYPE, WinDLL
+    from ctypes.util import struct
     from ctypes.wintypes import BOOL, BYTE, WCHAR, UINT, DWORD
 
     MAX_LEADBYTES = 12  # 5 ranges, 2 bytes ea., 0 term.
     MAX_DEFAULTCHAR = 2 # single or double byte
     MAX_PATH = 260
-    class CPINFOEXW(Structure):
-        _fields_ = [("MaxCharSize", UINT),
-                    ("DefaultChar", BYTE*MAX_DEFAULTCHAR),
-                    ("LeadByte", BYTE*MAX_LEADBYTES),
-                    ("UnicodeDefaultChar", WCHAR),
-                    ("CodePage", UINT),
-                    ("CodePageName", WCHAR*MAX_PATH)]
+
+    @struct
+    class CPINFOEXW:
+        MaxCharSize: UINT
+        DefaultChar: BYTE * MAX_DEFAULTCHAR
+        LeadByte: BYTE * MAX_LEADBYTES
+        UnicodeDefaultChar: WCHAR
+        CodePage: UINT
+        CodePageName: WCHAR * MAX_PATH
 
     prototype = WINFUNCTYPE(BOOL, UINT, DWORD, POINTER(CPINFOEXW))
     GetCPInfoEx = prototype(("GetCPInfoExW", WinDLL("kernel32")))
diff --git a/Lib/test/test_ctypes/test_refcounts.py b/Lib/test/test_ctypes/test_refcounts.py
index d79937277e7df69..5191862658c5671 100644
--- a/Lib/test/test_ctypes/test_refcounts.py
+++ b/Lib/test/test_ctypes/test_refcounts.py
@@ -54,8 +54,10 @@ def func(*args):
         gc.collect()
         self.assertEqual(sys.getrefcount(func), orig_refcount)
 
-        class X(ctypes.Structure):
-            _fields_ = [("a", OtherCallback)]
+        @ctypes.util.struct
+        class X:
+            a: OtherCallback
+
         x = X()
         x.a = OtherCallback(func)
 
diff --git a/Lib/test/test_ctypes/test_unicode.py b/Lib/test/test_ctypes/test_unicode.py
index d9e17371d135724..8e91f8f2c566803 100644
--- a/Lib/test/test_ctypes/test_unicode.py
+++ b/Lib/test/test_ctypes/test_unicode.py
@@ -1,4 +1,4 @@
-import ctypes
+import ctypes.util
 import unittest
 from test.support import import_helper
 _ctypes_test = import_helper.import_module("_ctypes_test")
@@ -26,8 +26,10 @@ def test_buffers(self):
         self.assertEqual(buf[6:5:-1], "")
 
     def test_embedded_null(self):
-        class TestStruct(ctypes.Structure):
-            _fields_ = [("unicode", ctypes.c_wchar_p)]
+        @ctypes.util.struct
+        class TestStruct:
+            unicode: ctypes.c_wchar_p
+
         t = TestStruct()
         # This would raise a ValueError:
         t.unicode = "foo\0bar\0\0"
diff --git a/Lib/test/test_ctypes/test_win32_com_foreign_func.py b/Lib/test/test_ctypes/test_win32_com_foreign_func.py
index 7e54f8f6c31d33f..c797ab98cf1e424 100644
--- a/Lib/test/test_ctypes/test_win32_com_foreign_func.py
+++ b/Lib/test/test_ctypes/test_win32_com_foreign_func.py
@@ -1,4 +1,4 @@
-import ctypes
+import ctypes.util
 import gc
 import sys
 import unittest
@@ -20,14 +20,13 @@
 E_NOINTERFACE = -2147467262
 
 
-class GUID(ctypes.Structure):
[email protected]
+class GUID:
     # https://learn.microsoft.com/en-us/windows/win32/api/guiddef/ns-guiddef-guid
-    _fields_ = [
-        ("Data1", DWORD),
-        ("Data2", WORD),
-        ("Data3", WORD),
-        ("Data4", BYTE * 8),
-    ]
+    Data1: DWORD
+    Data2: WORD
+    Data3: WORD
+    Data4: BYTE * 8
 
 
 def create_proto_com_method(name, index, restype, *argtypes):
diff --git a/Lib/test/test_io/utils.py b/Lib/test/test_io/utils.py
index 3b1faec2140fbc6..dde49337a24f0b9 100644
--- a/Lib/test/test_io/utils.py
+++ b/Lib/test/test_io/utils.py
@@ -8,12 +8,13 @@
 
 
 try:
-    import ctypes
+    import ctypes.util
 except ImportError:
     def byteslike(*pos, **kw):
         return array.array("b", bytes(*pos, **kw))
 else:
-    class EmptyStruct(ctypes.Structure):
+    @ctypes.util.struct
+    class EmptyStruct:
         pass
 
     def byteslike(*pos, **kw):

_______________________________________________
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]
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.