Generate a wrapper for members of the VBA object library accessed by pywin32
Some users of pywin32 use it for sending VBA commands to Word and Excel. The problem is that pywin32 does not have permanent wrappers around VBA objects, instead makepy is used to generate the necessary modules. That causes some inconvenience.
- No annotation type hints
def my_func (workbook: Workbook) - No type checking
isinstance(workbook, Workbook) - No autocomplete by IDE for class members
- No parameter hints for method members
For example a workbook object type would be:
<class 'win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9.Workbook.Workbook'>
So to use the Workbook type in annotations, type checks, parameter hints etc.. A manual definition is required, such as:
Workbook = win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9.Workbook.Workbook
However consider:
-
It would be only a partial solution because the wrapper generated by makepy had wrapped the methods as class members but not the properties, properties are mapped in a dictionary instead.
-
Manually re-declare all VBA types with meaningful names is unpractical
Is it possible to alter type library construction by makepy so VBA types are easily accessible for the api users?
Several use case examples:
import win32com.client as win32client
from gen_py.ms_word_object_library_16 import Documents, Document, Range
from gen_py.ms_excel_object_library_16 import Application, Workbooks, Workbook, WorkSheets, WorkSheet, Range
excel = win32client.gencache.EnsureDispatch('Excel.Application')
workbook: Workbook = excel.Workbooks.Add()
def foo (doc: Document) -> Range
etc...
@eliyahuA It's worse than that; because the gen_py outputs contain hyphens (win32com.gen_py.00020813-0000-0000-C000-000000000046x0x1x9) you actually can't import them at design & compile time and mypy doesn't follow __import__() statements. On top of that, depending on your environment setup, make_py may use a temp directory so you can't really know where to locate these.
Current workaround:
- Copy + paste the auto-generated classes to a local project repo and rename so that they can be imported.
- This will fail isinstance() checks but help mypy and intellisense/autocomplete.
- Even better would be to convert the classes in the copied file to
Protocols so mypy knows EnsureDispatch will return something with the same methods as in your copy but not necessarily an instance of the copied class.
To solve this problem I think a good method might be:
- Change the module names to use underscores instead of hyphens or to be a sanitised lib name like
ms_word_object_library_16if there is a way to get tlib name from tlib id and vice-versa. - For static analysis like mypy to work it needs files with known paths at design time. Since it looks like we don't want to get makepy to add files to the install directory, their location will always be unknown until runtime when they are looked up. Therefore I think makepy should generate stub .pyi files from the typelibs and allow the user to look after these - e.g. in a per project directory or a known place.
- stub files can be generated from the proxy files with stubgen, or as a step in the creation of the proxy files.
So if I call makepy and pass a stubs directory C:/code/mpyproj/stubs, I should get:
__gen_path__/00020813_0000_0000_C000_000000000046x0x1x9/Application.py
__gen_path__/00020813_0000_0000_C000_000000000046x0x1x9/AppEvents.py
etc., as well as:
C:/code/mpyproj/stubs/win32com/gen_py/00020813_0000_0000_C000_000000000046x0x1x9/Application.pyi
C:/code/mpyproj/stubs/win32com/gen_py/00020813_0000_0000_C000_000000000046x0x1x9/AppEvents.pyi
(if the module names are human readable then 00020813_0000_0000_C000_000000000046x0x1x9 becomes Excel in all cases in these examples, so in user code I write from win32com.gen_py.Excel.Application import Application and I would get all the type info even though the .py files are dynamically located).
- There could definitely be type info for all the win32comext modules which can also be distributed as stub files to retro-add types without having to change the dynamic locations of the actual source code.
Any news on this? It's really a pity that we can't use the generated stubs and have intellisense working.