From 73669c444b91ab396365350be8faf870abee3396 Mon Sep 17 00:00:00 2001 From: junkmd Date: Mon, 19 Jan 2026 20:32:33 +0900 Subject: [PATCH 1/4] docs: Add note about Python 3.15 `enum` behavior in `README.md`. Explain that `IntFlag` values for negative enum members may be reinterpreted in Python 3.15+, affecting `comtypes` generated enums. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 3fad6ff5..43bd7d96 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,12 @@ `comtypes` allows you to define, call, and implement custom and dispatch-based COM interfaces in pure Python. `comtypes` requires Windows and Python 3.9 or later. + +- **Note about Python 3.15 and `enum` behavior** + Starting with Python 3.15, the internal handling of `IntFlag`(`Flag`) values is planned to change: + **Negative `IntFlag` members will be reinterpreted by masking them to the defined positive bit domain, instead of keeping their original negative literal values**. + This can affect enumeration types generated by `comtypes` from COM type libraries. Action is needed to maintain literal evaluation. + For details and ongoing discussion, see: [GH-894](https://github.com/enthought/comtypes/issues/894). - Version [1.4.12](https://pypi.org/project/comtypes/1.4.12/) is the last version to support Python 3.8. - Version <= [1.4.7](https://pypi.org/project/comtypes/1.4.7/) does not work with Python 3.13 as reported in [GH-618](https://github.com/enthought/comtypes/issues/618). Version [1.4.8](https://pypi.org/project/comtypes/1.4.8/) can work with Python 3.13. - Version [1.4.6](https://pypi.org/project/comtypes/1.4.6/) is the last version to support Python 3.7. From d881a9462d0f6f59b83c922e083856ab3afbff9f Mon Sep 17 00:00:00 2001 From: junkmd Date: Mon, 19 Jan 2026 20:32:33 +0900 Subject: [PATCH 2/4] feat: Add `FutureWarning` for Python 3.15 `enum` behavior. Introduces a `FutureWarning` in `__init__.py` for Python 3.15 and later to notify users about potential changes in `enum` handling, specifically `IntFlag` values. --- comtypes/__init__.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/comtypes/__init__.py b/comtypes/__init__.py index 0a3347c5..a415953a 100644 --- a/comtypes/__init__.py +++ b/comtypes/__init__.py @@ -18,6 +18,22 @@ import logging import sys +if sys.version_info >= (3, 15): + import warnings + + _PYVER = f"{sys.version_info.major}.{sys.version_info.minor}" + warnings.warn( + ( + f"You are running 'comtypes' on Python {_PYVER}, where the behavior of " + "enum types (such as IntFlag) may differ from Python <= 3.14.\n" + f"It is recommended to use a version compatible with Python {_PYVER}.\n" + "See: https://github.com/enthought/comtypes/issues/894" + ), + FutureWarning, + stacklevel=2, + ) + + # HACK: Workaround for projects that depend on this package # There should be several projects around the world that depend on this package # and indirectly reference the symbols of `ctypes` from `comtypes`. From 94d71e102f13f9c8647ee0699e978aaabddfc260 Mon Sep 17 00:00:00 2001 From: junkmd Date: Mon, 19 Jan 2026 20:32:33 +0900 Subject: [PATCH 3/4] test: Improve Python 3.15 `enum` test clarity. Conditionally skip `test_enums_in_friendly_mod` on Python 3.15 alpha/beta versions in `test_client.py` to prevent failures due to potential `IntFlag` changes. This change includes adding `subTest` messages for better debugging. Additionally, refactor enum member access in `test_puredispatch.py` from `msi.MsiInstallState.msiInstallStateUnknown` to `msi.msiInstallStateUnknown` for improved clarity and consistency. --- comtypes/test/test_client.py | 19 ++++++++++++++++++- comtypes/test/test_puredispatch.py | 4 ++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/comtypes/test/test_client.py b/comtypes/test/test_client.py index 991456ba..3a75252c 100644 --- a/comtypes/test/test_client.py +++ b/comtypes/test/test_client.py @@ -269,6 +269,18 @@ def test_progid(self): self.assertEqual(consts.TextCompare, Scripting.TextCompare) self.assertEqual(consts.DatabaseCompare, Scripting.DatabaseCompare) + PY_3_15_ALPHA_BETA = ( + sys.version_info.major == 3 + and sys.version_info.minor == 15 + and sys.version_info.releaselevel in ("alpha", "beta") + ) + ENUMS_MESSAGE = ( + "Starting from Python 3.15, negative members in `IntFlag` may " + "no longer be evaluated as literals.\nWe need to address this before " + "the release. See: https://github.com/enthought/comtypes/issues/894" + ) + + @ut.skipIf(PY_3_15_ALPHA_BETA, ENUMS_MESSAGE) def test_enums_in_friendly_mod(self): comtypes.client.GetModule("scrrun.dll") comtypes.client.GetModule("msi.dll") @@ -287,11 +299,16 @@ def test_enums_in_friendly_mod(self): ), ]: for member in enumtype: - with self.subTest(enumtype=enumtype, member=member): + with self.subTest( + msg=self.ENUMS_MESSAGE, + enumtype=enumtype, + member=member, + ): self.assertIn(member.name, fadic) self.assertEqual(fadic[member.name], member.value) for member_name, member_value in fadic.items(): with self.subTest( + msg=self.ENUMS_MESSAGE, enumtype=enumtype, member_name=member_name, member_value=member_value, diff --git a/comtypes/test/test_puredispatch.py b/comtypes/test/test_puredispatch.py index 62c3f51e..c4550cb6 100644 --- a/comtypes/test/test_puredispatch.py +++ b/comtypes/test/test_puredispatch.py @@ -83,13 +83,13 @@ def test_product_state(self): ) # There is no product associated with the Null GUID. pdcode = str(GUID()) - expected = msi.MsiInstallState.msiInstallStateUnknown + expected = msi.msiInstallStateUnknown self.assertEqual(expected, inst.ProductState(pdcode)) self.assertEqual(expected, inst.ProductState[pdcode]) # The `ProductState` property is a read-only property. # https://learn.microsoft.com/en-us/windows/win32/msi/installer-productstate-property with self.assertRaises(TypeError): - inst.ProductState[pdcode] = msi.MsiInstallState.msiInstallStateDefault # type: ignore + inst.ProductState[pdcode] = msi.msiInstallStateDefault # type: ignore # NOTE: Named parameters are not yet implemented for the named property. # See https://github.com/enthought/comtypes/issues/371 # TODO: After named parameters are supported, this will become a test to From 82b8d5492f41010a14c88d39f044f5241c67d7e3 Mon Sep 17 00:00:00 2001 From: junkmd Date: Mon, 19 Jan 2026 20:32:33 +0900 Subject: [PATCH 4/4] test: Prevent memory errors in `from_outparam` test. The test now uses `create_unicode_buffer` to safely allocate a valid memory block outside of the COM allocator's control. This ensures the test correctly validates the behavior of `from_outparam` on unmanaged memory without causing access violations. --- comtypes/test/test_outparam.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/comtypes/test/test_outparam.py b/comtypes/test/test_outparam.py index 1650427d..dc7de6c5 100644 --- a/comtypes/test/test_outparam.py +++ b/comtypes/test/test_outparam.py @@ -1,6 +1,14 @@ import logging import unittest -from ctypes import c_wchar, c_wchar_p, cast, memmove, sizeof, wstring_at +from ctypes import ( + c_wchar, + c_wchar_p, + cast, + create_unicode_buffer, + memmove, + sizeof, + wstring_at, +) from unittest.mock import patch from comtypes.malloc import CoGetMalloc, _CoTaskMemAlloc, _CoTaskMemFree @@ -38,10 +46,11 @@ def comstring(text, typ=c_wchar_p): class Test(unittest.TestCase): @patch.object(c_wchar_p, "__ctypes_from_outparam__", from_outparam) def test_c_char(self): - ptr = c_wchar_p("abc") - # The normal constructor does not allocate memory using `CoTaskMemAlloc`. - # Therefore, calling the patched `ptr.__ctypes_from_outparam__()` would - # attempt to free invalid memory, potentially leading to a crash. + # Allocate memory from the Python/C runtime heap. + # This ensures the address is valid but "unallocated" from COM. + buf = create_unicode_buffer("abc") + ptr = cast(buf, c_wchar_p) + # Confirm the memory is not managed by the COM task allocator. self.assertEqual(malloc.DidAlloc(ptr), 0) x = comstring("Hello, World")