comtypes 1.4.1__zip → 1.4.3__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 (116) hide show
  1. {comtypes-1.4.1 → comtypes-1.4.3}/CHANGES.txt +24 -1
  2. {comtypes-1.4.1/comtypes.egg-info → comtypes-1.4.3}/PKG-INFO +29 -1
  3. {comtypes-1.4.1 → comtypes-1.4.3}/README.md +28 -0
  4. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/__init__.py +6 -8
  5. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/automation.py +26 -2
  6. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/_generate.py +105 -80
  7. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/hints.pyi +29 -2
  8. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_GUID.py +23 -2
  9. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_client.py +14 -4
  10. comtypes-1.4.3/comtypes/test/test_client_regenerate_modules.py +158 -0
  11. comtypes-1.4.3/comtypes/test/test_dispifc_records.py +90 -0
  12. comtypes-1.4.3/comtypes/test/test_findgendir.py +87 -0
  13. comtypes-1.4.3/comtypes/tools/codegenerator/__init__.py +5 -0
  14. {comtypes-1.4.1/comtypes/tools → comtypes-1.4.3/comtypes/tools/codegenerator}/codegenerator.py +140 -716
  15. comtypes-1.4.3/comtypes/tools/codegenerator/helpers.py +357 -0
  16. comtypes-1.4.3/comtypes/tools/codegenerator/modulenamer.py +24 -0
  17. comtypes-1.4.3/comtypes/tools/codegenerator/namespaces.py +212 -0
  18. comtypes-1.4.3/comtypes/tools/codegenerator/packing.py +81 -0
  19. {comtypes-1.4.1/comtypes/tools → comtypes-1.4.3/comtypes/tools/codegenerator}/typeannotator.py +32 -15
  20. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/tools/tlbparser.py +19 -49
  21. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/tools/typedesc.py +58 -10
  22. {comtypes-1.4.1 → comtypes-1.4.3/comtypes.egg-info}/PKG-INFO +29 -1
  23. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes.egg-info/SOURCES.txt +10 -3
  24. {comtypes-1.4.1 → comtypes-1.4.3}/setup.py +1 -0
  25. comtypes-1.4.1/comtypes/test/test_findgendir.py +0 -94
  26. {comtypes-1.4.1 → comtypes-1.4.3}/LICENSE.txt +0 -0
  27. {comtypes-1.4.1 → comtypes-1.4.3}/MANIFEST.in +0 -0
  28. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/GUID.py +0 -0
  29. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/_comobject.py +0 -0
  30. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/_memberspec.py +0 -0
  31. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/_meta.py +0 -0
  32. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/_npsupport.py +0 -0
  33. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/_safearray.py +0 -0
  34. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/clear_cache.py +0 -0
  35. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/__init__.py +0 -0
  36. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/_code_cache.py +0 -0
  37. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/_constants.py +0 -0
  38. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/_events.py +0 -0
  39. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/dynamic.py +0 -0
  40. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/client/lazybind.py +0 -0
  41. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/connectionpoints.py +0 -0
  42. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/errorinfo.py +0 -0
  43. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/git.py +0 -0
  44. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/hresult.py +0 -0
  45. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/logutil.py +0 -0
  46. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/messageloop.py +0 -0
  47. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/patcher.py +0 -0
  48. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/persist.py +0 -0
  49. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/safearray.py +0 -0
  50. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/__init__.py +0 -0
  51. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/automation.py +0 -0
  52. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/connectionpoints.py +0 -0
  53. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/inprocserver.py +0 -0
  54. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/localserver.py +0 -0
  55. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/register.py +0 -0
  56. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/server/w_getopt.py +0 -0
  57. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/shelllink.py +0 -0
  58. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestComServer.idl +0 -0
  59. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestComServer.py +0 -0
  60. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestComServer.tlb +0 -0
  61. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestDispServer.idl +0 -0
  62. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestDispServer.py +0 -0
  63. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/TestDispServer.tlb +0 -0
  64. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/__init__.py +0 -0
  65. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/find_memleak.py +0 -0
  66. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/mylib.idl +0 -0
  67. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/mytypelib.idl +0 -0
  68. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/runtests.py +0 -0
  69. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/setup.py +0 -0
  70. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_BSTR.py +0 -0
  71. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_DISPPARAMS.py +0 -0
  72. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_QueryService.py +0 -0
  73. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_agilent.py +0 -0
  74. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_avmc.py +0 -0
  75. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_basic.py +0 -0
  76. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_casesensitivity.py +0 -0
  77. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_clear_cache.py +0 -0
  78. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_client_dynamic.py +0 -0
  79. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_collections.py +0 -0
  80. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_comserver.py +0 -0
  81. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_createwrappers.py +0 -0
  82. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_dict.py +0 -0
  83. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_dispinterface.py +0 -0
  84. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_dyndispatch.py +0 -0
  85. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_excel.py +0 -0
  86. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_getactiveobj.py +0 -0
  87. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_ie.py +0 -0
  88. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_ienum.py +0 -0
  89. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_imfattributes.py +0 -0
  90. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_inout_args.py +0 -0
  91. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_jscript.js +0 -0
  92. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_msscript.py +0 -0
  93. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_npsupport.py +0 -0
  94. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_outparam.py +0 -0
  95. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_propputref.py +0 -0
  96. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_pump_events.py +0 -0
  97. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_safearray.py +0 -0
  98. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_sapi.py +0 -0
  99. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_server.py +0 -0
  100. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_showevents.py +0 -0
  101. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_subinterface.py +0 -0
  102. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_typeinfo.py +0 -0
  103. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_urlhistory.py +0 -0
  104. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_variant.py +0 -0
  105. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_win32com_interop.py +0 -0
  106. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_wmi.py +0 -0
  107. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/test/test_word.py +0 -0
  108. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/tools/__init__.py +0 -0
  109. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/tools/typedesc_base.py +0 -0
  110. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/typeinfo.py +0 -0
  111. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/util.py +0 -0
  112. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes/viewobject.py +0 -0
  113. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes.egg-info/dependency_links.txt +0 -0
  114. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes.egg-info/entry_points.txt +0 -0
  115. {comtypes-1.4.1 → comtypes-1.4.3}/comtypes.egg-info/top_level.txt +0 -0
  116. {comtypes-1.4.1 → comtypes-1.4.3}/setup.cfg +0 -0
