From de79219836db30b0e77a3e12b63d3dcc23a374f2 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sun, 4 Oct 2026 12:23:14 +0900 Subject: [PATCH 1/5] feat: Improve type hints for `IEnumVARIANT`: - Add `TYPE_CHECKING` block with method signatures for `Skip`, `Reset`, and `Clone`. - Provide overloads for `Next` method to return a tuple for a single element and a sequence for multiple elements. - Add precise return type annotations for `__iter__`, `__next__`, and `__getitem__`. These changes enhance static analysis, IDE support, and documentation clarity for the COM enumeration interface. --- comtypes/automation.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/comtypes/automation.py b/comtypes/automation.py index 158cc6a6..2cf3b6f7 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -3,11 +3,12 @@ import datetime import decimal from _ctypes import COMError, CopyComPointer +from collections.abc import Sequence from ctypes import * from ctypes import Array as _CArrayType from ctypes import _Pointer from ctypes.wintypes import DWORD, LONG, UINT, VARIANT_BOOL, WCHAR, WORD -from typing import TYPE_CHECKING, Any, ClassVar, Optional +from typing import TYPE_CHECKING, Any, ClassVar, Literal, Optional, overload import comtypes import comtypes.patcher @@ -641,16 +642,22 @@ class IEnumVARIANT(IUnknown): _idlflags_ = ["hidden"] _dynamic = False - def __iter__(self): + if TYPE_CHECKING: + + def Skip(self, cConnections: int) -> hints.Hresult: ... + def Reset(self) -> hints.Hresult: ... + def Clone(self) -> hints.Self: ... + + def __iter__(self) -> "hints.Self": return self - def __next__(self): + def __next__(self) -> Any: item, fetched = self.Next(1) if fetched: return item raise StopIteration - def __getitem__(self, index): + def __getitem__(self, index: int) -> Any: self.Reset() # Does not yet work. # if isinstance(index, slice): @@ -662,6 +669,10 @@ def __getitem__(self, index): return item raise IndexError + @overload + def Next(self, celt: Literal[1]) -> tuple[Any, int]: ... + @overload + def Next(self, celt: int) -> Sequence[Any]: ... def Next(self, celt): fetched = c_ulong() if celt == 1: From ecfbc07098857a60dc25326a20330a750219ebc5 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sun, 4 Oct 2026 12:23:14 +0900 Subject: [PATCH 2/5] feat: Add `# type: ignore` comments to `IEnumVARIANT.Next` implementation to silence type checking errors. --- comtypes/automation.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/comtypes/automation.py b/comtypes/automation.py index 2cf3b6f7..e4d7b025 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -673,14 +673,14 @@ def __getitem__(self, index: int) -> Any: def Next(self, celt: Literal[1]) -> tuple[Any, int]: ... @overload def Next(self, celt: int) -> Sequence[Any]: ... - def Next(self, celt): + def Next(self, celt): # type: ignore fetched = c_ulong() if celt == 1: v = VARIANT() - self.__com_Next(celt, v, fetched) + self.__com_Next(celt, v, fetched) # type: ignore return v._get_value(dynamic=self._dynamic), fetched.value array = (VARIANT * celt)() - self.__com_Next(celt, array, fetched) + self.__com_Next(celt, array, fetched) # type: ignore result = [v._get_value(dynamic=self._dynamic) for v in array[: fetched.value]] for v in array: v.value = None From 83d38ebb5acce27d1da10d6ced5e2281d6da0ff6 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sun, 4 Oct 2026 12:23:14 +0900 Subject: [PATCH 3/5] docs: Add comprehensive docstring to `IEnumVARIANT.Next`. --- comtypes/automation.py | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/comtypes/automation.py b/comtypes/automation.py index e4d7b025..51ccb23d 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -674,6 +674,36 @@ def Next(self, celt: Literal[1]) -> tuple[Any, int]: ... @overload def Next(self, celt: int) -> Sequence[Any]: ... def Next(self, celt): # type: ignore + """Retrieve the next *celt* items from the enumeration. + + This method behaves differently depending on the value of *celt*: + + - `celt == 1`: + A single `VARIANT` is fetched via one COM call. The return + value is `(value, fetched)` — a two-element tuple where *value* + is the retrieved object and *fetched* is the number of items + actually returned (`0` or `1`). + + - `celt != 1` (including `0`): + A `VARIANT` array of length *celt* is allocated and filled in a + single COM call. Only the first *fetched* slots are meaningful; + the rest are discarded. The return value is fetched items + (possibly empty when `celt == 0` or nothing is left in the + enumeration). + + Args: + celt: The maximum number of items to retrieve. + + Returns: + A `(value, fetched)` tuple when *celt* is `1`. + Fetched items when *celt* is not `1`. + + Note: + This object implements dunder methods that define iterator and + container behavior, so a more Pythonic approach is recommended + for accessing its elements rather than calling this method + directly. + """ fetched = c_ulong() if celt == 1: v = VARIANT() From 560053d9380161d029790c6d16a8950736a98652 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sun, 4 Oct 2026 12:23:14 +0900 Subject: [PATCH 4/5] chore: Document the historical context behind the current behavior in a comment. --- comtypes/automation.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/comtypes/automation.py b/comtypes/automation.py index 51ccb23d..90930ae3 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -704,6 +704,22 @@ def Next(self, celt): # type: ignore for accessing its elements rather than calling this method directly. """ + # This wrapper deviates from a plain COM `IEnumVARIANT::Next` proxy in + # two ways that are specific to this package: + # + # 1. Support for celt != 1 (commit 9f68b6a, by theller): + # "this allows to get more objects at a time." + # When celt != 1, the method allocates a VARIANT array, fetches up + # to `celt` items in a single COM call, and returns them as a list. + # + # 2. Return type for celt == 1 (commit 65bdc13, by theller): + # The original override returned only the unwrapped value. It was + # later corrected so that celt == 1 returns a tuple corresponding + # exactly to the two `[out]` parameters declared by the COM method + # specifier. + # + # Both decisions are those of the package originator (theller) and are + # intentionally preserved here. fetched = c_ulong() if celt == 1: v = VARIANT() From 207c3f5b6828798def2bc8a989c252110464e063 Mon Sep 17 00:00:00 2001 From: junkmd Date: Sun, 4 Oct 2026 12:23:14 +0900 Subject: [PATCH 5/5] chore: Comment on the purpose of operations that manipulate variables no longer affecting the return value. --- comtypes/automation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/comtypes/automation.py b/comtypes/automation.py index 90930ae3..acd4e86e 100644 --- a/comtypes/automation.py +++ b/comtypes/automation.py @@ -728,6 +728,7 @@ def Next(self, celt): # type: ignore array = (VARIANT * celt)() self.__com_Next(celt, array, fetched) # type: ignore result = [v._get_value(dynamic=self._dynamic) for v in array[: fetched.value]] + # Release VARIANT refcounts before the temporary array is freed. for v in array: v.value = None return result