Closed
Description
Feature or enhancement
Proposal:
There were breaking changes to ctypes
in Python 3.13.
Projects like comtypes
and pyglet
, which implement functionality by combining ctypes
and metaclasses, no longer work unless their codebases are updated.
We discussed what changes such projects need to make to their codebases in gh-124520.
I believe the easiest and most effective way to prevent regressions from happening again is to add tests on the cpython
side.
I wrote a simple test like the one below.
# Lib/test/test_ctypes/test_c_simple_type_meta.py?
import unittest
import ctypes
from ctypes import POINTER, c_void_p
from ._support import PyCSimpleType, _CData, _SimpleCData
class PyCSimpleTypeAsMetaclassTest(unittest.TestCase):
def tearDown(self):
# to not leak references, we must clean _pointer_type_cache
ctypes._reset_cache()
def test_early_return_in_dunder_new_1(self):
# this implementation is used in `IUnknown` of comtypes.
class _ct_meta(type):
def __new__(cls, name, bases, namespace):
self = super().__new__(cls, name, bases, namespace)
if bases == (c_void_p,):
return self
if issubclass(self, _PtrBase):
return self
if bases == (object,):
_ptr_bases = (self, _PtrBase)
else:
_ptr_bases = (self, POINTER(bases[0]))
p = _p_meta(f"POINTER({self.__name__})", _ptr_bases, {})
ctypes._pointer_type_cache[self] = p
return self
class _p_meta(PyCSimpleType, _ct_meta):
pass
class _PtrBase(c_void_p, metaclass=_p_meta):
pass
class _CtBase(object, metaclass=_ct_meta):
pass
class _Sub(_CtBase):
pass
class _Sub2(_Sub):
pass
def test_early_return_in_dunder_new_2(self):
# this implementation is used in `CoClass` of comtypes.
class _ct_meta(type):
def __new__(cls, name, bases, namespace):
self = super().__new__(cls, name, bases, namespace)
if isinstance(self, _p_meta):
return self
p = _p_meta(
f"POINTER({self.__name__})", (self, c_void_p), {}
)
ctypes._pointer_type_cache[self] = p
return self
class _p_meta(PyCSimpleType, _ct_meta):
pass
class _Base(object):
pass
class _Sub(_Base, metaclass=_ct_meta):
pass
class _Sub2(_Sub):
pass
If no errors occur when this test is executed, we can assume that no regressions have occurred with the combination of ctypes
and metaclasses.
However, I feel that this may not be enough. What should we specify as the target for the assert
?
Has this already been discussed elsewhere?
No response given
Links to previous discussion of this feature:
Linked PRs
- gh-125783: Add tests to prevent regressions with the combination of
ctypes
and metaclasses. #125881 - [3.13] gh-125783: Add tests to prevent regressions with the combination of
ctypes
and metaclasses. (GH-125881) #125987 - [3.12] gh-125783: Add tests to prevent regressions with the combination of
ctypes
and metaclasses. (GH-125881) #125988 - gh-125783: Add more tests to prevent regressions with the combination of ctypes and metaclasses. #126126
- [3.13] gh-125783: Add more tests to prevent regressions with the combination of ctypes and metaclasses. (GH-126126) #126275
- [3.12] gh-125783: Add more tests to prevent regressions with the combination of ctypes and metaclasses. (GH-126126) #126276