https://github.com/python/cpython/commit/0e81ef93ebd87607c8d946e7dfb6940c5fe9f083
commit: 0e81ef93ebd87607c8d946e7dfb6940c5fe9f083
branch: main
author: Peter Bierma <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-07-18T08:30:50-04:00
summary:

gh-153903: Add a `ctypes` decorator for generating function pointers using 
annotations (GH-153904)

Co-authored-by: Tomas R. <[email protected]>
Co-authored-by: Bartosz SÅ‚awecki <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-07-18-06-16-55.gh-issue-153903.mJFrs8.rst
M Doc/library/ctypes.rst
M Doc/whatsnew/3.16.rst
M Lib/ctypes/util.py
M Lib/test/test_ctypes/test_funcptr.py

diff --git a/Doc/library/ctypes.rst b/Doc/library/ctypes.rst
index 80868a8b2418f35..34c8636abaa925d 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -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)
diff --git a/Doc/whatsnew/3.16.rst b/Doc/whatsnew/3.16.rst
index 5cb1a86dac46233..e6565b186a598c5 100644
--- a/Doc/whatsnew/3.16.rst
+++ b/Doc/whatsnew/3.16.rst
@@ -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
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 51c0d441bc8df0e..b035bfb99feee0a 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -574,6 +574,24 @@ def inner(decorated_class):
 
     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
 
diff --git a/Lib/test/test_ctypes/test_funcptr.py 
b/Lib/test/test_ctypes/test_funcptr.py
index be641da30eadae1..94d03ad553d2227 100644
--- a/Lib/test/test_ctypes/test_funcptr.py
+++ b/Lib/test/test_ctypes/test_funcptr.py
@@ -2,6 +2,7 @@
 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 @@ def c_string(init):
     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
index 000000000000000..00f89b471175f45
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-07-18-06-16-55.gh-issue-153903.mJFrs8.rst
@@ -0,0 +1,2 @@
+Add :func:`ctypes.util.wrap_dll_function` for creating
+:class:`~ctypes._CFuncPtr` objects from a function signature.

_______________________________________________
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]

Reply via email to