-
-
Notifications
You must be signed in to change notification settings - Fork 33.7k
Closed
Closed
Copy link
Labels
testsTests in the Lib/test dirTests in the Lib/test dirtopic-ctypestype-featureA feature request or enhancementA feature request or enhancement
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):
passIf 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
ctypesand metaclasses. #125881 - [3.13] gh-125783: Add tests to prevent regressions with the combination of
ctypesand metaclasses. (GH-125881) #125987 - [3.12] gh-125783: Add tests to prevent regressions with the combination of
ctypesand 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
Metadata
Metadata
Assignees
Labels
testsTests in the Lib/test dirTests in the Lib/test dirtopic-ctypestype-featureA feature request or enhancementA feature request or enhancement