enthought / comtypes

A pure Python, lightweight COM client and server framework, based on the ctypes Python FFI package.
Other
283 stars 96 forks source link

If an enumeration type with duplicate members is defined in a COM type library, the generated module will break #550

Open junkmd opened 1 month ago

junkmd commented 1 month ago

This is a re-post of a bug report that was originally posted in the merged PR #525, to prevent it from being overlooked.

Changes to prevent this error have been added in https://github.com/junkmd/comtypes/tree/fix_525_attempted_to_reuse_key_and_more. (This can be installed by pip install https://github.com/junkmd/comtypes/archive/refs/heads/fix_525_attempted_to_reuse_key_and_more.zip) However, verification using an actual COM library has not been conducted.

I do not have a COM library where this error occurs, so I was unable to reproduce the reported situation. I am waiting for reports from a member of the community who can reproduce a similar problem.


          Still an issue with comtypes=1.4.2 installed on python 3.11.09 while trying to access CSI api.
          comtypes=1.3.1 on python 3.11.09 works fine 

          Error shown: 
          TypeError('Attempted to reuse key: %r' % key .........

Originally posted by @coderPE in https://github.com/enthought/comtypes/issues/525#issuecomment-2113395141


          Arguments passed: 
          sap_object = comtypes.client.GetActiveObject("CSI.SAP2000.API.SapObject")
          This is the line that I'm executing, I don't think this has any arguments in it. 
          [gen.zip](https://github.com/enthought/comtypes/files/15328882/gen.zip)

          The gen folder is attached. 

          API link: https://wiki.csiamerica.com/display/kb/OAPI
          The API access is bundled with the software license so I don't believe I can share it. 

          Detailed traceback is below: 
          `---------------------------------------------------------------------------
          TypeError                                 Traceback (most recent call last)
          Cell In[2], line 8
                6 # Check if a running instance of SAP2000 is already open
                7 try:
          ----> 8     sap_object = comtypes.client.GetActiveObject("CSI.SAP2000.API.SapObject")
                9     sap_model = sap_object.SapModel
               10     print("Attached to the running instance of SAP2000.")

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\__init__.py:188, in GetActiveObject(progid, interface, dynamic)
              186 if dynamic:
              187     return comtypes.client.dynamic.Dispatch(obj)
          --> 188 return _manage(obj, clsid, interface=interface)

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\__init__.py:196, in _manage(obj, clsid, interface)
              194 obj.__dict__["__clsid"] = str(clsid)
              195 if interface is None:
          --> 196     obj = GetBestInterface(obj)
              197 return obj

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\__init__.py:121, in GetBestInterface(punk)
              118 tlib = tinfo.GetContainingTypeLib()[0]  # typelib
              120 # import the wrapper, generating it on demand
          --> 121 mod = GetModule(tlib)
              122 # Python interface class
              123 interface = getattr(mod, itf_name)

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\_generate.py:128, in GetModule(tlib)
              126 if mod is not None:
              127     return mod
          --> 128 return ModuleGenerator(tlib, pathname).generate()

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\_generate.py:245, in ModuleGenerator.generate(self)
              243 for ext_tlib in codegen.externals:  # generates dependency COM-lib modules
              244     GetModule(ext_tlib)
          --> 245 return [_create_module(name, code) for (name, code) in codebases][-1]

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\_generate.py:245, in <listcomp>(.0)
              243 for ext_tlib in codegen.externals:  # generates dependency COM-lib modules
              244     GetModule(ext_tlib)
          --> 245 return [_create_module(name, code) for (name, code) in codebases][-1]

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\_generate.py:217, in _create_module(modulename, code)
              215 # clear the import cache to make sure Python sees newly created modules
              216 importlib.invalidate_caches()
          --> 217 return _my_import(modulename)

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\client\_generate.py:28, in _my_import(fullname)
               26 if comtypes.client.gen_dir and comtypes.client.gen_dir not in g.__path__:
               27     g.__path__.append(comtypes.client.gen_dir)  # type: ignore
          ---> 28 return importlib.import_module(fullname)

          File c:\Users\<username>\.conda\envs\py030900\lib\importlib\__init__.py:127, in import_module(name, package)
              125             break
              126         level += 1
          --> 127 return _bootstrap._gcd_import(name[level:], package, level)

          File <frozen importlib._bootstrap>:1030, in _gcd_import(name, package, level)

          File <frozen importlib._bootstrap>:1007, in _find_and_load(name, import_)

          File <frozen importlib._bootstrap>:986, in _find_and_load_unlocked(name, import_)

          File <frozen importlib._bootstrap>:680, in _load_unlocked(spec)

          File <frozen importlib._bootstrap_external>:850, in exec_module(self, module)

          File <frozen importlib._bootstrap>:228, in _call_with_frames_removed(f, *args, **kwds)

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\gen\CSiAPIv1.py:708
              704     eHingeLocationType_OffsetFromIEnd = 2
              705     eHingeLocationType_OffsetFromJEnd = 3
          --> 708 class eHingeDistributionType(IntFlag):
              709     eHingeDistributionType_NonlinearBeamColumn = 1
              710     eHingeDistributionType_DistributedPlasticity = 2

          File c:\Users\<username>\.conda\envs\py030900\lib\site-packages\comtypes\gen\CSiAPIv1.py:712, in eHingeDistributionType()
              710 eHingeDistributionType_DistributedPlasticity = 2
              711 eHingeDistributionType_EqualSpacing = 3
          --> 712 eHingeDistributionType_EqualSpacing = 4
              713 eHingeDistributionType_EqualSpacing = 5

          File c:\Users\<username>\.conda\envs\py030900\lib\enum.py:133, in _EnumDict.__setitem__(self, key, value)
              130         key = '_order_'
              131 elif key in self._member_names:
              132     # descriptor overwriting an enum?
          --> 133     raise TypeError('Attempted to reuse key: %r' % key)
              134 elif key in self._ignore:
              135     pass

          TypeError: Attempted to reuse key: 'eHingeDistributionType_EqualSpacing'`

Originally posted by @coderPE in https://github.com/enthought/comtypes/issues/525#issuecomment-2113884309


          I have checked the `CSiAPIv1.py` (hereinafter referred to as the friendly module) and `_F896D55D_8BDF_4232_B9AB_4B210897A81D_0_1_0.py` (hereinafter referred to as the wrapper module) files you provided.

          Surprisingly, it seems that the type information for `eHingeDistributionType` in the `CSiAPIv1` type library file defines a member with a different value using the same name `eHingeDistributionType_EqualSpacing`.

          ```python
          # In `gen/_F896D55D_8BDF_4232_B9AB_4B210897A81D_0_1_0.py`, L102-L108;
          # values for enumeration 'eHingeDistributionType'
          eHingeDistributionType_NonlinearBeamColumn = 1
          eHingeDistributionType_DistributedPlasticity = 2
          eHingeDistributionType_EqualSpacing = 3
          eHingeDistributionType_EqualSpacing = 4
          eHingeDistributionType_EqualSpacing = 5
          eHingeDistributionType = c_int  # enum
          ```

          ```python
          # In `gen/CSiAPIv1.py`, L708-L713;
          class eHingeDistributionType(IntFlag):
              eHingeDistributionType_NonlinearBeamColumn = 1
              eHingeDistributionType_DistributedPlasticity = 2
              eHingeDistributionType_EqualSpacing = 3
              eHingeDistributionType_EqualSpacing = 4
              eHingeDistributionType_EqualSpacing = 5
          ```

          ## `comtypes` specifications
          `codegenerator` generates code based on the information of the COM type library.
          No error avoidance measures have been taken so far when processing enumeration information with defined duplicate members.
          This is because, from the basic principles of COM and the specifications of enumerations in most languages, it is hard to imagine that the same name member is defined in an enumeration.
          However, since it is actually happening in `CSiAPIv1`, it may be possible to define such an enumeration depending on the language that implemented the behavior of the API, and it may not cause an error when building.

Originally posted by @junkmd in https://github.com/enthought/comtypes/issues/525#issuecomment-2113967341