Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 55 additions & 19 deletions comtypes/test/test_dict.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
"""Use Scripting.Dictionary to test the lazybind module."""
"""Use Scripting.Dictionary to test the lazybind and the generated modules."""

import unittest

from comtypes import typeinfo
from comtypes.automation import VARIANT
from comtypes.client import CreateObject
from comtypes.client import CreateObject, GetModule
from comtypes.client.lazybind import Dispatch

GetModule("scrrun.dll")
import comtypes.gen.Scripting as scrrun


class Test(unittest.TestCase):
def test_dict(self):
def test_dynamic(self):
d = CreateObject("Scripting.Dictionary", dynamic=True)
self.assertEqual(type(d), Dispatch)

# Count is a normal propget, no propput
self.assertEqual(d.Count, 0)
with self.assertRaises(AttributeError):
setattr(d, "Count", -1)
d.Count = -1

# HashVal is a 'named' propget, no propput
# HashVal is a 'hidden' member and used internally.
##d.HashVal

# Add(Key, Item) -> None
Expand All @@ -30,10 +35,11 @@ def test_dict(self):

# CompareMode: propget, propput
# (Can only be set when dict is empty!)
self.assertEqual(d.CompareMode, 0)
d.CompareMode = 1
self.assertEqual(d.CompareMode, 1)
d.CompareMode = 0
# Verify that the default is BinaryCompare.
self.assertEqual(d.CompareMode, scrrun.BinaryCompare)
d.CompareMode = scrrun.TextCompare
self.assertEqual(d.CompareMode, scrrun.TextCompare)
d.CompareMode = scrrun.BinaryCompare

# Exists(key) -> bool
self.assertEqual(d.Exists(42), False)
Expand Down Expand Up @@ -66,32 +72,29 @@ def test_dict(self):
# part 2, testing propput and propputref

s = CreateObject("Scripting.Dictionary", dynamic=True)
s.CompareMode = 42
s.CompareMode = scrrun.DatabaseCompare

# This calls propputref, since we assign an Object
d.Item["object"] = s
# This calls propput, since we assing a Value
# This calls propput, since we assign a Value
d.Item["value"] = s.CompareMode

a = d.Item["object"]

self.assertEqual(d.Item["object"], s)
self.assertEqual(d.Item["object"].CompareMode, 42)
self.assertEqual(d.Item["value"], 42)
self.assertEqual(d.Item["object"].CompareMode, scrrun.DatabaseCompare)
self.assertEqual(d.Item["value"], scrrun.DatabaseCompare)

# Changing a property of the object
s.CompareMode = 5
s.CompareMode = scrrun.BinaryCompare
self.assertEqual(d.Item["object"], s)
self.assertEqual(d.Item["object"].CompareMode, 5)
self.assertEqual(d.Item["value"], 42)
self.assertEqual(d.Item["object"].CompareMode, scrrun.BinaryCompare)
self.assertEqual(d.Item["value"], scrrun.DatabaseCompare)

# This also calls propputref since we assign an Object
d.Item["var"] = VARIANT(s)
self.assertEqual(d.Item["var"], s)

# iter(d)
keys = [x for x in d]
self.assertEqual(d.Keys(), tuple([x for x in d]))
self.assertEqual(d.Keys(), tuple(x for x in d))

# d[key] = value
# d[key] -> value
Expand All @@ -100,6 +103,39 @@ def test_dict(self):
# d(key) -> value
self.assertEqual(d("blah"), "blarg")

def test_static(self):
d = CreateObject(scrrun.Dictionary, interface=scrrun.IDictionary)
# This confirms that the Dictionary is a dual interface.
ti = d.GetTypeInfo(0)
self.assertTrue(ti.GetTypeAttr().wTypeFlags & typeinfo.TYPEFLAG_FDUAL)
# Count is a normal propget, no propput
self.assertEqual(d.Count, 0)
with self.assertRaises(AttributeError):
d.Count = -1 # type: ignore
# Dual interfaces call COM methods that support named arguments.
d.Add("spam", "foo")
d.Add("egg", Item="bar")
self.assertEqual(d.Count, 2)
d.Add(Key="ham", Item="baz")
self.assertEqual(len(d), 3)
d.Add(Item="qux", Key="toast")
d.Item["beans"] = "quux"
d["bacon"] = "corge"
self.assertEqual(d("spam"), "foo")
self.assertEqual(d.Item["egg"], "bar")
self.assertEqual(d["ham"], "baz")
self.assertEqual(d("toast"), "qux")
self.assertEqual(d.Item("beans"), "quux")
self.assertEqual(d("bacon"), "corge")
# NOTE: Named parameters are not yet implemented for the named property.
# See https://github.com/enthought/comtypes/issues/371
# TODO: After named parameters are supported, this will become a test to
# assert the return value.
with self.assertRaises(TypeError):
d.Item(Key="spam")
with self.assertRaises(TypeError):
d(Key="egg")


