Skip to content

Define enumerations in generated modules. #345

@junkmd

Description

@junkmd

Motivation

Currently, when we generate a Python module from a COM type library with client.GetModule, the stuffs defined as Enumeration in the COM type library are defined in Python as aliases for ctypes.c_int.

Importing and using them in a production code is rarely done.

They are passed to STDMETHOD, COMMETHOD, DISPMETHOD or DISPPROPERTY to define method and property arguments and return values.
It is difficult to add features such as enumerated type member information to these symbols by using classes inherited from c_int or by using weird tricks regarding __dict__, given the magnitude of impact.

This problem has been left as a comment that marked XXX on the codegenerator, since this package was born.

def Enumeration(self, tp):
self._enumtypes += 1
self.last_item_class = False
if tp.name:
print("# values for enumeration '%s'" % tp.name, file=self.stream)
else:
print("# values for unnamed enumeration", file=self.stream)
# Some enumerations have the same name for the enum type
# and an enum value. Excel's XlDisplayShapes is such an example.
# Since we don't have separate namespaces for the type and the values,
# we generate the TYPE last, overwriting the value. XXX
for item in tp.values:
self.generate(item)
if tp.name:
print("%s = c_int # enum" % tp.name, file=self.stream)
self.names.add(tp.name)

Summary Examples

This feature is implemented by using the same name but making it an alias of c_int in the wrapper module and a subclass of enum.IntFlag in the friendly module.

The .../comtypes/gen/Scripting.py prior to this change looks something like this.

import comtypes.gen._420B2830_E718_11CF_893D_00A0C9054228_0_1_0 as __wrapper_module__
from comtypes.gen._420B2830_E718_11CF_893D_00A0C9054228_0_1_0 import (
    FileAttribute, DriveTypeConst, TristateMixed, IFileSystem,
    SystemFolder, IFileCollection, StandardStreamTypes,
    _check_version, GUID, IFolderCollection, Dictionary, Directory,
    Remote, Library, StdOut, IOMode,
    __MIDL___MIDL_itf_scrrun_0001_0001_0003, WindowsFolder, VARIANT,
    Alias, helpstring, TristateUseDefault, IScriptEncoder, Archive,
    TextStream, IDrive, IFile, HRESULT, Hidden, StdErr, Folders,
    dispid, typelib_path, IFileSystem3, ReadOnly, ForWriting, StdIn,
    FileSystemObject, ForAppending, Encoder, CDRom, IDriveCollection,
    COMMETHOD, Volume, CompareMethod, BinaryCompare, IDictionary,
    BSTR, Normal, TristateTrue, TristateFalse, Fixed, UnknownType,
    Files, File, _lcid, Tristate, SpecialFolderConst, ForReading,
    ITextStream, __MIDL___MIDL_itf_scrrun_0001_0001_0001, Removable,
    RamDisk, VARIANT_BOOL, Drives, Folder, TemporaryFolder, CoClass,
    __MIDL___MIDL_itf_scrrun_0000_0000_0001, System,
    __MIDL___MIDL_itf_scrrun_0001_0001_0002, Compressed, TextCompare,
    DatabaseCompare, IFolder, IUnknown, Drive
)


__all__ = [
    'Encoder', 'FileAttribute', 'CDRom', 'IDriveCollection',
    'DriveTypeConst', 'TristateMixed', 'Volume', 'SystemFolder',
    'IFileSystem', 'IFileCollection', 'CompareMethod',
    'StandardStreamTypes', 'BinaryCompare', 'IDictionary', 'Normal',
    'TristateTrue', 'TristateFalse', 'Fixed', 'IFolderCollection',
    'Dictionary', 'Directory', 'Remote', 'Library', 'StdOut',
    'IOMode', '__MIDL___MIDL_itf_scrrun_0001_0001_0003',
    'UnknownType', 'WindowsFolder', 'Files', 'File', 'Tristate',
    'Alias', 'ForReading', 'SpecialFolderConst', 'ITextStream',
    'TristateUseDefault', 'IScriptEncoder',
    '__MIDL___MIDL_itf_scrrun_0001_0001_0001', 'Archive', 'Removable',
    'RamDisk', 'IDrive', 'TextStream', 'IFile', 'Drives', 'Folder',
    'Hidden', 'StdErr', 'Folders', 'TemporaryFolder', 'typelib_path',
    '__MIDL___MIDL_itf_scrrun_0000_0000_0001', 'ReadOnly', 'System',
    'IFileSystem3', 'ForWriting', 'Compressed',
    '__MIDL___MIDL_itf_scrrun_0001_0001_0002', 'StdIn', 'TextCompare',
    'DatabaseCompare', 'FileSystemObject', 'IFolder', 'Drive',
    'ForAppending'
]

