comtypes 1.4.1__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.
Files changed (109) hide show
  1. {comtypes-1.4.1 → comtypes-1.4.2}/CHANGES.txt +9 -0
  2. {comtypes-1.4.1/comtypes.egg-info → comtypes-1.4.2}/PKG-INFO +1 -1
  3. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/__init__.py +1 -1
  4. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/_generate.py +105 -80
  5. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/hints.pyi +14 -2
  6. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_client.py +14 -4
  7. comtypes-1.4.2/comtypes/test/test_client_regenerate_modules.py +158 -0
  8. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/codegenerator.py +168 -79
  9. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/typeannotator.py +17 -15
  10. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/typedesc.py +1 -1
  11. {comtypes-1.4.1 → comtypes-1.4.2/comtypes.egg-info}/PKG-INFO +1 -1
  12. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes.egg-info/SOURCES.txt +1 -0
  13. {comtypes-1.4.1 → comtypes-1.4.2}/LICENSE.txt +0 -0
  14. {comtypes-1.4.1 → comtypes-1.4.2}/MANIFEST.in +0 -0
  15. {comtypes-1.4.1 → comtypes-1.4.2}/README.md +0 -0
  16. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/GUID.py +0 -0
  17. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/_comobject.py +0 -0
  18. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/_memberspec.py +0 -0
  19. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/_meta.py +0 -0
  20. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/_npsupport.py +0 -0
  21. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/_safearray.py +0 -0
  22. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/automation.py +0 -0
  23. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/clear_cache.py +0 -0
  24. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/__init__.py +0 -0
  25. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/_code_cache.py +0 -0
  26. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/_constants.py +0 -0
  27. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/_events.py +0 -0
  28. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/dynamic.py +0 -0
  29. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/client/lazybind.py +0 -0
  30. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/connectionpoints.py +0 -0
  31. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/errorinfo.py +0 -0
  32. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/git.py +0 -0
  33. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/hresult.py +0 -0
  34. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/logutil.py +0 -0
  35. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/messageloop.py +0 -0
  36. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/patcher.py +0 -0
  37. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/persist.py +0 -0
  38. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/safearray.py +0 -0
  39. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/__init__.py +0 -0
  40. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/automation.py +0 -0
  41. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/connectionpoints.py +0 -0
  42. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/inprocserver.py +0 -0
  43. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/localserver.py +0 -0
  44. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/register.py +0 -0
  45. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/server/w_getopt.py +0 -0
  46. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/shelllink.py +0 -0
  47. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestComServer.idl +0 -0
  48. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestComServer.py +0 -0
  49. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestComServer.tlb +0 -0
  50. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestDispServer.idl +0 -0
  51. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestDispServer.py +0 -0
  52. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/TestDispServer.tlb +0 -0
  53. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/__init__.py +0 -0
  54. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/find_memleak.py +0 -0
  55. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/mylib.idl +0 -0
  56. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/mytypelib.idl +0 -0
  57. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/runtests.py +0 -0
  58. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/setup.py +0 -0
  59. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_BSTR.py +0 -0
  60. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_DISPPARAMS.py +0 -0
  61. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_GUID.py +0 -0
  62. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_QueryService.py +0 -0
  63. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_agilent.py +0 -0
  64. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_avmc.py +0 -0
  65. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_basic.py +0 -0
  66. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_casesensitivity.py +0 -0
  67. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_clear_cache.py +0 -0
  68. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_client_dynamic.py +0 -0
  69. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_collections.py +0 -0
  70. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_comserver.py +0 -0
  71. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_createwrappers.py +0 -0
  72. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_dict.py +0 -0
  73. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_dispinterface.py +0 -0
  74. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_dyndispatch.py +0 -0
  75. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_excel.py +0 -0
  76. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_findgendir.py +0 -0
  77. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_getactiveobj.py +0 -0
  78. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_ie.py +0 -0
  79. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_ienum.py +0 -0
  80. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_imfattributes.py +0 -0
  81. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_inout_args.py +0 -0
  82. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_jscript.js +0 -0
  83. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_msscript.py +0 -0
  84. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_npsupport.py +0 -0
  85. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_outparam.py +0 -0
  86. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_propputref.py +0 -0
  87. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_pump_events.py +0 -0
  88. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_safearray.py +0 -0
  89. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_sapi.py +0 -0
  90. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_server.py +0 -0
  91. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_showevents.py +0 -0
  92. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_subinterface.py +0 -0
  93. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_typeinfo.py +0 -0
  94. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_urlhistory.py +0 -0
  95. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_variant.py +0 -0
  96. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_win32com_interop.py +0 -0
  97. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_wmi.py +0 -0
  98. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/test/test_word.py +0 -0
  99. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/__init__.py +0 -0
  100. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/tlbparser.py +0 -0
  101. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/tools/typedesc_base.py +0 -0
  102. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/typeinfo.py +0 -0
  103. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/util.py +0 -0
  104. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes/viewobject.py +0 -0
  105. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes.egg-info/dependency_links.txt +0 -0
  106. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes.egg-info/entry_points.txt +0 -0
  107. {comtypes-1.4.1 → comtypes-1.4.2}/comtypes.egg-info/top_level.txt +0 -0
  108. {comtypes-1.4.1 → comtypes-1.4.2}/setup.cfg +0 -0
  109. {comtypes-1.4.1 → comtypes-1.4.2}/setup.py +0 -0
