Adapting to Python 3.15+ `IntFlag` changes for negative members.
@junkmd is already working on this.
Since Dec 30, 2025.
Assessment
This issue has not been assessed yet.
Description
To the community,
In this project, IntFlag subclasses have been generated within friendly modules based on enumeration definitions retrieved from COM type libraries (see https://github.com/enthought/comtypes/issues/345).
Among these, some enums — such as MsiInstallState defined in the Microsoft Windows Installer Object Library — contain a mix of positive and negative values.
While these definitions worked as expected up to Python 3.14, the internal implementation of IntFlag is changing in Python 3.15.0-alpha, meaning they will no longer be evaluated as they were previously.
Background
The root of this issue lies in the inconsistency of handling negative values within a flag (IntFlag).
As discussed in https://github.com/python/cpython/issues/107538 and https://github.com/python/cpython/pull/132273, Flag is intended to represent a "set of bits". Including negative values as members creates a logical contradiction.
I was the one who originally proposed and implemented the enum definitions in this project, and I should have recognized the irrationality of treating negative values as flags at that time.
I apologize for this oversight.
Problem reproduction (minimal reproducer)
The following is a minimal reproducer using a snippet of the MsiInstallState definition generated by import comtypes.client; comtypes.client.GetModule('msi.dll'), along with a basic value check and test.
In Python 3.15.0-alpha and later, the evaluation of IntFlag is being changed, and negative members will no longer be evaluated as their literal values (e.g., -1).
from enum import IntFlag
class MsiInstallState(IntFlag):
msiInstallStateNotUsed = -7
msiInstallStateBadConfig = -6
msiInstallStateIncomplete = -5
msiInstallStateSourceAbsent = -4
msiInstallStateInvalidArg = -2
msiInstallStateUnknown = -1
msiInstallStateBroken = 0
msiInstallStateAdvertised = 1
msiInstallStateRemoved = 1
msiInstallStateAbsent = 2
msiInstallStateLocal = 3
msiInstallStateSource = 4
msiInstallStateDefault = 5
if __name__ == "__main__":
print((repr(MsiInstallState.msiInstallStateUnknown), MsiInstallState.msiInstallStateUnknown.value))
assert MsiInstallState.msiInstallStateUnknown == -1
Execution result in Python 3.14:
('<MsiInstallState.msiInstallStateUnknown: -1>', -1)
Execution result in Python 3.15:
('<MsiInstallState.msiInstallStateUnknown: 7>', 7)
Traceback (most recent call last):
File "...\enum_testing.py", line 22, in <module>
assert MsiInstallState.msiInstallStateUnknown == -1
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError
Why does the value become 7?
In Python 3.15+, IntFlag treats only the bit range covered by the defined positive members as the "valid bit domain".
Negative values are then reinterpreted as "all bits ON (masked)" within that specific range.
-
Identifying the Valid Bit Range:
The maximum positive value inMsiInstallStateis5(101in binary).
Consequently,IntFlagdetermines the valid bit width for this enum to be the lower 3 bits ($2^0, 2^1, 2^2$). -
Masking the Negative Value:
A negative value like-1has all bits set to1in its two's complement representation.
However, Python 3.15IntFlagnormalizes this to a state where "all bits within the valid 3-bit range are1." -
Calculation Result:
With the lower 3 bits all set to1, the value becomes ($2^2 + 2^1 + 2^0 = 7$), losing consistency with the literal-1.
Future direction
To ensure this project continues to function as intended in Python 3.15 and beyond, some form of adaptation is necessary.
At this stage, I want community feedback.
Directions under consideration:
-
Direction 1: modification of code generation logic in
comtypes- Adjust the generation method within the library to maintain value consistency even in Python 3.15+.
-
Direction 2: proposing changes to CPython regarding the handling of negative values
- Demonstrate to the CPython community and the core developers that there is a demand for treating negative values as flags and advocate for improvements or maintenance of the Python<=3.14 behavior.
Request to the community and discussion rules
Your feedback is vital.
This issue is something that all Python developers dealing with enums containing negative values may face — regardless of whether they use comtypes, or whether they define enums via dynamic generation or static hard-coding.
To ensure the discussion proceeds smoothly, please adhere to the following:
- If you have a specific proposal, please comment on this issue first.
- Please do not create a pull request before discussing it here. If discussions happen separately on PRs, it becomes difficult to track the decision-making process and hurts traceability.
If Python 3.15 reaches the release candidate (RC) stage and negative members are still not evaluated as literals, I intend to consider merging the only available proposal or the one deemed most rational at that time.
I look forward to a sincere discussion to find the best possible solution.
Thank you for your input.
- Dominant language
- Python
- Stars
- 346
- Forks
- 105
- PR merge metrics
- No merged PRs in 30d
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from enthought/comtypes
-
good first issue
Difficulty 2/5 1-3 hours Newbie friendliness 62/100
-
enhancement
Difficulty 3/5 1-2 days Newbie friendliness 78/100
-
question
Difficulty 3/5 1-2 days Newbie friendliness 58/100
-
ci tests
Difficulty 3/5 1-2 days Newbie friendliness 62/100
-
bug tests
Difficulty 4/5 3-5 days Newbie friendliness 35/100
All issues in enthought/comtypes
Similar issues
-
sponsored
Difficulty 2/5 1-3 hours Newbie friendliness 65/100
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
Diaoul/subliminal#1382 ·
-
Difficulty 1/5 Under an hour Newbie friendliness 92/100
-
triage/confirmed
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
agentscope-ai/agentscope#2775 ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 84/100