diff --git a/CHANGES.txt b/CHANGES.txt index cf23ba483..e3e6532aa 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -8,6 +8,9 @@ Note that build 228 was the last version supporting Python 2. Since build 300: ---------------- +* CoClass objects should work better with special methods like __len__ etc. + (#1699) + * Creating a `win32crypt.CRYPT_ATTRIBUTE` object now correctly sets `cbData`. * COM objects are now registered with the full path to pythoncomXX.dll, fixes diff --git a/com/win32com/client/__init__.py b/com/win32com/client/__init__.py index c930fb957..588df6fe4 100644 --- a/com/win32com/client/__init__.py +++ b/com/win32com/client/__init__.py @@ -534,6 +534,23 @@ def __setattr__(self, attr, value): pass self.__dict__[attr] = value + # Special methods don't use __getattr__ etc, so explicitly delegate here. + # Some wrapped objects might not have them, but that's OK - the attribute + # error can just bubble up. + def __call__(self, *args, **kwargs): + return self.__dict__["_dispobj_"].__call__(*args, **kwargs) + def __str__(self, *args): + return self.__dict__["_dispobj_"].__str__(*args) + def __int__(self, *args): + return self.__dict__["_dispobj_"].__int__(*args) + def __iter__(self): + return self.__dict__["_dispobj_"].__iter__() + def __len__(self): + return self.__dict__["_dispobj_"].__len__() + def __nonzero__(self): + return self.__dict__["_dispobj_"].__nonzero__() + + # A very simple VARIANT class. Only to be used with poorly-implemented COM # objects. If an object accepts an arg which is a simple "VARIANT", but still # is very pickly about the actual variant type (eg, isn't happy with a VT_I4, diff --git a/com/win32com/client/genpy.py b/com/win32com/client/genpy.py index 87ebf875c..b82870483 100644 --- a/com/win32com/client/genpy.py +++ b/com/win32com/client/genpy.py @@ -607,7 +607,7 @@ def WriteClass(self, generator): for item, flag in self.interfaces: if flag & pythoncom.IMPLTYPEFLAG_FDEFAULT: # and dual: defItem = item - # If we have written a class, refeence its name, otherwise the IID + # If we have written a class, reference its name, otherwise the IID if item.bWritten: key = item.python_name else: key = repr(str(item.clsid)) # really the iid. print('\t\t%s,' % (key,), file=stream) diff --git a/com/win32com/test/testPyComTest.py b/com/win32com/test/testPyComTest.py index 0c58bacd7..6dbba481e 100644 --- a/com/win32com/test/testPyComTest.py +++ b/com/win32com/test/testPyComTest.py @@ -172,10 +172,17 @@ def TestCommon(o, is_generated): progress("Checking getting/passing IUnknown") check_get_set(o.GetSetUnknown, o) progress("Checking getting/passing IDispatch") - if not isinstance(o.GetSetDispatch(o), o.__class__): + # This might be called with either the interface or the CoClass - but these + # functions always return from the interface. + expected_class = o.__class__ + # CoClass instances have `default_interface` + expected_class = getattr(expected_class, "default_interface", expected_class) + if not isinstance(o.GetSetDispatch(o), expected_class): raise error("GetSetDispatch failed: %r" % (o.GetSetDispatch(o),)) progress("Checking getting/passing IDispatch of known type") - if o.GetSetInterface(o).__class__ != o.__class__: + expected_class = o.__class__ + expected_class = getattr(expected_class, "default_interface", expected_class) + if o.GetSetInterface(o).__class__ != expected_class: raise error("GetSetDispatch failed") progress("Checking misc args") @@ -408,6 +415,16 @@ def TestGenerated(): counter = EnsureDispatch("PyCOMTest.SimpleCounter") TestCounter(counter, True) + # This dance lets us get a CoClass even though it's not explicitly registered. + # This is `CoPyComTest` + from win32com.client.CLSIDToClass import GetClass + coclass_o = GetClass("{8EE0C520-5605-11D0-AE5F-CADD4C000000}")() + TestCommon(coclass_o, True) + + # This is `CoSimpleCounter` and the counter tests should work. + coclass = GetClass("{B88DD310-BAE8-11D0-AE86-76F2C1000000}")() + TestCounter(coclass, True) + # XXX - this is failing in dynamic tests, but should work fine. i1, i2 = o.GetMultipleInterfaces() if not isinstance(i1, DispatchBaseClass) or not isinstance(i2, DispatchBaseClass):