@@ -1,9 +1,32 @@
1
1
  Comtypes CHANGELOG
2
2
  ==================
3
3
 
4
+ Release 1.4.3
5
+ --------------
6
+ * Refactor ``test_findgendir``. By @junkmd.
7
+ * Update ``test_GUID``. By @junkmd.
8
+ * Add a feature to ``typeannotator`` that interprets the return value. By @junkmd.
9
+ * Update type annotations for ``_IUnknown_Base`` and ``IUnknown``. By @junkmd.
10
+ * Add ``groupby_impltypeflags``. By @junkmd.
11
+ * Add static typing for when a ``CoClass`` is assigned as the return value type. By @junkmd.
12
+ * Improve ``EnumerationNamespaces`` and ``CodeGenerator.enum_aliases``. By @junkmd.
13
+ * Modernize constructor arguments and attributes of each stuff in ``typedesc``. By @junkmd.
14
+ * Split ``codegenerator`` into some modules in the sub-package directory. By @junkmd.
15
+ * Implement record pointers as method parameters of a Dispatch Interface. By @geppi.
16
+ * Add ``npsupport`` to CI pipeline matrix. By @junkmd.
17
+ * Add "Frequently Asked Questions" to ``README.md``. By @junkmd.
18
+
19
+ Release 1.4.2
20
+ --------------
21
+ * Improve the stability of module generation by ``GetModule``. By @junkmd.
22
+ * Define ``c_int`` aliases earlier than others in wrapper modules. By @junkmd.
23
+ * Remove trailing newlines generated by ``codegenerator`` and ``typeannotator``. By @junkmd.
24
+ * Determine whether a interface is one of the known symbols not only by its name but also by using its iid. By @junkmd.
25
+ * Add ``Incomplete`` and ``Hresult`` to ``hints.pyi``. By @junkmd.
26
+
4
27
  Release 1.4.1
5
28
  --------------
6
- * Fix `TypeError` when defining enumeration types
29
+ * Fix ``TypeError`` when defining enumeration types. By @junkmd.
7
30
 
8
31
  Release 1.4.0
9
32
  --------------
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: comtypes
3
- Version: 1.4.1
3
+ Version: 1.4.3
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
@@ -75,6 +75,34 @@ This project and the maintainers of thousands of other packages are working with
75
75
 