if __name__ == "__main__":
unittest.main()
95 changes: 95 additions & 0 deletions comtypes/test/test_msi.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import unittest as ut
import winreg

import comtypes.client
from comtypes import GUID, typeinfo
from comtypes.automation import IDispatch

MSI_TLIB = typeinfo.LoadTypeLibEx("msi.dll")
comtypes.client.GetModule(MSI_TLIB)
import comtypes.gen.WindowsInstaller as msi

HKCR = 0 # HKEY_CLASSES_ROOT
HKCU = 1 # HKEY_CURRENT_USER


class Test_Installer(ut.TestCase):
def test_registry_value_with_root_key_value(self):
# `WindowsInstaller.Installer` provides access to Windows configuration.
inst = comtypes.client.CreateObject(
"WindowsInstaller.Installer", interface=msi.Installer
)
# Both methods below get the "Programmatic Identifier" used to handle
# ".txt" files.
with winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, ".txt") as key:
progid, _ = winreg.QueryValueEx(key, "")
# This confirms that the Installer can correctly read system information.
self.assertEqual(progid, inst.RegistryValue(HKCR, ".txt", ""))

def test_registry_value_with_root_key(self):
inst = comtypes.client.CreateObject(
"WindowsInstaller.Installer", interface=msi.Installer
)
# If the third arg is missing, `Installer.RegistryValue` returns a Boolean
# designating whether the key exists.
# https://learn.microsoft.com/en-us/windows/win32/msi/installer-registryvalue
# The `HKEY_CURRENT_USER\\Control Panel\\Desktop` registry key is a standard
# registry key that exists across all versions of the Windows.
self.assertTrue(inst.RegistryValue(HKCU, r"Control Panel\Desktop"))
# Since a single backslash is reserved as a path separator and cannot be used
# in a key name itself. Therefore, such a key exists in no version of Windows.
self.assertFalse(inst.RegistryValue(HKCU, "\\"))

def test_registry_value_with_named_params(self):
inst = comtypes.client.CreateObject(
"WindowsInstaller.Installer", interface=msi.Installer
)
IID_Installer = msi.Installer._iid_
# This confirms that the Installer is a pure dispatch interface.
self.assertIsInstance(inst, IDispatch)
ti = MSI_TLIB.GetTypeInfoOfGuid(IID_Installer)
ta = ti.GetTypeAttr()
self.assertEqual(IID_Installer, ta.guid)
self.assertFalse(ta.wTypeFlags & typeinfo.TYPEFLAG_FDUAL)
# NOTE: Named parameters are not yet implemented for the dispmethod called
# via the `Invoke` method.
# See https://github.com/enthought/comtypes/issues/371
# As a safeguard until implementation is complete, an error will be raised
# if named arguments are passed to prevent invalid calls.
# TODO: After named parameters are supported, this will become a test to
# assert the return value.
ERRMSG = "named parameters not yet implemented"
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(Root=HKCR, Key=".txt", Value="") # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(Value="", Root=HKCR, Key=".txt") # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(HKCR, Key=".txt", Value="") # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(HKCR, ".txt", Value="") # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(Root=HKCU, Key=r"Control Panel\Desktop") # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(Key=r"Control Panel\Desktop", Root=HKCR) # type: ignore
with self.assertRaises(ValueError, msg=ERRMSG):
inst.RegistryValue(HKCR, Key=r"Control Panel\Desktop") # type: ignore

