Skip to content

Commit

Permalink
Modify the metaclasses to ensure compatibility with Python 3.13 and e…
Browse files Browse the repository at this point in the history
…arlier versions. (#636)

* Update `python-version`s in the CI pipeline.

* Update `_make_safearray_type`.

* Add early returns with `self`
to `_coclass_meta.__new__`.

* Add early returns with `self`
to `_cominterface_meta.__new__`.
  • Loading branch information
junkmd authored Oct 20, 2024
1 parent 112cd26 commit 5153b3a
Show file tree
Hide file tree
Showing 4 changed files with 40 additions and 5 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/autotest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ jobs:
strategy:
matrix:
os: [windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
architecture: ['x86', 'x64']
npsupport: ['with npsupport', 'without npsupport']
steps:
Expand Down Expand Up @@ -43,7 +43,7 @@ jobs:
strategy:
matrix:
os: [windows-latest, windows-2019]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13']
architecture: ['x86', 'x64']
steps:
- uses: actions/checkout@v4
Expand Down
12 changes: 12 additions & 0 deletions comtypes/_meta.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ def __new__(cls, name, bases, namespace):
if "_reg_clsid_" in namespace:
clsid = namespace["_reg_clsid_"]
comtypes.com_coclass_registry[str(clsid)] = self

# `_coclass_pointer_meta` is a subclass inherited from `_coclass_meta`.
# In other words, when the `__new__` method of this metaclass is called, an
# instance of `_coclass_pointer_meta` might be created and assigned to `self`.
if isinstance(self, _coclass_pointer_meta):
# `self` is the `_coclass_pointer_meta` type or a `POINTER(coclass)` type.
# Prevent creating/registering a pointer to a pointer (to a pointer...),
# or specifying the metaclass type instance in the `bases` parameter when
# instantiating it, which would lead to infinite recursion.
# Depending on a version or revision of Python, this may be essential.
return self

PTR = _coclass_pointer_meta(
f"POINTER({self.__name__})",
(self, c_void_p),
Expand Down
25 changes: 25 additions & 0 deletions comtypes/_post_coinit/unknwn.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,31 @@ def __new__(cls, name, bases, namespace):
if dispmethods is not None:
self._disp_methods_ = dispmethods

# ` _compointer_meta` is a subclass inherited from `_cominterface_meta`.
# `_compointer_base` uses `_compointer_meta` as its metaclass.
# In other words, when the `__new__` method of this metaclass is called,
# `_compointer_base` type or an subclass of it might be created and assigned
# to `self`.
if bases == (c_void_p,):
# `self` is the `_compointer_base` type.
# On some versions of Python, `_compointer_base` is not yet available in
# the accessible namespace at this point in its initialization, and
# referencing it could raise a `NameError`.
# This metaclass is intended to be used only as a base class for defining
# the `_compointer_meta` or as the metaclass for the `IUnknown`, so the
# situation where the `bases` parameter is `(c_void_p,)` is limited to when
# the `_compointer_meta` is specified as the metaclass of the
# `_compointer_base`.
# Prevent specifying the metaclass type instance in the `bases` parameter
# when instantiating it, as this would lead to infinite recursion.
return self
if issubclass(self, _compointer_base):
# `self` is a `POINTER(interface)` type.
# Prevent creating/registering a pointer to a pointer (to a pointer...),
# which would lead to infinite recursion.
# Depending on a version or revision of Python, this may be essential.
return self

# If we sublass a COM interface, for example:
#
# class IDispatch(IUnknown):
Expand Down
4 changes: 1 addition & 3 deletions comtypes/safearray.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,7 @@ def _make_safearray_type(itemtype):
)

meta = type(_safearray.tagSAFEARRAY)
sa_type = meta.__new__(
meta, "SAFEARRAY_%s" % itemtype.__name__, (_safearray.tagSAFEARRAY,), {}
)
sa_type = meta(f"SAFEARRAY_{itemtype.__name__}", (_safearray.tagSAFEARRAY,), {})

try:
vartype = _ctype_to_vartype[itemtype]
Expand Down

0 comments on commit 5153b3a

Please sign in to comment.