comtypes 1.4.0__zip → 1.4.2__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {comtypes-1.4.0 → comtypes-1.4.2}/CHANGES.txt +13 -0
- {comtypes-1.4.0/comtypes.egg-info → comtypes-1.4.2}/PKG-INFO +1 -1
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/__init__.py +1 -1
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/_generate.py +105 -80
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/hints.pyi +14 -2
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_client.py +23 -3
- comtypes-1.4.2/comtypes/test/test_client_regenerate_modules.py +158 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/codegenerator.py +180 -91
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/typeannotator.py +17 -15
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/typedesc.py +1 -1
- {comtypes-1.4.0 → comtypes-1.4.2/comtypes.egg-info}/PKG-INFO +1 -1
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes.egg-info/SOURCES.txt +1 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/LICENSE.txt +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/MANIFEST.in +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/README.md +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/GUID.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/_comobject.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/_memberspec.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/_meta.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/_npsupport.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/_safearray.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/automation.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/clear_cache.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/__init__.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/_code_cache.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/_constants.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/_events.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/dynamic.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/client/lazybind.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/connectionpoints.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/errorinfo.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/git.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/hresult.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/logutil.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/messageloop.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/patcher.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/persist.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/safearray.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/__init__.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/automation.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/connectionpoints.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/inprocserver.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/localserver.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/register.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/server/w_getopt.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/shelllink.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestComServer.idl +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestComServer.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestComServer.tlb +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestDispServer.idl +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestDispServer.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/TestDispServer.tlb +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/__init__.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/find_memleak.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/mylib.idl +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/mytypelib.idl +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/runtests.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/setup.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_BSTR.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_DISPPARAMS.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_GUID.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_QueryService.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_agilent.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_avmc.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_basic.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_casesensitivity.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_clear_cache.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_client_dynamic.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_collections.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_comserver.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_createwrappers.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_dict.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_dispinterface.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_dyndispatch.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_excel.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_findgendir.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_getactiveobj.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_ie.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_ienum.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_imfattributes.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_inout_args.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_jscript.js +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_msscript.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_npsupport.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_outparam.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_propputref.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_pump_events.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_safearray.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_sapi.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_server.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_showevents.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_subinterface.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_typeinfo.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_urlhistory.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_variant.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_win32com_interop.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_wmi.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/test/test_word.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/__init__.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/tlbparser.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/tools/typedesc_base.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/typeinfo.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/util.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes/viewobject.py +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes.egg-info/dependency_links.txt +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes.egg-info/entry_points.txt +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/comtypes.egg-info/top_level.txt +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/setup.cfg +0 -0
- {comtypes-1.4.0 → comtypes-1.4.2}/setup.py +0 -0
|
@@ -1,6 +1,19 @@
|
|
|
1
1
|
Comtypes CHANGELOG
|
|
2
2
|
==================
|
|
3
3
|
|
|
4
|
+
Release 1.4.2
|
|
5
|
+
--------------
|
|
6
|
+
|
|
7
|
+
* Improve the stability of module generation by ``GetModule``. By @junkmd.
|
|
8
|
+
* Define ``c_int`` aliases earlier than others in wrapper modules. By @junkmd.
|
|
9
|
+
* Remove trailing newlines generated by ``codegenerator`` and ``typeannotator``. By @junkmd.
|
|
10
|
+
* Determine whether a interface is one of the known symbols not only by its name but also by using its iid. By @junkmd.
|
|
11
|
+
* Add ``Incomplete`` and ``Hresult`` to ``hints.pyi``. By @junkmd.
|
|
12
|
+
|
|
13
|
+
Release 1.4.1
|
|
14
|
+
--------------
|
|
15
|
+
* Fix `TypeError` when defining enumeration types
|
|
16
|
+
|
|
4
17
|
Release 1.4.0
|
|
5
18
|
--------------
|
|
6
19
|
* Add enumeration definitions in generated friendly modules. By @junkmd.
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
from __future__ import print_function
|
|
2
2
|
import ctypes
|
|
3
3
|
import importlib
|
|
4
|
+
import inspect
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
import sys
|
|
7
8
|
import types
|
|
8
|
-
from typing import Any, Tuple, List, Optional, Dict, Union as _UnionT
|
|
9
|
+
from typing import Any, Tuple, List, Mapping, Optional, Dict, Union as _UnionT
|
|
9
10
|
import winreg
|
|
10
11
|
|
|
11
12
|
from comtypes import GUID, typeinfo
|
|
@@ -121,7 +122,10 @@ def GetModule(tlib: _UnionT[Any, typeinfo.ITypeLib]) -> types.ModuleType:
|
|
|
121
122
|
pathname = None
|
|
122
123
|
tlib = _load_tlib(tlib)
|
|
123
124
|
logger.debug("GetModule(%s)", tlib.GetLibAttr())
|
|
124
|
-
|
|
125
|
+
mod = _get_existing_module(tlib)
|
|
126
|
+
if mod is not None:
|
|
127
|
+
return mod
|
|
128
|
+
return ModuleGenerator(tlib, pathname).generate()
|
|
125
129
|
|
|
126
130
|
|
|
127
131
|
def _load_tlib(obj: Any) -> typeinfo.ITypeLib:
|
|
@@ -160,94 +164,110 @@ def _load_tlib(obj: Any) -> typeinfo.ITypeLib:
|
|
|
160
164
|
raise TypeError("'%r' is not supported type for loading typelib" % obj)
|
|
161
165
|
|
|
162
166
|
|
|
163
|
-
def
|
|
164
|
-
|
|
167
|
+
def _get_existing_module(tlib: typeinfo.ITypeLib) -> Optional[types.ModuleType]:
|
|
168
|
+
def _get_friendly(name: str) -> Optional[types.ModuleType]:
|
|
169
|
+
try:
|
|
170
|
+
mod = _my_import(name)
|
|
171
|
+
except Exception as details:
|
|
172
|
+
logger.info("Could not import %s: %s", friendly_name, details)
|
|
173
|
+
else:
|
|
174
|
+
return mod
|
|
175
|
+
|
|
176
|
+
def _get_wrapper(name: str) -> Optional[types.ModuleType]:
|
|
177
|
+
if name in sys.modules:
|
|
178
|
+
return sys.modules[name]
|
|
179
|
+
try:
|
|
180
|
+
return _my_import(name)
|
|
181
|
+
except Exception as details:
|
|
182
|
+
logger.info("Could not import %s: %s", name, details)
|
|
183
|
+
|
|
184
|
+
wrapper_name = codegenerator.name_wrapper_module(tlib)
|
|
185
|
+
friendly_name = codegenerator.name_friendly_module(tlib)
|
|
186
|
+
wrapper_module = _get_wrapper(wrapper_name)
|
|
187
|
+
if wrapper_module is not None:
|
|
188
|
+
if friendly_name is None:
|
|
189
|
+
return wrapper_module
|
|
190
|
+
else:
|
|
191
|
+
friendly_module = _get_friendly(friendly_name)
|
|
192
|
+
if friendly_module is not None:
|
|
193
|
+
return friendly_module
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _create_module(modulename: str, code: str) -> types.ModuleType:
|
|
198
|
+
"""Creates the module, then imports it."""
|
|
165
199
|
# `modulename` is 'comtypes.gen.xxx'
|
|
166
|
-
|
|
167
|
-
|
|
200
|
+
stem = modulename.split(".")[-1]
|
|
201
|
+
if comtypes.client.gen_dir is None:
|
|
202
|
+
# in memory system
|
|
203
|
+
import comtypes.gen as g
|
|
204
|
+
|
|
205
|
+
mod = types.ModuleType(modulename)
|
|
206
|
+
abs_gen_path = os.path.abspath(g.__path__[0]) # type: ignore
|
|
207
|
+
mod.__file__ = os.path.join(abs_gen_path, "<memory>")
|
|
208
|
+
exec(code, mod.__dict__)
|
|
209
|
+
sys.modules[modulename] = mod
|
|
210
|
+
setattr(g, stem, mod)
|
|
211
|
+
return mod
|
|
212
|
+
# in file system
|
|
213
|
+
with open(os.path.join(comtypes.client.gen_dir, f"{stem}.py"), "w") as ofi:
|
|
168
214
|
print(code, file=ofi)
|
|
169
215
|
# clear the import cache to make sure Python sees newly created modules
|
|
170
|
-
|
|
171
|
-
importlib.invalidate_caches()
|
|
216
|
+
importlib.invalidate_caches()
|
|
172
217
|
return _my_import(modulename)
|
|
173
218
|
|
|
174
219
|
|
|
175
|
-
def _create_module_in_memory(modulename: str, code: str) -> types.ModuleType:
|
|
176
|
-
"""create module in memory system, and import it"""
|
|
177
|
-
# `modulename` is 'comtypes.gen.xxx'
|
|
178
|
-
import comtypes.gen as g
|
|
179
|
-
|
|
180
|
-
mod = types.ModuleType(modulename)
|
|
181
|
-
abs_gen_path = os.path.abspath(g.__path__[0]) # type: ignore
|
|
182
|
-
mod.__file__ = os.path.join(abs_gen_path, "<memory>")
|
|
183
|
-
exec(code, mod.__dict__)
|
|
184
|
-
sys.modules[modulename] = mod
|
|
185
|
-
setattr(g, modulename.split(".")[-1], mod)
|
|
186
|
-
return mod
|
|
187
|
-
|
|
188
|
-
|
|
189
220
|
class ModuleGenerator(object):
|
|
190
|
-
def __init__(self) -> None:
|
|
191
|
-
self.
|
|
192
|
-
|
|
193
|
-
def generate(
|
|
194
|
-
self, tlib: typeinfo.ITypeLib, pathname: Optional[str]
|
|
195
|
-
) -> types.ModuleType:
|
|
196
|
-
# create and import the real typelib wrapper module
|
|
197
|
-
mod = self._create_wrapper_module(tlib, pathname)
|
|
198
|
-
# try to get the friendly-name, if not, returns the real typelib wrapper module
|
|
199
|
-
modulename = codegenerator.name_friendly_module(tlib)
|
|
200
|
-
if modulename is None:
|
|
201
|
-
return mod
|
|
202
|
-
# create and import the friendly-named module
|
|
203
|
-
return self._create_friendly_module(tlib, modulename)
|
|
204
|
-
|
|
205
|
-
def _create_friendly_module(
|
|
206
|
-
self, tlib: typeinfo.ITypeLib, modulename: str
|
|
207
|
-
) -> types.ModuleType:
|
|
208
|
-
"""helper which creates and imports the friendly-named module."""
|
|
209
|
-
try:
|
|
210
|
-
mod = _my_import(modulename)
|
|
211
|
-
except Exception as details:
|
|
212
|
-
logger.info("Could not import %s: %s", modulename, details)
|
|
213
|
-
else:
|
|
214
|
-
return mod
|
|
215
|
-
# the module is always regenerated if the import fails
|
|
216
|
-
logger.info("# Generating %s", modulename)
|
|
217
|
-
# determine the Python module name
|
|
218
|
-
modname = codegenerator.name_wrapper_module(tlib)
|
|
219
|
-
code = self.codegen.generate_friendly_code(modname)
|
|
220
|
-
if comtypes.client.gen_dir is None:
|
|
221
|
-
return _create_module_in_memory(modulename, code)
|
|
222
|
-
return _create_module_in_file(modulename, code)
|
|
223
|
-
|
|
224
|
-
def _create_wrapper_module(
|
|
225
|
-
self, tlib: typeinfo.ITypeLib, pathname: Optional[str]
|
|
226
|
-
) -> types.ModuleType:
|
|
227
|
-
"""helper which creates and imports the real typelib wrapper module."""
|
|
228
|
-
modulename = codegenerator.name_wrapper_module(tlib)
|
|
229
|
-
if modulename in sys.modules:
|
|
230
|
-
return sys.modules[modulename]
|
|
231
|
-
try:
|
|
232
|
-
return _my_import(modulename)
|
|
233
|
-
except Exception as details:
|
|
234
|
-
logger.info("Could not import %s: %s", modulename, details)
|
|
235
|
-
# generate the module since it doesn't exist or is out of date
|
|
236
|
-
logger.info("# Generating %s", modulename)
|
|
237
|
-
p = tlbparser.TypeLibParser(tlib)
|
|
221
|
+
def __init__(self, tlib: typeinfo.ITypeLib, pathname: Optional[str]) -> None:
|
|
222
|
+
self.wrapper_name = codegenerator.name_wrapper_module(tlib)
|
|
223
|
+
self.friendly_name = codegenerator.name_friendly_module(tlib)
|
|
238
224
|
if pathname is None:
|
|
239
|
-
pathname = tlbparser.get_tlib_filename(tlib)
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
225
|
+
self.pathname = tlbparser.get_tlib_filename(tlib)
|
|
226
|
+
else:
|
|
227
|
+
self.pathname = pathname
|
|
228
|
+
self.tlib = tlib
|
|
229
|
+
|
|
230
|
+
def generate(self) -> types.ModuleType:
|
|
231
|
+
"""Generates wrapper and friendly modules."""
|
|
232
|
+
known_symbols, known_interfaces = _get_known_namespaces()
|
|
233
|
+
codegen = codegenerator.CodeGenerator(known_symbols, known_interfaces)
|
|
234
|
+
codebases: List[Tuple[str, str]] = []
|
|
235
|
+
logger.info("# Generating %s", self.wrapper_name)
|
|
236
|
+
items = list(tlbparser.TypeLibParser(self.tlib).parse().values())
|
|
237
|
+
wrp_code = codegen.generate_wrapper_code(items, filename=self.pathname)
|
|
238
|
+
codebases.append((self.wrapper_name, wrp_code))
|
|
239
|
+
if self.friendly_name is not None:
|
|
240
|
+
logger.info("# Generating %s", self.friendly_name)
|
|
241
|
+
frd_code = codegen.generate_friendly_code(self.wrapper_name)
|
|
242
|
+
codebases.append((self.friendly_name, frd_code))
|
|
243
|
+
for ext_tlib in codegen.externals: # generates dependency COM-lib modules
|
|
243
244
|
GetModule(ext_tlib)
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
return _create_module_in_file(modulename, code)
|
|
245
|
+
return [_create_module(name, code) for (name, code) in codebases][-1]
|
|
246
|
+
|
|
247
247
|
|
|
248
|
+
_SymbolName = str
|
|
249
|
+
_ModuleName = str
|
|
250
|
+
_ItfName = str
|
|
251
|
+
_ItfIid = str
|
|
248
252
|
|
|
249
|
-
|
|
250
|
-
|
|
253
|
+
|
|
254
|
+
def _get_known_namespaces() -> Tuple[
|
|
255
|
+
Mapping[_SymbolName, _ModuleName], Mapping[_ItfName, _ItfIid]
|
|
256
|
+
]:
|
|
257
|
+
"""Returns symbols and interfaces that are already statically defined in `ctypes`
|
|
258
|
+
and `comtypes`.
|
|
259
|
+
From `ctypes`, all the names are obtained.
|
|
260
|
+
From `comtypes`, only the names in each module's `__known_symbols__` are obtained.
|
|
261
|
+
|
|
262
|
+
Note:
|
|
263
|
+
The interfaces that should be included in `__known_symbols__` should be limited
|
|
264
|
+
to those that can be said to be bound to the design concept of COM, such as
|
|
265
|
+
`IUnknown`, and those defined in `objidl` and `oaidl`.
|
|
266
|
+
`comtypes` does NOT aim to statically define all COM object interfaces in
|
|
267
|
+
its repository.
|
|
268
|
+
"""
|
|
269
|
+
known_symbols: Dict[_SymbolName, _ModuleName] = {}
|
|
270
|
+
known_interfaces: Dict[_ItfName, _ItfIid] = {}
|
|
251
271
|
for mod_name in (
|
|
252
272
|
"comtypes.persist",
|
|
253
273
|
"comtypes.typeinfo",
|
|
@@ -259,11 +279,16 @@ def _get_known_symbols() -> Dict[str, str]:
|
|
|
259
279
|
mod = importlib.import_module(mod_name)
|
|
260
280
|
if hasattr(mod, "__known_symbols__"):
|
|
261
281
|
names: List[str] = mod.__known_symbols__
|
|
282
|
+
for name in names:
|
|
283
|
+
tgt = getattr(mod, name)
|
|
284
|
+
if inspect.isclass(tgt) and issubclass(tgt, comtypes.IUnknown):
|
|
285
|
+
assert name not in known_interfaces
|
|
286
|
+
known_interfaces[name] = str(tgt._iid_)
|
|
262
287
|
else:
|
|
263
288
|
names = list(mod.__dict__)
|
|
264
289
|
for name in names:
|
|
265
290
|
known_symbols[name] = mod.__name__
|
|
266
|
-
return known_symbols
|
|
291
|
+
return known_symbols, known_interfaces
|
|
267
292
|
|
|
268
293
|
|
|
269
294
|
################################################################
|
|
@@ -22,9 +22,11 @@ if sys.version_info >= (3, 8):
|
|
|
22
22
|
else:
|
|
23
23
|
from typing_extensions import Protocol
|
|
24
24
|
if sys.version_info >= (3, 10):
|
|
25
|
-
from typing import Concatenate, ParamSpec
|
|
25
|
+
from typing import Concatenate, ParamSpec, TypeAlias
|
|
26
|
+
from typing import TypeGuard as TypeGuard
|
|
26
27
|
else:
|
|
27
|
-
from typing_extensions import Concatenate, ParamSpec
|
|
28
|
+
from typing_extensions import Concatenate, ParamSpec, TypeAlias
|
|
29
|
+
from typing_extensions import TypeGuard as TypeGuard
|
|
28
30
|
if sys.version_info >= (3, 11):
|
|
29
31
|
from typing import Self
|
|
30
32
|
else:
|
|
@@ -34,6 +36,16 @@ from comtypes.automation import IDispatch as IDispatch, VARIANT as VARIANT
|
|
|
34
36
|
from comtypes.server import IClassFactory as IClassFactory
|
|
35
37
|
from comtypes.typeinfo import ITypeInfo as ITypeInfo
|
|
36
38
|
|
|
39
|
+
Incomplete: TypeAlias = Any
|
|
40
|
+
"""The type symbol is used temporarily until the COM library parsers or
|
|
41
|
+
code generators are enhanced to annotate detailed type hints.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
Hresult: TypeAlias = int
|
|
45
|
+
"""The value returned when calling a method with no `[out]` or `[out, retval]`
|
|
46
|
+
arguments and with `HRESULT` as its return type in its COM method definition.
|
|
47
|
+
"""
|
|
48
|
+
|
|
37
49
|
class _MethodTypeDesc(Protocol):
|
|
38
50
|
arguments: List[Tuple[Any, str, List[str], Optional[Any]]]
|
|
39
51
|
idlflags: List[str]
|
|
@@ -59,12 +59,17 @@ class Test_GetModule(ut.TestCase):
|
|
|
59
59
|
mod = comtypes.client.GetModule(typeinfo.LoadTypeLibEx("scrrun.dll"))
|
|
60
60
|
self.assertIs(mod, Scripting)
|
|
61
61
|
|
|
62
|
-
def
|
|
62
|
+
def test_mscorlib(self):
|
|
63
63
|
# NOTE: `codegenerator` generates code that contains unused imports,
|
|
64
64
|
# but removing them are attracting wierd bugs in library-wrappers
|
|
65
65
|
# which depend on externals.
|
|
66
|
-
#
|
|
67
|
-
comtypes.client.GetModule(("{BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}",))
|
|
66
|
+
# `mscorlib` imports `stdole` wrapper module and refers`IEnumVARIANT` from it.
|
|
67
|
+
mod = comtypes.client.GetModule(("{BED7F4EA-1A96-11D2-8F08-00A0C9A6186D}",))
|
|
68
|
+
# NOTE: `ModuleGenerator` treats the `ctypes._Pointer` base class for pointers
|
|
69
|
+
# as one of the known symbols, but `mscorlib` has the `_Pointer` com interface.
|
|
70
|
+
# Even though they have the same name, `codegenerator` generates code to define
|
|
71
|
+
# the `_Pointer` interface, rather than importing `_Pointer` from `ctypes`.
|
|
72
|
+
self.assertTrue(issubclass(mod._Pointer, comtypes.IUnknown))
|
|
68
73
|
|
|
69
74
|
def test_no_replacing_Patch_namespace(self):
|
|
70
75
|
# NOTE: An object named `Patch` is defined in some dll.
|
|
@@ -74,6 +79,21 @@ class Test_GetModule(ut.TestCase):
|
|
|
74
79
|
# NOTE: `WindowsInstaller`, which has `Patch` definition in dll.
|
|
75
80
|
comtypes.client.GetModule("msi.dll")
|
|
76
81
|
|
|
82
|
+
def test_the_name_of_the_enum_member_and_the_coclass_are_duplicated(self):
|
|
83
|
+
# NOTE: In `MSHTML`, the name `htmlInputImage` is used both as a member of
|
|
84
|
+
# the `_htmlInput` enum type and as a CoClass that has `IHTMLElement` and
|
|
85
|
+
# others as interfaces.
|
|
86
|
+
# If a CoClass is assigned where an integer should be assigned, such as in
|
|
87
|
+
# the definition of an enumeration, the generation of the module will fail.
|
|
88
|
+
# See also https://github.com/enthought/comtypes/issues/524
|
|
89
|
+
with contextlib.redirect_stdout(None): # supress warnings
|
|
90
|
+
mshtml = comtypes.client.GetModule("mshtml.tlb")
|
|
91
|
+
# When the member of an enumeration and a CoClass have the same name,
|
|
92
|
+
# the defined later one is assigned to the name in the module.
|
|
93
|
+
# By asserting whether the CoClass is assigned to that name, it ensures
|
|
94
|
+
# that the member of the enumeration is defined earlier.
|
|
95
|
+
self.assertTrue(issubclass(mshtml.htmlInputImage, comtypes.CoClass))
|
|
96
|
+
|
|
77
97
|
def test_abstracted_wrapper_module_in_friendly_module(self):
|
|
78
98
|
mod = comtypes.client.GetModule("scrrun.dll")
|
|
79
99
|
self.assertTrue(hasattr(mod, "__wrapper_module__"))
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import contextlib
|
|
2
|
+
import importlib
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import shutil
|
|
5
|
+
import sys
|
|
6
|
+
import tempfile
|
|
7
|
+
import types
|
|
8
|
+
from typing import Iterator
|
|
9
|
+
import unittest as ut
|
|
10
|
+
from unittest import mock
|
|
11
|
+
|
|
12
|
+
import comtypes
|
|
13
|
+
import comtypes.client
|
|
14
|
+
import comtypes.gen
|
|
15
|
+
|
|
16
|
+
comtypes.client.GetModule("scrrun.dll")
|
|
17
|
+
from comtypes.gen import Scripting # noqa
|
|
18
|
+
from comtypes.gen import stdole # noqa
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
SCRRUN_FRIENDLY = Path(Scripting.__file__)
|
|
22
|
+
SCRRUN_WRAPPER = Path(Scripting.__wrapper_module__.__file__)
|
|
23
|
+
STDOLE_FRIENDLY = Path(stdole.__file__)
|
|
24
|
+
STDOLE_WRAPPER = Path(stdole.__wrapper_module__.__file__)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@contextlib.contextmanager
|
|
28
|
+
def _mkdtmp_gen_dir() -> Iterator[Path]:
|
|
29
|
+
with tempfile.TemporaryDirectory() as t:
|
|
30
|
+
tmp_dir = Path(t)
|
|
31
|
+
tmp_comtypes_dir = tmp_dir / "comtypes"
|
|
32
|
+
tmp_comtypes_dir.mkdir()
|
|
33
|
+
(tmp_comtypes_dir / "__init__.py").touch()
|
|
34
|
+
tmp_comtypes_gen_dir = tmp_comtypes_dir / "gen"
|
|
35
|
+
tmp_comtypes_gen_dir.mkdir()
|
|
36
|
+
(tmp_comtypes_gen_dir / "__init__.py").touch()
|
|
37
|
+
yield tmp_comtypes_gen_dir
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@contextlib.contextmanager
|
|
41
|
+
def _patch_gen_pkg(new_path: Path) -> Iterator[types.ModuleType]:
|
|
42
|
+
new_comtypes_init = (new_path / "comtypes" / "__init__.py").resolve()
|
|
43
|
+
assert new_comtypes_init.exists()
|
|
44
|
+
new_comtypes_gen_init = (new_path / "comtypes" / "gen" / "__init__.py").resolve()
|
|
45
|
+
assert new_comtypes_gen_init.exists()
|
|
46
|
+
orig_comtypes = sys.modules["comtypes"]
|
|
47
|
+
orig_gen_names = list(filter(lambda k: k.startswith("comtypes.gen"), sys.modules))
|
|
48
|
+
tmp_sys_path = list(sys.path) # copy
|
|
49
|
+
with mock.patch.object(sys, "path", tmp_sys_path):
|
|
50
|
+
sys.path.insert(0, str(new_path))
|
|
51
|
+
with mock.patch.dict(sys.modules):
|
|
52
|
+
# The reason for removing the parent module (in this case, `comtypes`)
|
|
53
|
+
# from `sys.modules` is because the child module (in this case,
|
|
54
|
+
# `comtypes.gen`) refers to the namespace of the parent module.
|
|
55
|
+
# If the parent module exists in `sys.modules`, Python uses that cache
|
|
56
|
+
# to import the child module. Therefore, in order to import a new version
|
|
57
|
+
# of the child module, it is necessary to temporarily remove the parent
|
|
58
|
+
# module from `sys.modules`.
|
|
59
|
+
del sys.modules["comtypes"]
|
|
60
|
+
for k in orig_gen_names:
|
|
61
|
+
del sys.modules[k]
|
|
62
|
+
# The module that is imported here is not the one cached in `sys.modules`
|
|
63
|
+
# before the patch, but the module that is newly loaded from
|
|
64
|
+
# `new_path / 'comtypes' / 'gen' / '__init__.py'`.
|
|
65
|
+
new_comtypes_gen = importlib.import_module("comtypes.gen")
|
|
66
|
+
assert new_comtypes_gen.__file__ is not None
|
|
67
|
+
assert Path(new_comtypes_gen.__file__).resolve() == new_comtypes_gen_init
|
|
68
|
+
# The `comtypes` module cached in `sys.modules` as a side effect of
|
|
69
|
+
# executing the above line is empty because it is the one loaded from
|
|
70
|
+
# `new_path / 'comtypes' / '__init__.py'`.
|
|
71
|
+
# If we call the test target as it is, an error will occur due to
|
|
72
|
+
# referencing an empty module, so we restore the original `comtypes`
|
|
73
|
+
# to `sys.modules`.
|
|
74
|
+
sys.modules["comtypes"] = orig_comtypes
|
|
75
|
+
assert sys.modules["comtypes.gen"] is new_comtypes_gen
|
|
76
|
+
# By making the empty `comtypes.gen` package we created earlier to be
|
|
77
|
+
# referenced as the `gen` attribute of `comtypes`, the original
|
|
78
|
+
# `comtypes.gen` will not be referenced within the context.
|
|
79
|
+
with mock.patch.object(orig_comtypes, "gen", new_comtypes_gen):
|
|
80
|
+
yield new_comtypes_gen
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@contextlib.contextmanager
|
|
84
|
+
def patch_gen_dir() -> Iterator[Path]:
|
|
85
|
+
with _mkdtmp_gen_dir() as tmp_gen_dir:
|
|
86
|
+
with mock.patch.object(comtypes.client, "gen_dir", str(tmp_gen_dir)):
|
|
87
|
+
try:
|
|
88
|
+
with _patch_gen_pkg(tmp_gen_dir.parent.parent):
|
|
89
|
+
yield tmp_gen_dir
|
|
90
|
+
finally:
|
|
91
|
+
importlib.invalidate_caches()
|
|
92
|
+
importlib.reload(comtypes.gen)
|
|
93
|
+
importlib.reload(stdole)
|
|
94
|
+
importlib.reload(Scripting)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Test(ut.TestCase):
|
|
98
|
+
def test_all_modules_are_missing(self):
|
|
99
|
+
with patch_gen_dir() as gen_dir:
|
|
100
|
+
# ensure `gen_dir` and `sys.modules` are patched.
|
|
101
|
+
with self.assertRaises(ImportError):
|
|
102
|
+
from comtypes.gen import Scripting as _ # noqa
|
|
103
|
+
self.assertFalse((gen_dir / SCRRUN_FRIENDLY.name).exists())
|
|
104
|
+
self.assertFalse((gen_dir / SCRRUN_WRAPPER.name).exists())
|
|
105
|
+
self.assertFalse((gen_dir / STDOLE_FRIENDLY.name).exists())
|
|
106
|
+
self.assertFalse((gen_dir / STDOLE_WRAPPER.name).exists())
|
|
107
|
+
# generate new files and modules.
|
|
108
|
+
comtypes.client.GetModule("scrrun.dll")
|
|
109
|
+
self.assertTrue((gen_dir / SCRRUN_FRIENDLY.name).exists())
|
|
110
|
+
self.assertTrue((gen_dir / SCRRUN_WRAPPER.name).exists())
|
|
111
|
+
self.assertTrue((gen_dir / STDOLE_FRIENDLY.name).exists())
|
|
112
|
+
self.assertTrue((gen_dir / STDOLE_WRAPPER.name).exists())
|
|
113
|
+
|
|
114
|
+
def test_friendly_module_is_missing(self):
|
|
115
|
+
with patch_gen_dir() as gen_dir:
|
|
116
|
+
shutil.copy2(SCRRUN_WRAPPER, gen_dir / SCRRUN_WRAPPER.name)
|
|
117
|
+
wrp_mtime = (gen_dir / SCRRUN_WRAPPER.name).stat().st_mtime_ns
|
|
118
|
+
shutil.copy2(STDOLE_FRIENDLY, gen_dir / STDOLE_FRIENDLY.name)
|
|
119
|
+
shutil.copy2(STDOLE_WRAPPER, gen_dir / STDOLE_WRAPPER.name)
|
|
120
|
+
comtypes.client.GetModule("scrrun.dll")
|
|
121
|
+
self.assertTrue((gen_dir / SCRRUN_FRIENDLY.name).exists())
|
|
122
|
+
# Check the most recent content modification time to confirm whether
|
|
123
|
+
# the module file has been regenerated.
|
|
124
|
+
self.assertGreater(
|
|
125
|
+
(gen_dir / SCRRUN_WRAPPER.name).stat().st_mtime_ns, wrp_mtime
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
def test_wrapper_module_is_missing(self):
|
|
129
|
+
with patch_gen_dir() as gen_dir:
|
|
130
|
+
shutil.copy2(SCRRUN_WRAPPER, gen_dir / SCRRUN_FRIENDLY.name)
|
|
131
|
+
frd_mtime = (gen_dir / SCRRUN_FRIENDLY.name).stat().st_mtime_ns
|
|
132
|
+
shutil.copy2(STDOLE_FRIENDLY, gen_dir / STDOLE_FRIENDLY.name)
|
|
133
|
+
shutil.copy2(STDOLE_WRAPPER, gen_dir / STDOLE_WRAPPER.name)
|
|
134
|
+
comtypes.client.GetModule("scrrun.dll")
|
|
135
|
+
self.assertTrue((gen_dir / SCRRUN_WRAPPER.name).exists())
|
|
136
|
+
self.assertGreater(
|
|
137
|
+
(gen_dir / SCRRUN_FRIENDLY.name).stat().st_mtime_ns, frd_mtime
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
def test_dependency_modules_are_missing(self):
|
|
141
|
+
with patch_gen_dir() as gen_dir:
|
|
142
|
+
shutil.copy2(SCRRUN_WRAPPER, gen_dir / SCRRUN_FRIENDLY.name)
|
|
143
|
+
frd_mtime = (gen_dir / SCRRUN_FRIENDLY.name).stat().st_mtime_ns
|
|
144
|
+
shutil.copy2(SCRRUN_WRAPPER, gen_dir / SCRRUN_WRAPPER.name)
|
|
145
|
+
wrp_mtime = (gen_dir / SCRRUN_WRAPPER.name).stat().st_mtime_ns
|
|
146
|
+
comtypes.client.GetModule("scrrun.dll")
|
|
147
|
+
self.assertTrue((gen_dir / STDOLE_FRIENDLY.name).exists())
|
|
148
|
+
self.assertTrue((gen_dir / STDOLE_WRAPPER.name).exists())
|
|
149
|
+
self.assertGreater(
|
|
150
|
+
(gen_dir / SCRRUN_FRIENDLY.name).stat().st_mtime_ns, frd_mtime
|
|
151
|
+
)
|
|
152
|
+
self.assertGreater(
|
|
153
|
+
(gen_dir / SCRRUN_WRAPPER.name).stat().st_mtime_ns, wrp_mtime
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
if __name__ == "__main__":
|
|
158
|
+
ut.main()
|