def test_product_state(self):
inst = comtypes.client.CreateObject(
"WindowsInstaller.Installer", interface=msi.Installer
)
# There is no product associated with the Null GUID.
pdcode = str(GUID())
expected = msi.MsiInstallState.msiInstallStateUnknown
self.assertEqual(expected, inst.ProductState(pdcode))
self.assertEqual(expected, inst.ProductState[pdcode])
# The `ProductState` property is a read-only property.
# https://learn.microsoft.com/en-us/windows/win32/msi/installer-productstate-property
with self.assertRaises(TypeError):
inst.ProductState[pdcode] = msi.MsiInstallState.msiInstallStateDefault # type: ignore
# NOTE: Named parameters are not yet implemented for the named property.
# See https://github.com/enthought/comtypes/issues/371
# TODO: After named parameters are supported, this will become a test to
# assert the return value.
with self.assertRaises(TypeError):
inst.ProductState(Product=pdcode) # type: ignore
28 changes: 20 additions & 8 deletions comtypes/test/test_typeannotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,18 @@ def _create_typedesc_disp_interface(self) -> typedesc.DispInterface:
put_def = typedesc.DispMethod(8, 4, "def", void_type, ["propput"], None)
put_def.add_argument(VARIANT_type, "arg1", ["in", "optional"], None)
put_def.add_argument(VARIANT_type, "arg2", ["in"], None)
for m in [ham, bacon, get_spam, put_spam, except_, raise_, get_def, put_def]:
egg = typedesc.DispMethod(643, 1, "egg", VARIANT_BOOL_type, [], None)
for m in [
ham,
bacon,
get_spam,
put_spam,
except_,
raise_,
get_def,
put_def,
egg,
]:
itf.add_member(m)
return itf

Expand All @@ -59,14 +70,15 @@ def test_disp_interface(self):
" def ham(self) -> hints.Incomplete: ...\n"
" pass # @property # dispprop\n"
" pass # avoid using a keyword for def except(self) -> hints.Incomplete: ...\n" # noqa
" def bacon(self, *args: hints.Any, **kwargs: hints.Any) -> hints.Incomplete: ...\n" # noqa
" def _get_spam(self, arg1: hints.Incomplete = ...) -> hints.Incomplete: ...\n" # noqa
" def _set_spam(self, arg1: hints.Incomplete = ..., **kwargs: hints.Any) -> hints.Incomplete: ...\n" # noqa
" def bacon(self, *args: hints.Any, **kwargs: hints.Any, /) -> hints.Incomplete: ...\n" # noqa
" def _get_spam(self, arg1: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa
" def _set_spam(self, arg1: hints.Incomplete = ..., **kwargs: hints.Any, /) -> hints.Incomplete: ...\n" # noqa
" spam = hints.named_property('spam', _get_spam, _set_spam)\n"
" pass # avoid using a keyword for def raise(self, foo: hints.Incomplete, bar: hints.Incomplete = ...) -> hints.Incomplete: ...\n" # noqa
" def _get_def(self, arg1: hints.Incomplete = ...) -> hints.Incomplete: ...\n" # noqa
" def _set_def(self, arg1: hints.Incomplete = ..., **kwargs: hints.Any) -> hints.Incomplete: ...\n" # noqa
" pass # avoid using a keyword for def = hints.named_property('def', _get_def, _set_def)" # noqa
" pass # avoid using a keyword for def raise(self, foo: hints.Incomplete, bar: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa
" def _get_def(self, arg1: hints.Incomplete = ..., /) -> hints.Incomplete: ...\n" # noqa
" def _set_def(self, arg1: hints.Incomplete = ..., **kwargs: hints.Any, /) -> hints.Incomplete: ...\n" # noqa
" pass # avoid using a keyword for def = hints.named_property('def', _get_def, _set_def)\n" # noqa
" def egg(self) -> hints.Incomplete: ..." # noqa
)
self.assertEqual(
expected, typeannotator.DispInterfaceMembersAnnotator(itf).generate()
Expand Down
12 changes: 10 additions & 2 deletions comtypes/tools/codegenerator/typeannotator.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,16 @@ def getvalue(self, name: str) -> str:
inargs.append(f"{argname}: hints.Incomplete = ...")
has_optional = True
out = _to_outtype(self.method.returns)
in_ = ("self, " + ", ".join(inargs)) if inargs else "self"
content = f"def {name}({in_}) -> {out}: ..."
# NOTE: Since named parameters are not yet implemented, all arguments
# for the dispmethod (called via `Invoke`) are marked as positional-only
# parameters, introduced in PEP570. See also `automation.IDispatch.Invoke`.
# See https://github.com/enthought/comtypes/issues/371
# TODO: After named parameters are supported, the positional-only parameter
# markers will be removed.
if inargs:
content = f"def {name}(self, {', '.join(inargs)}, /) -> {out}: ..."
else:
content = f"def {name}(self) -> {out}: ..."
if keyword.iskeyword(name):
content = f"pass # avoid using a keyword for {content}"
return content
Expand Down