With this change, it looks like this.

from enum import IntFlag

import comtypes.gen._420B2830_E718_11CF_893D_00A0C9054228_0_1_0 as __wrapper_module__
from comtypes.gen._420B2830_E718_11CF_893D_00A0C9054228_0_1_0 import (
    Hidden, Volume, Alias, TextCompare, SystemFolder,
    FileSystemObject, Fixed, typelib_path, IDrive, IFile, IDictionary,
    CoClass, _lcid, ForWriting, RamDisk, StdIn, COMMETHOD, Removable,
    CDRom, Files, TristateUseDefault, Archive, TristateMixed,
    _check_version, WindowsFolder, Remote, DatabaseCompare,
    TextStream, IUnknown, helpstring, HRESULT, IFolderCollection,
    Encoder, ITextStream, Dictionary, Compressed, VARIANT, BSTR,
    IFileCollection, IDriveCollection, Folder, VARIANT_BOOL, Drives,
    Folders, System, StdOut, UnknownType, File, ForAppending, StdErr,
    ReadOnly, BinaryCompare, IFolder, IScriptEncoder, IFileSystem,
    Drive, Normal, Directory, ForReading, TristateTrue, IFileSystem3,
    Library, dispid, TemporaryFolder, TristateFalse, GUID
)


class __MIDL___MIDL_itf_scrrun_0001_0001_0003(IntFlag):
    StdIn = __wrapper_module__.StdIn
    StdOut = __wrapper_module__.StdOut
    StdErr = __wrapper_module__.StdErr


class CompareMethod(IntFlag):
    BinaryCompare = __wrapper_module__.BinaryCompare
    TextCompare = __wrapper_module__.TextCompare
    DatabaseCompare = __wrapper_module__.DatabaseCompare


class __MIDL___MIDL_itf_scrrun_0000_0000_0001(IntFlag):
    Normal = __wrapper_module__.Normal
    ReadOnly = __wrapper_module__.ReadOnly
    Hidden = __wrapper_module__.Hidden
    System = __wrapper_module__.System
    Volume = __wrapper_module__.Volume
    Directory = __wrapper_module__.Directory
    Archive = __wrapper_module__.Archive
    Alias = __wrapper_module__.Alias
    Compressed = __wrapper_module__.Compressed


class __MIDL___MIDL_itf_scrrun_0001_0001_0002(IntFlag):
    WindowsFolder = __wrapper_module__.WindowsFolder
    SystemFolder = __wrapper_module__.SystemFolder
    TemporaryFolder = __wrapper_module__.TemporaryFolder


class __MIDL___MIDL_itf_scrrun_0001_0001_0001(IntFlag):
    UnknownType = __wrapper_module__.UnknownType
    Removable = __wrapper_module__.Removable
    Fixed = __wrapper_module__.Fixed
    Remote = __wrapper_module__.Remote
    CDRom = __wrapper_module__.CDRom
    RamDisk = __wrapper_module__.RamDisk


class IOMode(IntFlag):
    ForReading = __wrapper_module__.ForReading
    ForWriting = __wrapper_module__.ForWriting
    ForAppending = __wrapper_module__.ForAppending


