I'm using win32com to control Visio from Python.
Getting and setting shapesheet values is as easy as:
print(shp.CellsU('PinX').ResultStr(''))
# and
shp.CellsU('PinX').FormulaU = '1'
So far so good, but I want a shorter syntax by overriding the setter and getter to get something like:
print(shp.PinX)
# and
shp.PinX = '1'
So I went for a property:
ShapeClass = type(shp)
def SetPinX(self,value):
self.CellsU('PinX').FormulaU = value
def GetPinX(self):
return self.CellsU('PinX').ResultStr('')
ShapeClass.PinX = property(GetPinX,SetPinX)
Now the strange result - The getter works just fine (print(shp.PinX) give the expected value), but the setter won't work.
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
~\AppData\Local\conda\conda\envs\YG_Package_1\lib\site-packages\win32com\client\__init__.py in __setattr__(self, attr, value)
478 try:
--> 479 args, defArgs=self._prop_map_put_[attr]
480 except KeyError:
KeyError: 'PinX'
During handling of the above exception, another exception occurred:
AttributeError Traceback (most recent call last)
<ipython-input-28-23f68b65624d> in <module>()
----> 1 shp.PinX= '1'
~\AppData\Local\conda\conda\envs\YG_Package_1\lib\site-packages\win32com\client\__init__.py in __setattr__(self, attr, value)
479 args, defArgs=self._prop_map_put_[attr]
480 except KeyError:
--> 481 raise AttributeError("'%s' object has no attribute '%s'" % (repr(self), attr))
482 self._oleobj_.Invoke(*(args + (value,) + defArgs))
483 def _get_good_single_object_(self, obj, obUserName=None, resultCLSID=None):
AttributeError: '<win32com.gen_py.Microsoft Visio 15.0 Type Library.IVShape instance at 0x85710888>' object has no attribute 'PinX'
dir(ShapeClass) shows the attribute PinX just fine.
Testing with an own class also worked. So that the error is not in the way I'm implementing the property.
I suspect win32com to have problems with setters being overridden.
Would anyone have an idea on how to solve the problem?
The win32com.client.DispatchBaseClass
base class uses __setattr__
to intercept all attribute setting access. This affects your property object; property setters only get called by the default object.__setattr__
implementation, not by the custom method used by win32com
.
So yes, shp.PinX = '1'
will call DispatchBaseClass.__setattr__('PinX', '1')
, even if there is a data descriptor called PinX
defined on the class, and that'll fail as that only supports attributes defined by the COM interface.
You'll have to override the __setattr__
method here to check for available properties first. You could either subclass DispatchBaseClass
or specific generated classes, or we could just monkey-patch win32com
directly:
import inspect
from win32com.client import DispatchBaseClass
dispatch_setattr = DispatchBaseClass.__setattr__
def allow_datadescriptor_setattr(self, name, value):
# for non-private names, check if the attribute exists on the class
# and is a data descriptor. If so, use object.__setattr__ to permit
# the data descriptor to intercept attribute setting
if name[:1] != '_' and inspect.isdatadescriptor(getattr(type(self), name, None)):
return object.__setattr__(self, name, value)
# private name, or doesn't exist on the class, or not a data descriptor
# invoke the original win32com dispatch __setattr__
dispatch_setattr(self, name, value)
DispatchBaseClass.__setattr__ = allow_datadescriptor_setattr
The above allows any descriptor with a __set__
or __delete__
method to intercept assignments to their name, not just property
objects.