76
76
  [Learn more](https://tidelift.com/subscription/pkg/pypi-comtypes?utm_source=pypi-comtypes&utm_medium=referral&utm_campaign=github).
77
77
 
78
+ ## Frequently Asked Questions
79
+
80
+ ### Q: Why does this package not support platforms other than Windows?
81
+ **A:** [The Microsoft Component Object Model (COM)](https://learn.microsoft.com/en-us/windows/win32/com/com-technical-overview) is a technology that is unique to Windows and is not supported on other platforms.
82
+
83
+ [The phrase _"COM is a platform-independent"_ in the MS documentation](https://learn.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal) means that COM maintains compatibility across different versions of Windows, and does NOT imply that it is supported on Linux or Mac.
84
+
85
+ As long as COM is not supported outside of Windows, this project does not plan to port to other platforms.
86
+
87
+ ### Q: Why does `cannot import name 'COMError' from '_ctypes'` error occur when using this package on platforms other than Windows?
88
+ **A:** The [`_ctypes`](https://github.com/python/cpython/blob/main/Modules/_ctypes/_ctypes.c) is part of the internal implementation of the [`ctypes`](https://github.com/python/cpython/blob/main/Lib/ctypes/) standard library that exists for Python on all platforms.
89
+ However, `COMError` and COM-related features are implemented only in Python for Windows.
90
+
91
+ In cross-platform software development, care must be taken to ensure that codebase dependent on `comtypes` does not execute in environments other than Windows.
92
+
93
+ ### Q: Despite a script that depends on `comtypes` having run successfully before, a error (`ImportError`, `NameError`, or `SyntaxError`) is raised now, and the same error occurs again and again.
94
+
95
+ **A:** Executing `py -m comtypes.clear_cache` and then running the script again might resolve the problem.
96
+
97
+ When `comtypes.client.GetModule` is called (either directly or indirectly), `comtypes` generates Python module files.
98
+ If Python is forced to terminate or crashes in the middle of file generation, the codebase written to the file becomes partial.
99
+ When Python tries to import this unexecutable partial codebase module, an error occurs.
100
+
101
+ Executing `py -m comtypes.clear_cache` identifies the directories where the "cache module files" are stored and deletes them.
102
+ After deleting these partial modules and running the script again, `comtypes.client.GetModule` is called and executable modules are generated anew.
103
+
104
+ However, if the script implementation does not use `comtypes.client.GetModule` or processes generated files, it may not be a solution.
105
+
78
106
  ## Documentation:
79
107
 
80
108
  The documentation is currently hosted on pythonhosted at:
@@ -56,6 +56,34 @@ This project and the maintainers of thousands of other packages are working with
56
56
 
57
57
  [Learn more](https://tidelift.com/subscription/pkg/pypi-comtypes?utm_source=pypi-comtypes&utm_medium=referral&utm_campaign=github).
58
58
 
59
+ ## Frequently Asked Questions
60
+
61
+ ### Q: Why does this package not support platforms other than Windows?
62
+ **A:** [The Microsoft Component Object Model (COM)](https://learn.microsoft.com/en-us/windows/win32/com/com-technical-overview) is a technology that is unique to Windows and is not supported on other platforms.
63
+
64
+ [The phrase _"COM is a platform-independent"_ in the MS documentation](https://learn.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal) means that COM maintains compatibility across different versions of Windows, and does NOT imply that it is supported on Linux or Mac.
65
+
66
+ As long as COM is not supported outside of Windows, this project does not plan to port to other platforms.
67
+
68
+ ### Q: Why does `cannot import name 'COMError' from '_ctypes'` error occur when using this package on platforms other than Windows?
69
+ **A:** The [`_ctypes`](https://github.com/python/cpython/blob/main/Modules/_ctypes/_ctypes.c) is part of the internal implementation of the [`ctypes`](https://github.com/python/cpython/blob/main/Lib/ctypes/) standard library that exists for Python on all platforms.
70
+ However, `COMError` and COM-related features are implemented only in Python for Windows.
71
+
72
+ In cross-platform software development, care must be taken to ensure that codebase dependent on `comtypes` does not execute in environments other than Windows.
73
+
74
+ ### Q: Despite a script that depends on `comtypes` having run successfully before, a error (`ImportError`, `NameError`, or `SyntaxError`) is raised now, and the same error occurs again and again.
75
+
76
+ **A:** Executing `py -m comtypes.clear_cache` and then running the script again might resolve the problem.
77
+
78
+ When `comtypes.client.GetModule` is called (either directly or indirectly), `comtypes` generates Python module files.
79
+ If Python is forced to terminate or crashes in the middle of file generation, the codebase written to the file becomes partial.
80
+ When Python tries to import this unexecutable partial codebase module, an error occurs.
81
+
82
+ Executing `py -m comtypes.clear_cache` identifies the directories where the "cache module files" are stored and deletes them.
83
+ After deleting these partial modules and running the script again, `comtypes.client.GetModule` is called and executable modules are generated anew.
84
+
85
+ However, if the script implementation does not use `comtypes.client.GetModule` or processes generated files, it may not be a solution.
86
+
59
87
  ## Documentation:
60
88
 
61
89
  The documentation is currently hosted on pythonhosted at:
@@ -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.3"
3
3
 
4
4
  import atexit
5
5
  from ctypes import *
@@ -774,7 +774,7 @@ _T_IUnknown = TypeVar("_T_IUnknown", bound="IUnknown")
774
774
 
775
775
  if TYPE_CHECKING:
776
776
 
777
- class _IUnknown_Base(c_void_p, metaclass=_cominterface_meta):
777
+ class _IUnknown_Base(c_void_p, metaclass=_cominterface_meta): # type: ignore
778
778
  """This is workaround to avoid false-positive of static type checking.
779
779
 
780
780
  `IUnknown` behaves as a ctypes type, and `POINTER` can take it.
@@ -784,9 +784,7 @@ if TYPE_CHECKING:
784
784
  `builtins.object`.
785
785
  """
786
786
 
787
- __com_QueryInterface: Callable[[Any, Any], int]
788
- __com_AddRef: Callable[[], int]
789
- __com_Release: Callable[[], int]
787
+ ...
790
788
 
791
789
  else:
792
790
  _IUnknown_Base = object
@@ -825,7 +823,7 @@ class IUnknown(_IUnknown_Base, metaclass=_cominterface_meta):
825
823
  p = POINTER(interface)()
826
824
  if iid is None:
827
825
  iid = interface._iid_
828
- self.__com_QueryInterface(byref(iid), byref(p))
826
+ self.__com_QueryInterface(byref(iid), byref(p)) # type: ignore
829
827
  clsid = self.__dict__.get("__clsid")
830
828
  if clsid is not None:
831
829
  p.__dict__["__clsid"] = clsid
@@ -835,11 +833,11 @@ class IUnknown(_IUnknown_Base, metaclass=_cominterface_meta):
835
833
  # XXX There should be other ways to install a docstring.
836
834
  def AddRef(self) -> int:
837
835
  """Increase the internal refcount by one and return it."""
838
- return self.__com_AddRef()
836
+ return self.__com_AddRef() # type: ignore
839
837
 
840
838
  def Release(self) -> int:
841
839
  """Decrease the internal refcount by one and return it."""
842
- return self.__com_Release()
840
+ return self.__com_Release() # type: ignore
843
841
 
844
842
 
845
843
  ################################################################
@@ -393,12 +393,36 @@ class tagVARIANT(Structure):
393
393
  ref = value._obj
394
394
  self._.c_void_p = addressof(ref)
395
395
  self.__keepref = value
396
- self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
396
+ if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"):
397
+ guids = ref._recordinfo_
398
+ from comtypes.typeinfo import GetRecordInfoFromGuids
399
+
400
+ ri = GetRecordInfoFromGuids(*guids)
401
+ self.vt = VT_RECORD | VT_BYREF
402
+ # Assigning a COM pointer to a structure field does NOT
403
+ # call AddRef(), have to call it manually:
404
+ ri.AddRef()
405
+ self._.pRecInfo = ri
406
+ self._.pvRecord = cast(value, c_void_p)
407
+ else:
408
+ self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
397
409
  elif isinstance(value, _Pointer):
398
410
  ref = value.contents
399
411
  self._.c_void_p = addressof(ref)
400
412
  self.__keepref = value
401
- self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
413
+ if isinstance(ref, Structure) and hasattr(ref, "_recordinfo_"):
414
+ guids = ref._recordinfo_
415
+ from comtypes.typeinfo import GetRecordInfoFromGuids
416
+
417
+ ri = GetRecordInfoFromGuids(*guids)
418
+ self.vt = VT_RECORD | VT_BYREF
419
+ # Assigning a COM pointer to a structure field does NOT
420
+ # call AddRef(), have to call it manually:
421
+ ri.AddRef()
422
+ self._.pRecInfo = ri
423
+ self._.pvRecord = cast(value, c_void_p)
424
+ else:
425
+ self.vt = _ctype_to_vartype[type(ref)] | VT_BYREF
402
426
  else:
403
427
  raise TypeError("Cannot put %r in VARIANT" % value)
404
428
  # buffer -> SAFEARRAY of VT_UI1 ?
@@ -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
  ################################################################
@@ -21,19 +21,46 @@ if sys.version_info >= (3, 8):
21
21
  from typing import Protocol
22
22
  else:
23
23
  from typing_extensions import Protocol
24
+ if sys.version_info >= (3, 9):
25
+ from typing import Annotated as Annotated
26
+ else:
27
+ from typing_extensions import Annotated as Annotated
24
28
  if sys.version_info >= (3, 10):
25
- from typing import Concatenate, ParamSpec
29
+ from typing import Concatenate, ParamSpec, TypeAlias
30
+ from typing import TypeGuard as TypeGuard
26
31
  else:
27
- from typing_extensions import Concatenate, ParamSpec
32
+ from typing_extensions import Concatenate, ParamSpec, TypeAlias
33
+ from typing_extensions import TypeGuard as TypeGuard
28
34
  if sys.version_info >= (3, 11):
29
35
  from typing import Self
30
36
  else:
31
37
  from typing_extensions import Self
32
38
 
39
+ import comtypes
33
40
  from comtypes.automation import IDispatch as IDispatch, VARIANT as VARIANT
34
41
  from comtypes.server import IClassFactory as IClassFactory
35
42
  from comtypes.typeinfo import ITypeInfo as ITypeInfo
36
43
 
44
+ Incomplete: TypeAlias = Any
45
+ """The type symbol is used temporarily until the COM library parsers or
46
+ code generators are enhanced to annotate detailed type hints.
47
+ """
48
+
49
+ Hresult: TypeAlias = int
50
+ """The value returned when calling a method with no `[out]` or `[out, retval]`
51
+ arguments and with `HRESULT` as its return type in its COM method definition.
52
+ """
53
+
54
+ _T_coclass = TypeVar("_T_coclass", bound=comtypes.CoClass)
55
+
56
+ class FirstComItfOf(Generic[_T_coclass]):
57
+ """When the type assigned to the parameter marked as `'out'` is `CoClass`,
58
+ the return type of that method at runtime becomes `_com_interface_[0]`
59
+ due to the metaclass.
60
+ This is used as `Annotated` metadata for such parameters, taking `CoClass`
61
+ as an argument.
62
+ """
63
+
37
64
  class _MethodTypeDesc(Protocol):
38
65
  arguments: List[Tuple[Any, str, List[str], Optional[Any]]]
39
66
  idlflags: List[str]
@@ -1,34 +1,55 @@
1
- import os
2
1
  import unittest
3
2
  from comtypes import GUID
4
3
 
5
4
 
6
5
  class Test(unittest.TestCase):
7
- def test(self):
6
+ def test_GUID_null(self):
8
7
  self.assertEqual(GUID(), GUID())
8
+ self.assertEqual(
9
+ str(GUID()),
10
+ "{00000000-0000-0000-0000-000000000000}",
11
+ )
12
+
13
+ def test_dunder_eq(self):
9
14
  self.assertEqual(
10
15
  GUID("{00000000-0000-0000-C000-000000000046}"),
11
16
  GUID("{00000000-0000-0000-C000-000000000046}"),
12
17
  )
13
18
 
19
+ def test_duner_str(self):
14
20
  self.assertEqual(
15
21
  str(GUID("{0002DF01-0000-0000-C000-000000000046}")),
16
22
  "{0002DF01-0000-0000-C000-000000000046}",
17
23
  )
24
+
25
+ def test_dunder_repr(self):
18
26
  self.assertEqual(
19
27
  repr(GUID("{0002DF01-0000-0000-C000-000000000046}")),
20
28
  'GUID("{0002DF01-0000-0000-C000-000000000046}")',
21
29
  )
22
30
 
31
+ def test_invalid_constructor_arg(self):
23
32
  self.assertRaises(WindowsError, GUID, "abc")
33
+
34
+ def test_from_progid(self):
35
+ self.assertEqual(
36
+ GUID.from_progid("Scripting.FileSystemObject"),
37
+ GUID("{0D43FE01-F093-11CF-8940-00A0C9054228}"),
38
+ )
24
39
  self.assertRaises(WindowsError, GUID.from_progid, "abc")
25
40
 
41
+ def test_as_progid(self):
42
+ self.assertEqual(
43
+ GUID("{0D43FE01-F093-11CF-8940-00A0C9054228}").as_progid(),
44
+ "Scripting.FileSystemObject",
45
+ )
26
46
  self.assertRaises(
27
47
  WindowsError,
28
48
  lambda guid: guid.as_progid(),
29
49
  GUID("{00000000-0000-0000-C000-000000000046}"),
30
50
  )
31
51
 
52
+ def test_create_new(self):
32
53
  self.assertNotEqual(GUID.create_new(), GUID.create_new())
33
54
 
34
55
 
@@ -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")