@@ -1,6 +1,15 @@
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
+
4
13
  Release 1.4.1
5
14
  --------------
6
15
  * Fix `TypeError` when defining enumeration types
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: comtypes
3
- Version: 1.4.1
3
+ Version: 1.4.2
4
4
  Summary: Pure Python COM package
5
5
  Home-page: https://github.com/enthought/comtypes
6
6
  Download-URL: https://github.com/enthought/comtypes/releases
@@ -1,5 +1,5 @@
1
1
  # comtypes version numbers follow semver (http://semver.org/) and PEP 440
2
- __version__ = "1.4.1"
2
+ __version__ = "1.4.2"
3
3
 
4
4
  import atexit
5
5
  from ctypes import *
@@ -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
- return ModuleGenerator().generate(tlib, pathname)
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 _create_module_in_file(modulename: str, code: str) -> types.ModuleType:
164
- """create module in file system, and import it"""
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
- filename = "%s.py" % modulename.split(".")[-1]
167
- with open(os.path.join(comtypes.client.gen_dir, filename), "w") as ofi:
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
- if hasattr(importlib, "invalidate_caches"):
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.codegen = codegenerator.CodeGenerator(_get_known_symbols())
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
- items = list(p.parse().values())
241
- code = self.codegen.generate_wrapper_code(items, filename=pathname)
242
- for ext_tlib in self.codegen.externals: # generates dependency COM-lib modules
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
- if comtypes.client.gen_dir is None:
245
- return _create_module_in_memory(modulename, code)
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
- def _get_known_symbols() -> Dict[str, str]:
250
- known_symbols: Dict[str, str] = {}
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 test_imports_IEnumVARIANT_from_other_generated_modules(self):
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
- # NOTE: `mscorlib`, which imports `IEnumVARIANT` from `stdole`.
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.
@@ -82,7 +87,12 @@ class Test_GetModule(ut.TestCase):
82
87
  # the definition of an enumeration, the generation of the module will fail.
83
88
  # See also https://github.com/enthought/comtypes/issues/524
84
89
  with contextlib.redirect_stdout(None): # supress warnings
85
- comtypes.client.GetModule("mshtml.tlb")
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))
86
96
 
87
97
  def test_abstracted_wrapper_module_in_friendly_module(self):
88
98
  mod = comtypes.client.GetModule("scrrun.dll")
@@ -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()