Skip to content

Commit 7008819

Browse files
authored
Introduce warnings for Python 3.15 enum compatibility and improve test stability. (#907)
* 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. * 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. * 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. * 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.
1 parent 1fc72e0 commit 7008819

File tree

5 files changed

+56
-8
lines changed

5 files changed

+56
-8
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
`comtypes` allows you to define, call, and implement custom and dispatch-based COM interfaces in pure Python.
1313

1414
`comtypes` requires Windows and Python 3.9 or later.
15+
16+
- **Note about Python 3.15 and `enum` behavior**
17+
Starting with Python 3.15, the internal handling of `IntFlag`(`Flag`) values is planned to change:
18+
**Negative `IntFlag` members will be reinterpreted by masking them to the defined positive bit domain, instead of keeping their original negative literal values**.
19+
This can affect enumeration types generated by `comtypes` from COM type libraries. Action is needed to maintain literal evaluation.
20+
For details and ongoing discussion, see: [GH-894](https://github.com/enthought/comtypes/issues/894).
1521
- Version [1.4.12](https://pypi.org/project/comtypes/1.4.12/) is the last version to support Python 3.8.
1622
- 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.
1723
- Version [1.4.6](https://pypi.org/project/comtypes/1.4.6/) is the last version to support Python 3.7.

comtypes/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@
1818
import logging
1919
import sys
2020

21+
if sys.version_info >= (3, 15):
22+
import warnings
23+
24+
_PYVER = f"{sys.version_info.major}.{sys.version_info.minor}"
25+
warnings.warn(
26+
(
27+
f"You are running 'comtypes' on Python {_PYVER}, where the behavior of "
28+
"enum types (such as IntFlag) may differ from Python <= 3.14.\n"
29+
f"It is recommended to use a version compatible with Python {_PYVER}.\n"
30+
"See: https://github.com/enthought/comtypes/issues/894"
31+
),
32+
FutureWarning,
33+
stacklevel=2,
34+
)
35+
36+
2137
# HACK: Workaround for projects that depend on this package
2238
# There should be several projects around the world that depend on this package
2339
# and indirectly reference the symbols of `ctypes` from `comtypes`.

comtypes/test/test_client.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,18 @@ def test_progid(self):
269269
self.assertEqual(consts.TextCompare, Scripting.TextCompare)
270270
self.assertEqual(consts.DatabaseCompare, Scripting.DatabaseCompare)
271271

272+
PY_3_15_ALPHA_BETA = (
273+
sys.version_info.major == 3
274+
and sys.version_info.minor == 15
275+
and sys.version_info.releaselevel in ("alpha", "beta")
276+
)
277+
ENUMS_MESSAGE = (
278+
"Starting from Python 3.15, negative members in `IntFlag` may "
279+
"no longer be evaluated as literals.\nWe need to address this before "
280+
"the release. See: https://github.com/enthought/comtypes/issues/894"
281+
)
282+
283+
@ut.skipIf(PY_3_15_ALPHA_BETA, ENUMS_MESSAGE)
272284
def test_enums_in_friendly_mod(self):
273285
comtypes.client.GetModule("scrrun.dll")
274286
comtypes.client.GetModule("msi.dll")
@@ -287,11 +299,16 @@ def test_enums_in_friendly_mod(self):
287299
),
288300
]:
289301
for member in enumtype:
290-
with self.subTest(enumtype=enumtype, member=member):
302+
with self.subTest(
303+
msg=self.ENUMS_MESSAGE,
304+
enumtype=enumtype,
305+
member=member,
306+
):
291307
self.assertIn(member.name, fadic)
292308
self.assertEqual(fadic[member.name], member.value)
293309
for member_name, member_value in fadic.items():
294310
with self.subTest(
311+
msg=self.ENUMS_MESSAGE,
295312
enumtype=enumtype,
296313
member_name=member_name,
297314
member_value=member_value,

comtypes/test/test_outparam.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
import logging
22
import unittest
3-
from ctypes import c_wchar, c_wchar_p, cast, memmove, sizeof, wstring_at
3+
from ctypes import (
4+
c_wchar,
5+
c_wchar_p,
6+
cast,
7+
create_unicode_buffer,
8+
memmove,
9+
sizeof,
10+
wstring_at,
11+
)
412
from unittest.mock import patch
513

614
from comtypes.malloc import CoGetMalloc, _CoTaskMemAlloc, _CoTaskMemFree
@@ -38,10 +46,11 @@ def comstring(text, typ=c_wchar_p):
3846
class Test(unittest.TestCase):
3947
@patch.object(c_wchar_p, "__ctypes_from_outparam__", from_outparam)
4048
def test_c_char(self):
41-
ptr = c_wchar_p("abc")
42-
# The normal constructor does not allocate memory using `CoTaskMemAlloc`.
43-
# Therefore, calling the patched `ptr.__ctypes_from_outparam__()` would
44-
# attempt to free invalid memory, potentially leading to a crash.
49+
# Allocate memory from the Python/C runtime heap.
50+
# This ensures the address is valid but "unallocated" from COM.
51+
buf = create_unicode_buffer("abc")
52+
ptr = cast(buf, c_wchar_p)
53+
# Confirm the memory is not managed by the COM task allocator.
4554
self.assertEqual(malloc.DidAlloc(ptr), 0)
4655

4756
x = comstring("Hello, World")

comtypes/test/test_puredispatch.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,13 +83,13 @@ def test_product_state(self):
8383
)
8484
# There is no product associated with the Null GUID.
8585
pdcode = str(GUID())
86-
expected = msi.MsiInstallState.msiInstallStateUnknown
86+
expected = msi.msiInstallStateUnknown
8787
self.assertEqual(expected, inst.ProductState(pdcode))
8888
self.assertEqual(expected, inst.ProductState[pdcode])
8989
# The `ProductState` property is a read-only property.
9090
# https://learn.microsoft.com/en-us/windows/win32/msi/installer-productstate-property
9191
with self.assertRaises(TypeError):
92-
inst.ProductState[pdcode] = msi.MsiInstallState.msiInstallStateDefault # type: ignore
92+
inst.ProductState[pdcode] = msi.msiInstallStateDefault # type: ignore
9393
# NOTE: Named parameters are not yet implemented for the named property.
9494
# See https://github.com/enthought/comtypes/issues/371
9595
# TODO: After named parameters are supported, this will become a test to

0 commit comments

Comments
 (0)