https://github.com/python/cpython/commit/b3ddde433e69166ecdf40095349f69e08997e9cb
commit: b3ddde433e69166ecdf40095349f69e08997e9cb
branch: main
author: Vyron Vasileiadis <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-08-30T10:47:53-04:00
summary:
gh-156500: Reject non-positional parameters in `ctypes.util.wrap_dll_function`
(GH-156501)
argtypes was built from every annotated parameter, so an annotated
keyword-only, *args, or **kwargs parameter contributed an extra positional
entry. Such a parameter has no positional counterpart to describe, so it
now raises ValueError at decoration time.
files:
M Doc/library/ctypes.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 acd6c2f97348e19..d76c449626fab4e 100644
--- a/Doc/library/ctypes.rst
+++ b/Doc/library/ctypes.rst
@@ -715,8 +715,11 @@ Specifying function pointers using type annotations
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.
+ :exc:`ValueError` is raised. A :exc:`ValueError` is also raised if it has a
+ keyword-only, ``*args``, or ``**kwargs`` parameter, since
+ :attr:`~ctypes._CFuncPtr.argtypes` describes positional arguments only. If
+ the name of the function does not exist in *dll*, an :exc:`AttributeError`
+ is raised.
For example::
diff --git a/Lib/ctypes/util.py b/Lib/ctypes/util.py
index 141d3fbe9382472..2b01506f3d4ffab 100644
--- a/Lib/ctypes/util.py
+++ b/Lib/ctypes/util.py
@@ -4,6 +4,7 @@
from dataclasses import dataclass
lazy import functools
+lazy import inspect
lazy import shutil
lazy import subprocess
@@ -509,6 +510,13 @@ def decorator(func):
except KeyError as error:
raise ValueError(f"{name!r} missing return type annotation") from
error
+ for param in inspect.signature(func).parameters.values():
+ if param.kind not in (param.POSITIONAL_ONLY,
+ param.POSITIONAL_OR_KEYWORD):
+ raise ValueError(f"{name!r} has non-positional parameter "
+ f"{param.name!r}; argtypes describes "
+ f"positional arguments only")
+
ptr.restype = restype
ptr.argtypes = tuple(annotations.values())
functools.update_wrapper(ptr, func, updated=())
diff --git a/Lib/test/test_ctypes/test_funcptr.py
b/Lib/test/test_ctypes/test_funcptr.py
index 28ff34f9048454d..b4c27809b5333d5 100644
--- a/Lib/test/test_ctypes/test_funcptr.py
+++ b/Lib/test/test_ctypes/test_funcptr.py
@@ -153,6 +153,47 @@ def noexist():
def PyObject_GetAttrString(op: ctypes.py_object, attr:
ctypes.c_char_p):
pass
+ def test_wrap_dll_function_non_positional(self):
+ # argtypes describes positional arguments only, so a parameter that
+ # cannot be passed positionally is rejected.
+ regex = "'PyObject_GetAttr' has non-positional parameter"
+
+ with self.assertRaisesRegex(ValueError, regex):
+ @wrap_dll_function(ctypes.pythonapi)
+ def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+ *args: ctypes.c_int) -> ctypes.py_object:
+ pass
+
+ with self.assertRaisesRegex(ValueError, regex):
+ @wrap_dll_function(ctypes.pythonapi)
+ def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+ **kwargs: ctypes.c_int) -> ctypes.py_object:
+ pass
+
+ with self.assertRaisesRegex(ValueError, regex):
+ @wrap_dll_function(ctypes.pythonapi)
+ def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+ *, kwonly: ctypes.c_int) -> ctypes.py_object:
+ pass
+
+ with self.assertRaisesRegex(ValueError, regex):
+ @wrap_dll_function(ctypes.pythonapi)
+ def PyObject_GetAttr(op: ctypes.py_object, attr: ctypes.py_object,
+ *, kwonly) -> ctypes.py_object:
+ pass
+
+ # Positional-only parameters have a positional counterpart, so they
+ # are accepted.
+ @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")
+
def test_wrap_dll_function_str_ann(self):
from test.test_ctypes import wrap_str_ann
version = wrap_str_ann.Py_GetVersion()
_______________________________________________
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]