class Tristate(IntFlag):
    TristateTrue = __wrapper_module__.TristateTrue
    TristateFalse = __wrapper_module__.TristateFalse
    TristateUseDefault = __wrapper_module__.TristateUseDefault
    TristateMixed = __wrapper_module__.TristateMixed


StandardStreamTypes = __MIDL___MIDL_itf_scrrun_0001_0001_0003
FileAttribute = __MIDL___MIDL_itf_scrrun_0000_0000_0001
DriveTypeConst = __MIDL___MIDL_itf_scrrun_0001_0001_0001
SpecialFolderConst = __MIDL___MIDL_itf_scrrun_0001_0001_0002


__all__ = [
    'DriveTypeConst', 'FileAttribute', 'Hidden', 'Compressed',
    'SpecialFolderConst', '__MIDL___MIDL_itf_scrrun_0001_0001_0003',
    'StandardStreamTypes', 'Volume',
    '__MIDL___MIDL_itf_scrrun_0001_0001_0001', 'IFileCollection',
    'Alias', 'TextCompare', 'IDriveCollection', 'Folder',
    'SystemFolder', '__MIDL___MIDL_itf_scrrun_0000_0000_0001',
    'IOMode', 'FileSystemObject', 'Drives', 'Fixed', 'Folders',
    'typelib_path', 'IDrive', 'Tristate', 'System', 'StdOut', 'IFile',
    'UnknownType', 'File', 'IDictionary', 'ForAppending',
    'ForWriting', 'StdErr', 'CompareMethod', 'RamDisk', 'ReadOnly',
    'StdIn', '__MIDL___MIDL_itf_scrrun_0001_0001_0002',
    'BinaryCompare', 'Encoder', 'IFolder', 'IScriptEncoder',
    'Removable', 'IFileSystem', 'CDRom', 'Files',
    'TristateUseDefault', 'Drive', 'Archive', 'Normal',
    'TristateMixed', 'Directory', 'WindowsFolder', 'Remote',
    'DatabaseCompare', 'ForReading', 'TristateTrue', 'IFileSystem3',
    'Library', 'TextStream', 'TemporaryFolder', 'TristateFalse',
    'IFolderCollection', 'ITextStream', 'Dictionary'
]

Why IntFlag?

The reasons as to why IntFlag is used for enumerations, because ...

Compatibility with traditional c_int aliases

Codebases like below will no longer work as before with this change because they rely on the symbol is an alias for c_int.

from comtypes.gen.Scripting import Tristate

Tristate.from_param(...)
import ctypes

from comtypes.gen import Scripting

for k, v in vars(Scripting).items():
    if v is ctypes.c_int and k != "c_int":
        ...

There are some ways to use both new and legacy functionalities.
Users can still use the old definitions by changing import and the definitions of module-level symbols as follows;

- from comtypes.gen.Scripting import Tristate
+ import ctypes
+ Tristate = ctypes.c_int
- from comtypes.gen.Scripting import Tristate
+ from comtypes.gen import Scripting
+ Tristate = Scripting.__wrapper_module__.Tristate
- from comtypes.gen import Scripting
+ from comtypes.gen.Scripting import __wrapper_module__ as Scripting
previous descriptions (ideas)

I propose the following procedure as a solution to this problem.

  1. The args passed to functions like STDMETHOD will be changed to ctypes.c_int itself from the symbols defined as Enumeration and External or Alias those referred to Enumeration in the COM type library.

    • Actual code will be below.
    ...
    XlCreator = c_int  # enum
    ...
        DISPMETHOD([dispid(149), 'propget'], XlCreator, 'Creator'),
    ...

    ...
    XlCreator = c_int  # enum
    ...
        DISPMETHOD([dispid(149), 'propget'], c_int, 'Creator'),
    ...
  2. The symbols currently defined as aliases of ctypes.c_int will be changed the definition as enumeration types.

    • The de facto standard for defining enumeration types in Python is the enum module.
      The enum module is supported since Py3.4, so it is not available in Py2.7 or Py3.3.
      Therefore, it will be after older Python versions are dropped that these will actually be changed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions