Skip to content

Add tests to prevent regressions with the combination of ctypes and metaclasses. #125783

Description

@junkmd

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?importunittestimportctypesfromctypesimportPOINTER, c_void_pfrom ._supportimportPyCSimpleType, _CData, _SimpleCDataclassPyCSimpleTypeAsMetaclassTest(unittest.TestCase):
deftearDown(self):
# to not leak references, we must clean _pointer_type_cachectypes._reset_cache()
deftest_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)
ifbases== (c_void_p,):
returnselfifissubclass(self, _PtrBase):
returnselfifbases== (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] =preturnselfclass_p_meta(PyCSimpleType, _ct_meta):
passclass_PtrBase(c_void_p, metaclass=_p_meta):
passclass_CtBase(object, metaclass=_ct_meta):
passclass_Sub(_CtBase):
passclass_Sub2(_Sub):
passdeftest_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)
ifisinstance(self, _p_meta):
returnselfp=_p_meta(
f"POINTER({self.__name__})", (self, c_void_p), {}
)
ctypes._pointer_type_cache[self] =preturnselfclass_p_meta(PyCSimpleType, _ct_meta):
passclass_Base(object):
passclass_Sub(_Base, metaclass=_ct_meta):
passclass_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:

#124520

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions