codekingpro/portable-devtools
114k
1"""Support for dynamic COM client support.2 3Introduction4 Dynamic COM client support is the ability to use a COM server without5 prior knowledge of the server. This can be used to talk to almost all6 COM servers, including much of MS Office.7 8 In general, you should not use this module directly - see below.9 10Example11 >>> import win32com.client12 >>> xl = win32com.client.Dispatch("Excel.Application")13 # The line above invokes the functionality of this class.14 # xl is now an object we can use to talk to Excel.15 >>> xl.Visible = 1 # The Excel window becomes visible.16 17"""18import traceback19import types20 21import pythoncom # Needed as code we eval() references it.22import win32com.client23import winerror24from pywintypes import IIDType25 26from . import build27 28debugging = 0 # General debugging29debugging_attr = 0 # Debugging dynamic attribute lookups.30 31LCID = 0x032 33# These errors generally mean the property or method exists,34# but can't be used in this context - eg, property instead of a method, etc.35# Used to determine if we have a real error or not.36ERRORS_BAD_CONTEXT = [37 winerror.DISP_E_MEMBERNOTFOUND,38 winerror.DISP_E_BADPARAMCOUNT,39 winerror.DISP_E_PARAMNOTOPTIONAL,40 winerror.DISP_E_TYPEMISMATCH,41 winerror.E_INVALIDARG,42]43 44ALL_INVOKE_TYPES = [45 pythoncom.INVOKE_PROPERTYGET,46 pythoncom.INVOKE_PROPERTYPUT,47 pythoncom.INVOKE_PROPERTYPUTREF,48 pythoncom.INVOKE_FUNC,49]50 51 52def debug_print(*args):53 if debugging:54 for arg in args:55 print(arg, end=" ")56 print()57 58 59def debug_attr_print(*args):60 if debugging_attr:61 for arg in args:62 print(arg, end=" ")63 print()64 65 66def MakeMethod(func, inst, cls):67 return types.MethodType(func, inst)68 69 70# get the type objects for IDispatch and IUnknown71PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]72PyIUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]73 74_GoodDispatchTypes = (str, IIDType)75_defaultDispatchItem = build.DispatchItem76 77 78def _GetGoodDispatch(IDispatch, clsctx=pythoncom.CLSCTX_SERVER):79 # quick return for most common case80 if isinstance(IDispatch, PyIDispatchType):81 return IDispatch82 if isinstance(IDispatch, _GoodDispatchTypes):83 try:84 IDispatch = pythoncom.connect(IDispatch)85 except pythoncom.ole_error:86 IDispatch = pythoncom.CoCreateInstance(87 IDispatch, None, clsctx, pythoncom.IID_IDispatch88 )89 else:90 # may already be a wrapped class.91 IDispatch = getattr(IDispatch, "_oleobj_", IDispatch)92 return IDispatch93 94 95def _GetGoodDispatchAndUserName(IDispatch, userName, clsctx):96 # Get a dispatch object, and a 'user name' (ie, the name as97 # displayed to the user in repr() etc.98 if userName is None:99 if isinstance(IDispatch, str):100 userName = IDispatch101 ## ??? else userName remains None ???102 else:103 userName = str(userName)104 return (_GetGoodDispatch(IDispatch, clsctx), userName)105 106 107def _GetDescInvokeType(entry, invoke_type):108 # determine the wFlags argument passed as input to IDispatch::Invoke109 # Only ever called by __getattr__ and __setattr__ from dynamic objects!110 # * `entry` is a MapEntry with whatever typeinfo we have about the property we are getting/setting.111 # * `invoke_type` is either INVOKE_PROPERTYGET | INVOKE_PROPERTYSET and really just112 # means "called by __getattr__" or "called by __setattr__"113 if not entry or not entry.desc:114 return invoke_type115 116 if entry.desc.desckind == pythoncom.DESCKIND_VARDESC:117 return invoke_type118 119 # So it's a FUNCDESC - just use what it specifies.120 return entry.desc.invkind121 122 123def Dispatch(124 IDispatch,125 userName=None,126 createClass=None,127 typeinfo=None,128 UnicodeToString=None,129 clsctx=pythoncom.CLSCTX_SERVER,130):131 assert UnicodeToString is None, "this is deprecated and will go away"132 IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx)133 if createClass is None:134 createClass = CDispatch135 lazydata = None136 try:137 if typeinfo is None:138 typeinfo = IDispatch.GetTypeInfo()139 if typeinfo is not None:140 try:141 # try for a typecomp142 typecomp = typeinfo.GetTypeComp()143 lazydata = typeinfo, typecomp144 except pythoncom.com_error:145 pass146 except pythoncom.com_error:147 typeinfo = None148 olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata)149 return createClass(IDispatch, olerepr, userName, lazydata=lazydata)150 151 152def MakeOleRepr(IDispatch, typeinfo, typecomp):153 olerepr = None154 if typeinfo is not None:155 try:156 attr = typeinfo.GetTypeAttr()157 # If the type info is a special DUAL interface, magically turn it into158 # a DISPATCH typeinfo.159 if (160 attr[5] == pythoncom.TKIND_INTERFACE161 and attr[11] & pythoncom.TYPEFLAG_FDUAL162 ):163 # Get corresponding Disp interface;164 # -1 is a special value which does this for us.165 href = typeinfo.GetRefTypeOfImplType(-1)166 typeinfo = typeinfo.GetRefTypeInfo(href)167 attr = typeinfo.GetTypeAttr()168 if typecomp is None:169 olerepr = build.DispatchItem(typeinfo, attr, None, 0)170 else:171 olerepr = build.LazyDispatchItem(attr, None)172 except pythoncom.ole_error:173 pass174 if olerepr is None:175 olerepr = build.DispatchItem()176 return olerepr177 178 179def DumbDispatch(180 IDispatch,181 userName=None,182 createClass=None,183 UnicodeToString=None,184 clsctx=pythoncom.CLSCTX_SERVER,185):186 "Dispatch with no type info"187 assert UnicodeToString is None, "this is deprecated and will go away"188 IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch, userName, clsctx)189 if createClass is None:190 createClass = CDispatch191 return createClass(IDispatch, build.DispatchItem(), userName)192 193 194class CDispatch:195 def __init__(196 self, IDispatch, olerepr, userName=None, UnicodeToString=None, lazydata=None197 ):198 assert UnicodeToString is None, "this is deprecated and will go away"199 if userName is None:200 userName = "<unknown>"201 self.__dict__["_oleobj_"] = IDispatch202 self.__dict__["_username_"] = userName203 self.__dict__["_olerepr_"] = olerepr204 self.__dict__["_mapCachedItems_"] = {}205 self.__dict__["_builtMethods_"] = {}206 self.__dict__["_enum_"] = None207 self.__dict__["_unicode_to_string_"] = None208 self.__dict__["_lazydata_"] = lazydata209 210 def __call__(self, *args):211 "Provide 'default dispatch' COM functionality - allow instance to be called"212 if self._olerepr_.defaultDispatchName:213 invkind, dispid = self._find_dispatch_type_(214 self._olerepr_.defaultDispatchName215 )216 else:217 invkind, dispid = (218 pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET,219 pythoncom.DISPID_VALUE,220 )221 if invkind is not None:222 allArgs = (dispid, LCID, invkind, 1) + args223 return self._get_good_object_(224 self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None225 )226 raise TypeError("This dispatch object does not define a default method")227 228 def __bool__(self):229 return True # ie "if object:" should always be "true" - without this, __len__ is tried.230 # _Possibly_ want to defer to __len__ if available, but Im not sure this is231 # desirable???232 233 def __repr__(self):234 return "<COMObject %s>" % (self._username_)235 236 def __str__(self):237 # __str__ is used when the user does "print object", so we gracefully238 # fall back to the __repr__ if the object has no default method.239 try:240 return str(self.__call__())241 except pythoncom.com_error as details:242 if details.hresult not in ERRORS_BAD_CONTEXT:243 raise244 return self.__repr__()245 246 def __dir__(self):247 lst = list(self.__dict__.keys()) + dir(self.__class__) + self._dir_ole_()248 try:249 lst += [p.Name for p in self.Properties_]250 except AttributeError:251 pass252 return list(set(lst))253 254 def _dir_ole_(self):255 items_dict = {}256 for iTI in range(0, self._oleobj_.GetTypeInfoCount()):257 typeInfo = self._oleobj_.GetTypeInfo(iTI)258 self._UpdateWithITypeInfo_(items_dict, typeInfo)259 return list(items_dict.keys())260 261 def _UpdateWithITypeInfo_(self, items_dict, typeInfo):262 typeInfos = [typeInfo]263 # suppress IDispatch and IUnknown methods264 inspectedIIDs = {pythoncom.IID_IDispatch: None}265 266 while len(typeInfos) > 0:267 typeInfo = typeInfos.pop()268 typeAttr = typeInfo.GetTypeAttr()269 270 if typeAttr.iid not in inspectedIIDs:271 inspectedIIDs[typeAttr.iid] = None272 for iFun in range(0, typeAttr.cFuncs):273 funDesc = typeInfo.GetFuncDesc(iFun)274 funName = typeInfo.GetNames(funDesc.memid)[0]275 if funName not in items_dict:276 items_dict[funName] = None277 278 # Inspect the type info of all implemented types279 # E.g. IShellDispatch5 implements IShellDispatch4 which implements IShellDispatch3 ...280 for iImplType in range(0, typeAttr.cImplTypes):281 iRefType = typeInfo.GetRefTypeOfImplType(iImplType)282 refTypeInfo = typeInfo.GetRefTypeInfo(iRefType)283 typeInfos.append(refTypeInfo)284 285 # Delegate comparison to the oleobjs, as they know how to do identity.286 def __eq__(self, other):287 other = getattr(other, "_oleobj_", other)288 return self._oleobj_ == other289 290 def __ne__(self, other):291 other = getattr(other, "_oleobj_", other)292 return self._oleobj_ != other293 294 def __int__(self):295 return int(self.__call__())296 297 def __len__(self):298 invkind, dispid = self._find_dispatch_type_("Count")299 if invkind:300 return self._oleobj_.Invoke(dispid, LCID, invkind, 1)301 raise TypeError("This dispatch object does not define a Count method")302 303 def _NewEnum(self):304 try:305 invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET306 enum = self._oleobj_.InvokeTypes(307 pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), ()308 )309 except pythoncom.com_error:310 return None # no enumerator for this object.311 from . import util312 313 return util.WrapEnum(enum, None)314 315 def __getitem__(self, index): # syver modified316 # Improved __getitem__ courtesy Syver Enstad317 # Must check _NewEnum before Item, to ensure b/w compat.318 if isinstance(index, int):319 if self.__dict__["_enum_"] is None:320 self.__dict__["_enum_"] = self._NewEnum()321 if self.__dict__["_enum_"] is not None:322 return self._get_good_object_(self._enum_.__getitem__(index))323 # See if we have an "Item" method/property we can use (goes hand in hand with Count() above!)324 invkind, dispid = self._find_dispatch_type_("Item")325 if invkind is not None:326 return self._get_good_object_(327 self._oleobj_.Invoke(dispid, LCID, invkind, 1, index)328 )329 raise TypeError("This object does not support enumeration")330 331 def __setitem__(self, index, *args):332 # XXX - todo - We should support calling Item() here too!333 # print "__setitem__ with", index, args334 if self._olerepr_.defaultDispatchName:335 invkind, dispid = self._find_dispatch_type_(336 self._olerepr_.defaultDispatchName337 )338 else:339 invkind, dispid = (340 pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF,341 pythoncom.DISPID_VALUE,342 )343 if invkind is not None:344 allArgs = (dispid, LCID, invkind, 0, index) + args345 return self._get_good_object_(346 self._oleobj_.Invoke(*allArgs), self._olerepr_.defaultDispatchName, None347 )348 raise TypeError("This dispatch object does not define a default method")349 350 def _find_dispatch_type_(self, methodName):351 if methodName in self._olerepr_.mapFuncs:352 item = self._olerepr_.mapFuncs[methodName]353 return item.desc[4], item.dispid354 355 if methodName in self._olerepr_.propMapGet:356 item = self._olerepr_.propMapGet[methodName]357 return item.desc[4], item.dispid358 359 try:360 dispid = self._oleobj_.GetIDsOfNames(0, methodName)361 except: ### what error?362 return None, None363 return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid364 365 def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):366 result = self._oleobj_.InvokeTypes(367 *(dispid, LCID, wFlags, retType, argTypes) + args368 )369 return self._get_good_object_(result, user, resultCLSID)370 371 def _wrap_dispatch_(372 self, ob, userName=None, returnCLSID=None, UnicodeToString=None373 ):374 # Given a dispatch object, wrap it in a class375 assert UnicodeToString is None, "this is deprecated and will go away"376 return Dispatch(ob, userName)377 378 def _get_good_single_object_(self, ob, userName=None, ReturnCLSID=None):379 if isinstance(ob, PyIDispatchType):380 # make a new instance of (probably this) class.381 return self._wrap_dispatch_(ob, userName, ReturnCLSID)382 if isinstance(ob, PyIUnknownType):383 try:384 ob = ob.QueryInterface(pythoncom.IID_IDispatch)385 except pythoncom.com_error:386 # It is an IUnknown, but not an IDispatch, so just let it through.387 return ob388 return self._wrap_dispatch_(ob, userName, ReturnCLSID)389 return ob390 391 def _get_good_object_(self, ob, userName=None, ReturnCLSID=None):392 """Given an object (usually the retval from a method), make it a good object to return.393 Basically checks if it is a COM object, and wraps it up.394 Also handles the fact that a retval may be a tuple of retvals"""395 if ob is None: # Quick exit!396 return None397 elif isinstance(ob, tuple):398 return tuple(399 map(400 lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(401 o, oun, rc402 ),403 ob,404 )405 )406 else:407 return self._get_good_single_object_(ob)408 409 def _make_method_(self, name):410 "Make a method object - Assumes in olerepr funcmap"411 methodName = build.MakePublicAttributeName(name) # translate keywords etc.412 methodCodeList = self._olerepr_.MakeFuncMethod(413 self._olerepr_.mapFuncs[name], methodName, 0414 )415 methodCode = "\n".join(methodCodeList)416 try:417 # print "Method code for %s is:\n" % self._username_, methodCode418 # self._print_details_()419 codeObject = compile(methodCode, "<COMObject %s>" % self._username_, "exec")420 # Exec the code object421 tempNameSpace = {}422 # "Dispatch" in the exec'd code is win32com.client.Dispatch, not ours.423 globNameSpace = globals().copy()424 globNameSpace["Dispatch"] = win32com.client.Dispatch425 exec(426 codeObject, globNameSpace, tempNameSpace427 ) # self.__dict__, self.__dict__428 name = methodName429 # Save the function in map.430 fn = self._builtMethods_[name] = tempNameSpace[name]431 newMeth = MakeMethod(fn, self, self.__class__)432 return newMeth433 except:434 debug_print("Error building OLE definition for code ", methodCode)435 traceback.print_exc()436 return None437 438 def _Release_(self):439 """Cleanup object - like a close - to force cleanup when you dont440 want to rely on Python's reference counting."""441 for childCont in self._mapCachedItems_.values():442 childCont._Release_()443 self._mapCachedItems_ = {}444 if self._oleobj_:445 self._oleobj_.Release()446 self.__dict__["_oleobj_"] = None447 if self._olerepr_:448 self.__dict__["_olerepr_"] = None449 self._enum_ = None450 451 def _proc_(self, name, *args):452 """Call the named method as a procedure, rather than function.453 Mainly used by Word.Basic, which whinges about such things."""454 try:455 item = self._olerepr_.mapFuncs[name]456 dispId = item.dispid457 return self._get_good_object_(458 self._oleobj_.Invoke(*(dispId, LCID, item.desc[4], 0) + (args))459 )460 except KeyError:461 raise AttributeError(name)462 463 def _print_details_(self):464 "Debug routine - dumps what it knows about an object."465 print("AxDispatch container", self._username_)466 try:467 print("Methods:")468 for method in self._olerepr_.mapFuncs.keys():469 print("\t", method)470 print("Props:")471 for prop, entry in self._olerepr_.propMap.items():472 print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))473 print("Get Props:")474 for prop, entry in self._olerepr_.propMapGet.items():475 print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))476 print("Put Props:")477 for prop, entry in self._olerepr_.propMapPut.items():478 print("\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry)))479 except:480 traceback.print_exc()481 482 def __LazyMap__(self, attr):483 try:484 if self._LazyAddAttr_(attr):485 debug_attr_print(486 "%s.__LazyMap__(%s) added something" % (self._username_, attr)487 )488 return 1489 except AttributeError:490 return 0491 492 # Using the typecomp, lazily create a new attribute definition.493 def _LazyAddAttr_(self, attr):494 if self._lazydata_ is None:495 return 0496 res = 0497 typeinfo, typecomp = self._lazydata_498 olerepr = self._olerepr_499 # We need to explicitly check each invoke type individually - simply500 # specifying '0' will bind to "any member", which may not be the one501 # we are actually after (ie, we may be after prop_get, but returned502 # the info for the prop_put.)503 for i in ALL_INVOKE_TYPES:504 try:505 x, t = typecomp.Bind(attr, i)506 # Support 'Get' and 'Set' properties - see507 # bug 1587023508 if x == 0 and attr[:3] in ("Set", "Get"):509 x, t = typecomp.Bind(attr[3:], i)510 if x == pythoncom.DESCKIND_FUNCDESC: # it's a FUNCDESC511 r = olerepr._AddFunc_(typeinfo, t, 0)512 elif x == pythoncom.DESCKIND_VARDESC: # it's a VARDESC513 r = olerepr._AddVar_(typeinfo, t, 0)514 else: # not found or TYPEDESC/IMPLICITAPP515 r = None516 if not r is None:517 key, map = r[0], r[1]518 item = map[key]519 if map == olerepr.propMapPut:520 olerepr._propMapPutCheck_(key, item)521 elif map == olerepr.propMapGet:522 olerepr._propMapGetCheck_(key, item)523 res = 1524 except:525 pass526 return res527 528 def _FlagAsMethod(self, *methodNames):529 """Flag these attribute names as being methods.530 Some objects do not correctly differentiate methods and531 properties, leading to problems when calling these methods.532 533 Specifically, trying to say: ob.SomeFunc()534 may yield an exception "None object is not callable"535 In this case, an attempt to fetch the *property* has worked536 and returned None, rather than indicating it is really a method.537 Calling: ob._FlagAsMethod("SomeFunc")538 should then allow this to work.539 """540 for name in methodNames:541 details = build.MapEntry(self.__AttrToID__(name), (name,))542 self._olerepr_.mapFuncs[name] = details543 544 def __AttrToID__(self, attr):545 debug_attr_print(546 "Calling GetIDsOfNames for property %s in Dispatch container %s"547 % (attr, self._username_)548 )549 return self._oleobj_.GetIDsOfNames(0, attr)550 551 def __getattr__(self, attr):552 if attr == "__iter__":553 # We can't handle this as a normal method, as if the attribute554 # exists, then it must return an iterable object.555 try:556 invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET557 enum = self._oleobj_.InvokeTypes(558 pythoncom.DISPID_NEWENUM, LCID, invkind, (13, 10), ()559 )560 except pythoncom.com_error:561 raise AttributeError("This object can not function as an iterator")562 563 # We must return a callable object.564 class Factory:565 def __init__(self, ob):566 self.ob = ob567 568 def __call__(self):569 import win32com.client.util570 571 return win32com.client.util.Iterator(self.ob)572 573 return Factory(enum)574 575 if attr.startswith("_") and attr.endswith("_"): # Fast-track.576 raise AttributeError(attr)577 # If a known method, create new instance and return.578 try:579 return MakeMethod(self._builtMethods_[attr], self, self.__class__)580 except KeyError:581 pass582 # XXX - Note that we current are case sensitive in the method.583 # debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_))584 # First check if it is in the method map. Note that an actual method585 # must not yet exist, (otherwise we would not be here). This586 # means we create the actual method object - which also means587 # this code will never be asked for that method name again.588 if attr in self._olerepr_.mapFuncs:589 return self._make_method_(attr)590 591 # Delegate to property maps/cached items592 retEntry = None593 if self._olerepr_ and self._oleobj_:594 # first check general property map, then specific "put" map.595 retEntry = self._olerepr_.propMap.get(attr)596 if retEntry is None:597 retEntry = self._olerepr_.propMapGet.get(attr)598 # Not found so far - See what COM says.599 if retEntry is None:600 try:601 if self.__LazyMap__(attr):602 if attr in self._olerepr_.mapFuncs:603 return self._make_method_(attr)604 retEntry = self._olerepr_.propMap.get(attr)605 if retEntry is None:606 retEntry = self._olerepr_.propMapGet.get(attr)607 if retEntry is None:608 retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))609 except pythoncom.ole_error:610 pass # No prop by that name - retEntry remains None.611 612 if retEntry is not None: # see if in my cache613 try:614 ret = self._mapCachedItems_[retEntry.dispid]615 debug_attr_print("Cached items has attribute!", ret)616 return ret617 except (KeyError, AttributeError):618 debug_attr_print("Attribute %s not in cache" % attr)619 620 # If we are still here, and have a retEntry, get the OLE item621 if retEntry is not None:622 invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET)623 debug_attr_print(624 "Getting property Id 0x%x from OLE object" % retEntry.dispid625 )626 try:627 ret = self._oleobj_.Invoke(retEntry.dispid, 0, invoke_type, 1)628 except pythoncom.com_error as details:629 if details.hresult in ERRORS_BAD_CONTEXT:630 # May be a method.631 self._olerepr_.mapFuncs[attr] = retEntry632 return self._make_method_(attr)633 raise634 debug_attr_print("OLE returned ", ret)635 return self._get_good_object_(ret)636 637 # no where else to look.638 raise AttributeError("%s.%s" % (self._username_, attr))639 640 def __setattr__(self, attr, value):641 if (642 attr in self.__dict__643 ): # Fast-track - if already in our dict, just make the assignment.644 # XXX - should maybe check method map - if someone assigns to a method,645 # it could mean something special (not sure what, tho!)646 self.__dict__[attr] = value647 return648 # Allow property assignment.649 debug_attr_print(650 "SetAttr called for %s.%s=%s on DispatchContainer"651 % (self._username_, attr, repr(value))652 )653 654 if self._olerepr_:655 # Check the "general" property map.656 if attr in self._olerepr_.propMap:657 entry = self._olerepr_.propMap[attr]658 invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)659 self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)660 return661 # Check the specific "put" map.662 if attr in self._olerepr_.propMapPut:663 entry = self._olerepr_.propMapPut[attr]664 invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)665 self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)666 return667 668 # Try the OLE Object669 if self._oleobj_:670 if self.__LazyMap__(attr):671 # Check the "general" property map.672 if attr in self._olerepr_.propMap:673 entry = self._olerepr_.propMap[attr]674 invoke_type = _GetDescInvokeType(675 entry, pythoncom.INVOKE_PROPERTYPUT676 )677 self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)678 return679 # Check the specific "put" map.680 if attr in self._olerepr_.propMapPut:681 entry = self._olerepr_.propMapPut[attr]682 invoke_type = _GetDescInvokeType(683 entry, pythoncom.INVOKE_PROPERTYPUT684 )685 self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)686 return687 try:688 entry = build.MapEntry(self.__AttrToID__(attr), (attr,))689 except pythoncom.com_error:690 # No attribute of that name691 entry = None692 if entry is not None:693 try:694 invoke_type = _GetDescInvokeType(695 entry, pythoncom.INVOKE_PROPERTYPUT696 )697 self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)698 self._olerepr_.propMap[attr] = entry699 debug_attr_print(700 "__setattr__ property %s (id=0x%x) in Dispatch container %s"701 % (attr, entry.dispid, self._username_)702 )703 return704 except pythoncom.com_error:705 pass706 raise AttributeError(707 "Property '%s.%s' can not be set." % (self._username_, attr)708 )709 