Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,10 @@ If any style issues are found, maintainers may ask you to make modifications.
### Pull requests
When you have resolved your issue, open a pull request in the `comtypes` repository.
Please include the issue number on the PR comment.
When enough PRs have been accepted to resolve the issue, please close the issue or mention it to the person(s) involved.
When enough PRs have been accepted to resolve the issue, please close the issue or mention it to the person(s) involved.
The CI pipeline runs on every pull request to ensure runtime functionality is maintained. It tests integration with optional dependencies such as `numpy` and `pywin32`, measures test coverage. Runtime reliability is **tier 1**.
The CI pipeline also runs static type checking (`mypy`, `pyright`, `ty`) on the installed package. This provides **tier 2** quality assurance for type inference and safety.
For critical vulnerability fixes or bug fixes, releases may proceed in urgent cases if runtime tests pass, even when type checking reports errors.

## Contributing to documentation :books:

Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ Functionalities
com_interfaces
npsupport
threading
type_checking


Links
Expand Down
102 changes: 102 additions & 0 deletions docs/source/type_checking.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
################################
Type Checking and Type Inference
################################

``comtypes`` generates Python wrapper module files from COM type
libraries.
These generated modules contain extensive type hints which allow
static type checkers to infer the shapes of COM objects, methods,
properties and the behaviour of factories such as ``CreateObject``
or ``GetModule``.

.. contents::

Basic example – ``Scripting.Dictionary``
****************************************

.. sourcecode:: python

import sys
from ctypes import POINTER

from comtypes.client import CreateObject, GetModule

if sys.version_info >= (3, 11):
from typing import assert_type
else:
from typing_extensions import assert_type

# Generate/ensure the existence of the ``Scripting`` module.
GetModule("scrrun.dll")
# Must be imported statically;
# The type checker cannot perform static type analysis on the
# ``ModuleType`` instance returned by ``GetModule``.
from comtypes.gen import Scripting


dic = CreateObject(
Scripting.Dictionary, interface=Scripting.IDictionary
)
# At runtime, ``dic`` is a ``POINTER(IDictionary)``.
# It behaves as a subclass of ``IDictionary`` via its metaclass.
# Since capabilities to express dynamic subclasses created by
# ``POINTER`` are not introduced into Python's static type system
# yet, it is typed to return ``IDictionary``.
# The static type checker sees the following type:
# dic: Scripting.IDictionary
assert isinstance(dic, POINTER(Scripting.IDictionary))
assert isinstance(dic, Scripting.IDictionary)
assert_type(dic, Scripting.IDictionary)

# Properties are correctly typed
dic.CompareMode = Scripting.TextCompare
# This test verifies that the ``Scripting.Dictionary`` supports
# both subscriptable and callable access patterns, and that the
# static type checker correctly recognizes these operations.
dic["foo"] = 1
assert dic["foo"] == dic.Item["foo"] == 1
dic.Add("bar", 2)
assert dic("bar") == dic.Item("bar") == 2
dic.Item["qux"] = 3
assert dic("qux") == dic.Item("qux") == 3


Limitations
***********

The COM factory is annotated as returning an ``IUnknown``-based
object, although the actual runtime type is the dynamically defined
subclass ``POINTER(IUnknown)``, created through the interaction
between the ``ctypes.POINTER`` factory function and the complex
metaclass of ``IUnknown``.

It is not annotated as the commonly used ctypes pointer type
``ctypes._Pointer[IUnknown]`` because Python's type system treats
``ctypes._Pointer[CT]`` as a container type rather than as a subtype
of ``CT``.
Additionally, the COM pointer type is dynamically generated by
``ctypes.POINTER``, with its behavior defined by the metaclass of
the COM interface.
In particular, although
``isinstance(ctypes.pointer(ctypes.c_int(1)), ctypes._Pointer)``
is ``True``,
``isinstance(CreateObject(progid, interface=IUnknown), ctypes._Pointer)``
is ``False``. Thus, the concrete type of the COM pointer is not
``ctypes._Pointer``.
Furthermore, Python's type system does not currently provide a way
to express the intersection types needed to represent such a
dynamically defined subclass.
As a result, the current Python type system cannot express the
concrete type and type relationship needed for static analysis of
the COM interface methods.

The recommended typing style in Python is to annotate return values
with the base or abstract interface that describes how the value is
expected to be used, rather than with its concrete runtime type.
For example, if a value is expected to be used only as a sequence,
without adding elements to it, ``collections.abc.Sequence[str]`` is
preferable to ``list[str]``, even when the actual returned value is
a ``list[str]``.
By annotating the return value as ``IUnknown``, this base-class
approach enables static analysis tools to recognize COM interface
methods.
Loading