Python (Stub) generation
Opened this issue · 0 comments
What is the goal of this request?
As a python developer I want to use a COM client to develop a tool automation / tool integration. With C++ and .NET I have support for type checking and language server support.
As python developer the type inspection only works at runtime. It would be good to be able to use newer technologies like python type hinting and static type checkers like mypy.
Example C# interface.
Consider the following interface:
// DLL: Sample.dll
[ComVisible(true)]
[Guid("8d64afd8-f62e-45a5-92c2-89c3c0d83eb9")]
[ProgId("SampleComponent.ComponentImplementation")]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(IMyInterface))]
public class SampleComponent: IMyInterface
{
public string Version { get; }
public void LongRunningOperation(string valueParameter, int timeout) { /***/ }
public string[] GetItems() { return Array.Empty<string>(); }
}
Feature: Python Dispatch
With this getting into mind, the following python class should be available:
# file sample_component.py
from win32com.client import Dispatch
from typing import TYPECHECKING, cast
if TYPECHECKING:
import .sample as dispatched
def get_sample_component() -> "dispatched.SampleComponent":
instance = Dispatch("SampleComponent.ComponentImplementation")
return cast("dispatched.ComponentImplementation", instance)
Feature: Python Stub Interfaces
In order to be able to check my python code against static python type hints, I need python stub interfaces:
# file sample.pyi
from win32com.client import Dispatch
from collections.abc import Collection
class SampleComponent(Dispatch):
@property
def Version(self) -> "str": ...
def LongRunningOperation(valueParameter: "str", timeout: "str") -> None: ...
def GetItems() -> "Collection[str]": ...
def get_sample_component() -> "SampleComponent": ...
all_items = [
"get_sample_component",
"SampleComponent",
]
__all__ = all_items
del Dispatch
del Collection
Feature: Packaging
In tool chain scenarios it might be wise to pack mulitple stubs / implementations into (hierarchical) packages. Hence the packaging should be available. The package should be selectable, either a namespace package or a classic package.
Example: classic package
from typing import TYPE_CHECKING
import package.sample_component as sc
if TYPE_CHECKING:
import package.sample as sc_types
component: "sc_types.SampleComponent" = sc.get_sample_component()
print(component.Version)
Example: namespace package
from typing import TYPE_CHECKING
import package.sample_component as sc
if TYPE_CHECKING:
import package.sample as sc_types
component: "sc_types.SampleComponent" = sc.get_sample_component()
print(component.Version)
The difference lies in the different implementation of the init.py in the files.
Planned implementation
Command-line
It is planned to add a new export command: tlb2pyi
The following options will apply:
--package
A (dot-separated) python package name (defaults to assembly file name)
--shared-file-name
The name of the shared file (defaults to the assembly file name with dots replaced by underscores)
--support-namespace
A boolean flag to support namespace packages (defaults to assembly file name contains a dot)
--output-dir
The output directory to use (defaults to .)
assembly
The dll to dump
Other arguments might be added on requirement.
Implementation
The assembly will be loaded into the context.
For each class flagged with ProgId
, a python class will be created inheriting from Dispatch. In addition, a get_ method will be created where Camel Case will be transformed to lower snake case.
All classes, value types, enums and interfaces will be dumped to the shared file.
Enums will derive from enum.IntEnum
. Interfaces will be implemented as Protocols
. Only public, ComVisible, Non-abstract classed will be exported to type stubs.
Mapping of .NET Types to Python types:
string: str
int: int
short: int (maybe annotated)
sbyte: int (maybe annotated)
long: int (maybe annotated)
uint: int (maybe annotated)
ushort: int (maybe annotated)
byte: int (maybe annotated)
ulong: int (maybe annotated)
float: float
double: float
Guid: TBD
Array: collections.abc.Collection (sized, iterable, container)
List: list
Dictionary: Mapping
Other types: TBD
Limitations
The pystub generator in its first implementation will only consider self-contained assemblies without related TLBs.