]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-153903: Add a `ctypes` decorator for generating function pointers using annotation...
authorPeter Bierma <zintensitydev@gmail.com>
Sat, 18 Jul 2026 12:30:50 +0000 (08:30 -0400)
committerGitHub <noreply@github.com>
Sat, 18 Jul 2026 12:30:50 +0000 (08:30 -0400)
Co-authored-by: Tomas R. <tomas.roun8@gmail.com>
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
Doc/library/ctypes.rst
Doc/whatsnew/3.16.rst
Lib/ctypes/util.py
Lib/test/test_ctypes/test_funcptr.py
Misc/NEWS.d/next/Library/2026-07-18-06-16-55.gh-issue-153903.mJFrs8.rst [new file with mode: 0644]

index 80868a8b2418f352f62aa116676a2daa55be0310..34c8636abaa925d36a241f41dbb343dbb072d3cf 100644 (file)
@@ -694,6 +694,46 @@ through the :attr:`~_CFuncPtr.errcheck` attribute;
 see the reference manual for details.
 
 
+Specifying function pointers using type annotations
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+.. decorator:: wrap_dll_function(dll)
+   :module: ctypes.util
+
+   A :term:`decorator` that generates :attr:`~ctypes._CFuncPtr.argtypes` and
+   :attr:`~ctypes._CFuncPtr.restype` from a function signature, using the
+   :attr:`~function.__name__` of the function and its :term:`type annotations <annotation>`.
+
+   The decorated function should look like this::
+
+      @wrap_dll_function(dll_to_wrap)
+      def function_ptr_name(arg_name: ctypes_type, ...) -> ctypes_type:
+         # There should be no body
+         pass
+
+   The body of the decorated function is ignored, and any parameters that are
+   missing type annotations are skipped. The names of the parameters are ignored
+   and do not have to match the underlying C implementation.
+
+   If the decorated function does not have a return type annotation, a
+   :exc:`ValueError` is raised. If the name of the function does not exist
+   in *dll*, an :exc:`AttributeError` is raised.
+
+   For example::
+
+      import ctypes
+      from ctypes.util import wrap_dll_function
+
+      @wrap_dll_function(ctypes.pythonapi)
+      def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p) -> ctypes.py_object:
+         pass
+
+      PyObject_GetAttrString(42, b"real")
+
+
+   .. versionadded:: next
+
+
 .. _ctypes-passing-pointers:
 
 Passing pointers (or: passing parameters by reference)
index 5cb1a86dac4623393783e45714ecc5517b7c8993..e6565b186a598c5292e3ccfbaf0ff21e270131b7 100644 (file)
@@ -237,6 +237,9 @@ ctypes
   from an annotation-based syntax, similar to how the :mod:`dataclasses` module
   is used.
   (Contributed by Peter Bierma in :gh:`104533`.)
+* Add :func:`ctypes.util.wrap_dll_function` for generating function pointers
+  through a function signature.
+  (Contributed by Peter Bierma in :gh:`153903`.)
 
 
 encodings
index 51c0d441bc8df0ee7ea03f7891544b7b68a7d1a8..b035bfb99feee0aaf43c695d4b901119d8ffe133 100644 (file)
@@ -574,6 +574,24 @@ def struct(class_or_none=None, /, *, align=None, layout=None, endian='native', p
 
     return process_the_struct(class_or_none)
 
+
+def wrap_dll_function(dll):
+    def decorator(func):
+        name = func.__name__
+        ptr = getattr(dll, name)
+        annotations = annotationlib.get_annotations(func)
+
+        try:
+            restype = annotations.pop("return")
+        except KeyError as error:
+            raise ValueError(f"{name!r} missing return type annotation") from error
+
+        ptr.restype = restype
+        ptr.argtypes = tuple(annotations.values())
+        return ptr
+
+    return decorator
+
 ################################################################
 # test code
 
index be641da30eadae149d1edb9218a7024617a0122c..94d03ad553d2227312f6f27f3886c651b6c98904 100644 (file)
@@ -2,6 +2,7 @@ import ctypes
 import unittest
 from ctypes import (CDLL, Structure, CFUNCTYPE, sizeof, _CFuncPtr,
                     c_void_p, c_char_p, c_char, c_int, c_uint, c_long)
+from ctypes.util import wrap_dll_function
 from test.support import import_helper
 _ctypes_test = import_helper.import_module("_ctypes_test")
 from ._support import (_CData, PyCFuncPtrType, Py_TPFLAGS_DISALLOW_INSTANTIATION,
@@ -130,6 +131,26 @@ class CFuncPtrTestCase(unittest.TestCase, StructCheckMixin):
     def test_abstract(self):
         self.assertRaises(TypeError, _CFuncPtr, 13, "name", 42, "iid")
 
+    def test_wrap_dll_function(self):
+        @wrap_dll_function(ctypes.pythonapi)
+        def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object) -> ctypes.py_object:
+            pass
+
+        class Foo:
+            a = "abc"
+
+        self.assertEqual(PyObject_GetAttr(Foo, "a"), "abc")
+
+        with self.assertRaises(AttributeError):
+            @wrap_dll_function(ctypes.pythonapi)
+            def noexist():
+                pass
+
+        with self.assertRaisesRegex(ValueError, "'PyObject_GetAttrString' missing return type annotation"):
+            @wrap_dll_function(ctypes.pythonapi)
+            def PyObject_GetAttrString(op: ctypes.py_object, attr: ctypes.c_char_p):
+                pass
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2026-07-18-06-16-55.gh-issue-153903.mJFrs8.rst b/Misc/NEWS.d/next/Library/2026-07-18-06-16-55.gh-issue-153903.mJFrs8.rst
new file mode 100644 (file)
index 0000000..00f89b4
--- /dev/null
@@ -0,0 +1,2 @@
+Add :func:`ctypes.util.wrap_dll_function` for creating
+:class:`~ctypes._CFuncPtr` objects from a function signature.