dynamic.py 21.2 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
"""Support for dynamic COM client support.

Introduction
 Dynamic COM client support is the ability to use a COM server without
 prior knowledge of the server.  This can be used to talk to almost all
 COM servers, including much of MS Office.
 
 In general, you should not use this module directly - see below.
 
Example
 >>> import win32com.client
 >>> xl = win32com.client.Dispatch("Excel.Application")
 # The line above invokes the functionality of this class.
 # xl is now an object we can use to talk to Excel.
 >>> xl.Visible = 1 # The Excel window becomes visible.

"""
import sys
import traceback
import types

import pythoncom
import winerror
import build

from pywintypes import IIDType

import win32com.client # Needed as code we eval() references it.

debugging=0			# General debugging
debugging_attr=0	# Debugging dynamic attribute lookups.

LCID = 0x0

# These errors generally mean the property or method exists,
# but can't be used in this context - eg, property instead of a method, etc.
# Used to determine if we have a real error or not.
ERRORS_BAD_CONTEXT = [
	winerror.DISP_E_MEMBERNOTFOUND,
	winerror.DISP_E_BADPARAMCOUNT,
	winerror.DISP_E_PARAMNOTOPTIONAL,
	winerror.DISP_E_TYPEMISMATCH,
	winerror.E_INVALIDARG,
]

ALL_INVOKE_TYPES = [
	pythoncom.INVOKE_PROPERTYGET,
	pythoncom.INVOKE_PROPERTYPUT,
	pythoncom.INVOKE_PROPERTYPUTREF,
	pythoncom.INVOKE_FUNC
]

def debug_print(*args):
	if debugging:
		for arg in args:
			print arg,
		print

def debug_attr_print(*args):
	if debugging_attr:
		for arg in args:
			print arg,
		print

# A helper to create method objects on the fly
py3k = sys.version_info > (3,0)
if py3k:
	def MakeMethod(func, inst, cls):
		return types.MethodType(func, inst) # class not needed in py3k
else:
	MakeMethod = types.MethodType # all args used in py2k.

# get the type objects for IDispatch and IUnknown
PyIDispatchType = pythoncom.TypeIIDs[pythoncom.IID_IDispatch]
PyIUnknownType = pythoncom.TypeIIDs[pythoncom.IID_IUnknown]

if py3k:
	_GoodDispatchTypes=(str, IIDType)
else:
	_GoodDispatchTypes=(str, IIDType, unicode)
_defaultDispatchItem=build.DispatchItem

def _GetGoodDispatch(IDispatch, clsctx = pythoncom.CLSCTX_SERVER):
	# quick return for most common case
	if isinstance(IDispatch, PyIDispatchType):
		return IDispatch
	if isinstance(IDispatch, _GoodDispatchTypes):
		try:
			IDispatch = pythoncom.connect(IDispatch)
		except pythoncom.ole_error:
			IDispatch = pythoncom.CoCreateInstance(IDispatch, None, clsctx, pythoncom.IID_IDispatch)
	else:
		# may already be a wrapped class.
		IDispatch = getattr(IDispatch, "_oleobj_", IDispatch)
	return IDispatch

def _GetGoodDispatchAndUserName(IDispatch, userName, clsctx):
	# Get a dispatch object, and a 'user name' (ie, the name as
	# displayed to the user in repr() etc.
	if userName is None:
		# Displayed name should be a plain string in py2k, and unicode in py3k
		if isinstance(IDispatch, str):
			userName = IDispatch
		elif not py3k and isinstance(IDispatch, unicode):
			# 2to3 converts the above 'unicode' to 'str', but this will never be executed in py3k
			userName = IDispatch.encode("ascii", "replace")
		## ??? else userName remains None ???
	elif not py3k and isinstance(userName, unicode):
		# 2to3 converts the above 'unicode' to 'str', but this will never be executed in py3k
		# As above - always a plain string in py2k
		userName = userName.encode("ascii", "replace")
	else:
		userName = str(userName)
	return (_GetGoodDispatch(IDispatch, clsctx), userName)

def _GetDescInvokeType(entry, default_invoke_type):
	if not entry or not entry.desc: return default_invoke_type
	return entry.desc[4]

def Dispatch(IDispatch, userName = None, createClass = None, typeinfo = None, UnicodeToString=None, clsctx = pythoncom.CLSCTX_SERVER):
	assert UnicodeToString is None, "this is deprecated and will go away"
	IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
	if createClass is None:
		createClass = CDispatch
	lazydata = None
	try:
		if typeinfo is None:
			typeinfo = IDispatch.GetTypeInfo()
		if typeinfo is not None:
			try:
				#try for a typecomp
				typecomp = typeinfo.GetTypeComp()
				lazydata = typeinfo, typecomp
			except pythoncom.com_error:
				pass
	except pythoncom.com_error:
		typeinfo = None
	olerepr = MakeOleRepr(IDispatch, typeinfo, lazydata)
	return createClass(IDispatch, olerepr, userName, lazydata=lazydata)

def MakeOleRepr(IDispatch, typeinfo, typecomp):
	olerepr = None
	if typeinfo is not None:
		try:
			attr = typeinfo.GetTypeAttr()
			# If the type info is a special DUAL interface, magically turn it into
			# a DISPATCH typeinfo.
			if attr[5] == pythoncom.TKIND_INTERFACE and attr[11] & pythoncom.TYPEFLAG_FDUAL:
				# Get corresponding Disp interface;
				# -1 is a special value which does this for us.
				href = typeinfo.GetRefTypeOfImplType(-1);
				typeinfo = typeinfo.GetRefTypeInfo(href)
				attr = typeinfo.GetTypeAttr()
			if typecomp is None:
				olerepr = build.DispatchItem(typeinfo, attr, None, 0)
			else:
				olerepr = build.LazyDispatchItem(attr, None)
		except pythoncom.ole_error:
			pass
	if olerepr is None: olerepr = build.DispatchItem()
	return olerepr

def DumbDispatch(IDispatch, userName = None, createClass = None,UnicodeToString=None, clsctx=pythoncom.CLSCTX_SERVER):
	"Dispatch with no type info"
	assert UnicodeToString is None, "this is deprecated and will go away"
	IDispatch, userName = _GetGoodDispatchAndUserName(IDispatch,userName,clsctx)
	if createClass is None:
		createClass = CDispatch
	return createClass(IDispatch, build.DispatchItem(), userName)

class CDispatch:
	def __init__(self, IDispatch, olerepr, userName=None, UnicodeToString=None, lazydata=None):
		assert UnicodeToString is None, "this is deprecated and will go away"
		if userName is None: userName = "<unknown>"
		self.__dict__['_oleobj_'] = IDispatch
		self.__dict__['_username_'] = userName
		self.__dict__['_olerepr_'] = olerepr
		self.__dict__['_mapCachedItems_'] = {}
		self.__dict__['_builtMethods_'] = {}
		self.__dict__['_enum_'] = None
		self.__dict__['_unicode_to_string_'] = None
		self.__dict__['_lazydata_'] = lazydata

	def __call__(self, *args):
		"Provide 'default dispatch' COM functionality - allow instance to be called"
		if self._olerepr_.defaultDispatchName:
			invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
		else:
			invkind, dispid = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, pythoncom.DISPID_VALUE
		if invkind is not None:
			allArgs = (dispid,LCID,invkind,1) + args
			return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
		raise TypeError("This dispatch object does not define a default method")

	def __nonzero__(self):
		return True # ie "if object:" should always be "true" - without this, __len__ is tried.
		# _Possibly_ want to defer to __len__ if available, but Im not sure this is
		# desirable???

	def __repr__(self):
		return "<COMObject %s>" % (self._username_)

	def __str__(self):
		# __str__ is used when the user does "print object", so we gracefully
		# fall back to the __repr__ if the object has no default method.
		try:
			return str(self.__call__())
		except pythoncom.com_error, details:
			if details.hresult not in ERRORS_BAD_CONTEXT:
				raise
			return self.__repr__()

	# Delegate comparison to the oleobjs, as they know how to do identity.
	def __eq__(self, other):
		other = getattr(other, "_oleobj_", other)
		return self._oleobj_ == other

	def __ne__(self, other):
		other = getattr(other, "_oleobj_", other)
		return self._oleobj_ != other

	def __int__(self):
		return int(self.__call__())

	def __len__(self):
		invkind, dispid = self._find_dispatch_type_("Count")
		if invkind:
			return self._oleobj_.Invoke(dispid, LCID, invkind, 1)
		raise TypeError("This dispatch object does not define a Count method")

	def _NewEnum(self):
		try:
			invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
			enum = self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,(13, 10),())
		except pythoncom.com_error:
			return None # no enumerator for this object.
		import util
		return util.WrapEnum(enum, None)

	def __getitem__(self, index): # syver modified
		# Improved __getitem__ courtesy Syver Enstad
		# Must check _NewEnum before Item, to ensure b/w compat.
		if isinstance(index, int):
			if self.__dict__['_enum_'] is None:
				self.__dict__['_enum_'] = self._NewEnum()
			if self.__dict__['_enum_'] is not None:
				return self._get_good_object_(self._enum_.__getitem__(index))
		# See if we have an "Item" method/property we can use (goes hand in hand with Count() above!)
		invkind, dispid = self._find_dispatch_type_("Item")
		if invkind is not None:
			return self._get_good_object_(self._oleobj_.Invoke(dispid, LCID, invkind, 1, index))
		raise TypeError("This object does not support enumeration")

	def __setitem__(self, index, *args):
		# XXX - todo - We should support calling Item() here too!
#		print "__setitem__ with", index, args
		if self._olerepr_.defaultDispatchName:
			invkind, dispid = self._find_dispatch_type_(self._olerepr_.defaultDispatchName)
		else:
			invkind, dispid = pythoncom.DISPATCH_PROPERTYPUT | pythoncom.DISPATCH_PROPERTYPUTREF, pythoncom.DISPID_VALUE
		if invkind is not None:
			allArgs = (dispid,LCID,invkind,0,index) + args
			return self._get_good_object_(self._oleobj_.Invoke(*allArgs),self._olerepr_.defaultDispatchName,None)
		raise TypeError("This dispatch object does not define a default method")

	def _find_dispatch_type_(self, methodName):
		if methodName in self._olerepr_.mapFuncs:
			item = self._olerepr_.mapFuncs[methodName]
			return item.desc[4], item.dispid

		if methodName in self._olerepr_.propMapGet:
			item = self._olerepr_.propMapGet[methodName]
			return item.desc[4], item.dispid

		try:
			dispid = self._oleobj_.GetIDsOfNames(0,methodName)
		except:	### what error?
			return None, None
		return pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET, dispid

	def _ApplyTypes_(self, dispid, wFlags, retType, argTypes, user, resultCLSID, *args):
		result = self._oleobj_.InvokeTypes(*(dispid, LCID, wFlags, retType, argTypes) + args)
		return self._get_good_object_(result, user, resultCLSID)

	def _wrap_dispatch_(self, ob, userName = None, returnCLSID = None, UnicodeToString=None):
		# Given a dispatch object, wrap it in a class
		assert UnicodeToString is None, "this is deprecated and will go away"
		return Dispatch(ob, userName)

	def _get_good_single_object_(self,ob,userName = None, ReturnCLSID=None):
		if isinstance(ob, PyIDispatchType):
			# make a new instance of (probably this) class.
			return self._wrap_dispatch_(ob, userName, ReturnCLSID)
		if isinstance(ob, PyIUnknownType):
			try:
				ob = ob.QueryInterface(pythoncom.IID_IDispatch)
			except pythoncom.com_error:
				# It is an IUnknown, but not an IDispatch, so just let it through.
				return ob
			return self._wrap_dispatch_(ob, userName, ReturnCLSID)
		return ob
		
	def _get_good_object_(self,ob,userName = None, ReturnCLSID=None):
		"""Given an object (usually the retval from a method), make it a good object to return.
		   Basically checks if it is a COM object, and wraps it up.
		   Also handles the fact that a retval may be a tuple of retvals"""
		if ob is None: # Quick exit!
			return None
		elif isinstance(ob, tuple):
			return tuple(map(lambda o, s=self, oun=userName, rc=ReturnCLSID: s._get_good_single_object_(o, oun, rc),  ob))
		else:
			return self._get_good_single_object_(ob)

	def _make_method_(self, name):
		"Make a method object - Assumes in olerepr funcmap"
		methodName = build.MakePublicAttributeName(name) # translate keywords etc.
		methodCodeList = self._olerepr_.MakeFuncMethod(self._olerepr_.mapFuncs[name], methodName,0)
		methodCode = "\n".join(methodCodeList)
		try:
#			print "Method code for %s is:\n" % self._username_, methodCode
#			self._print_details_()
			codeObject = compile(methodCode, "<COMObject %s>" % self._username_,"exec")
			# Exec the code object
			tempNameSpace = {}
			# "Dispatch" in the exec'd code is win32com.client.Dispatch, not ours.
			globNameSpace = globals().copy()
			globNameSpace["Dispatch"] = win32com.client.Dispatch
			exec codeObject in globNameSpace, tempNameSpace # self.__dict__, self.__dict__
			name = methodName
			# Save the function in map.
			fn = self._builtMethods_[name] = tempNameSpace[name]
			newMeth = MakeMethod(fn, self, self.__class__)
			return newMeth
		except:
			debug_print("Error building OLE definition for code ", methodCode)
			traceback.print_exc()
		return None
		
	def _Release_(self):
		"""Cleanup object - like a close - to force cleanup when you dont 
		   want to rely on Python's reference counting."""
		for childCont in self._mapCachedItems_.itervalues():
			childCont._Release_()
		self._mapCachedItems_ = {}
		if self._oleobj_:
			self._oleobj_.Release()
			self.__dict__['_oleobj_'] = None
		if self._olerepr_:
			self.__dict__['_olerepr_'] = None
		self._enum_ = None

	def _proc_(self, name, *args):
		"""Call the named method as a procedure, rather than function.
		   Mainly used by Word.Basic, which whinges about such things."""
		try:
			item = self._olerepr_.mapFuncs[name]
			dispId = item.dispid
			return self._get_good_object_(self._oleobj_.Invoke(*(dispId, LCID, item.desc[4], 0) + (args) ))
		except KeyError:
			raise AttributeError(name)
		
	def _print_details_(self):
		"Debug routine - dumps what it knows about an object."
		print "AxDispatch container",self._username_
		try:
			print "Methods:"
			for method in self._olerepr_.mapFuncs.iterkeys():
				print "\t", method
			print "Props:"
			for prop, entry in self._olerepr_.propMap.iteritems():
				print "\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry))
			print "Get Props:"
			for prop, entry in self._olerepr_.propMapGet.iteritems():
				print "\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry))
			print "Put Props:"
			for prop, entry in self._olerepr_.propMapPut.iteritems():
				print "\t%s = 0x%x - %s" % (prop, entry.dispid, repr(entry))
		except:
			traceback.print_exc()

	def __LazyMap__(self, attr):
		try:
			if self._LazyAddAttr_(attr):
				debug_attr_print("%s.__LazyMap__(%s) added something" % (self._username_,attr))
				return 1
		except AttributeError:
			return 0

	# Using the typecomp, lazily create a new attribute definition.
	def _LazyAddAttr_(self,attr):
		if self._lazydata_ is None: return 0
		res = 0
		typeinfo, typecomp = self._lazydata_
		olerepr = self._olerepr_
		# We need to explicitly check each invoke type individually - simply
		# specifying '0' will bind to "any member", which may not be the one
		# we are actually after (ie, we may be after prop_get, but returned
		# the info for the prop_put.)
		for i in ALL_INVOKE_TYPES:
			try:
				x,t = typecomp.Bind(attr,i)
				# Support 'Get' and 'Set' properties - see
				# bug 1587023
				if x==0 and attr[:3] in ('Set', 'Get'):
					x,t = typecomp.Bind(attr[3:], i)
				if x==1:	#it's a FUNCDESC
					r = olerepr._AddFunc_(typeinfo,t,0)
				elif x==2:	#it's a VARDESC
					r = olerepr._AddVar_(typeinfo,t,0)
				else:		#not found or TYPEDESC/IMPLICITAPP
					r=None
				if not r is None:
					key, map = r[0],r[1]
					item = map[key]
					if map==olerepr.propMapPut:
						olerepr._propMapPutCheck_(key,item)
					elif map==olerepr.propMapGet:
						olerepr._propMapGetCheck_(key,item)
					res = 1
			except:
				pass
		return res

	def _FlagAsMethod(self, *methodNames):
		"""Flag these attribute names as being methods.
		Some objects do not correctly differentiate methods and
		properties, leading to problems when calling these methods.

		Specifically, trying to say: ob.SomeFunc()
		may yield an exception "None object is not callable"
		In this case, an attempt to fetch the *property*has worked
		and returned None, rather than indicating it is really a method.
		Calling: ob._FlagAsMethod("SomeFunc")
		should then allow this to work.
		"""
		for name in methodNames:
			details = build.MapEntry(self.__AttrToID__(name), (name,))
			self._olerepr_.mapFuncs[name] = details

	def __AttrToID__(self,attr):
			debug_attr_print("Calling GetIDsOfNames for property %s in Dispatch container %s" % (attr, self._username_))
			return self._oleobj_.GetIDsOfNames(0,attr)

	def __getattr__(self, attr):
		if attr=='__iter__':
			# We can't handle this as a normal method, as if the attribute
			# exists, then it must return an iterable object.
			try:
				invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET
				enum = self._oleobj_.InvokeTypes(pythoncom.DISPID_NEWENUM,LCID,invkind,(13, 10),())
			except pythoncom.com_error:
				raise AttributeError("This object can not function as an iterator")
			# We must return a callable object.
			class Factory:
				def __init__(self, ob):
					self.ob = ob
				def __call__(self):
					import win32com.client.util
					return win32com.client.util.Iterator(self.ob)
			return Factory(enum)
			
		if attr.startswith('_') and attr.endswith('_'): # Fast-track.
			raise AttributeError(attr)
		# If a known method, create new instance and return.
		try:
			return MakeMethod(self._builtMethods_[attr], self, self.__class__)
		except KeyError:
			pass
		# XXX - Note that we current are case sensitive in the method.
		#debug_attr_print("GetAttr called for %s on DispatchContainer %s" % (attr,self._username_))
		# First check if it is in the method map.  Note that an actual method
		# must not yet exist, (otherwise we would not be here).  This
		# means we create the actual method object - which also means
		# this code will never be asked for that method name again.
		if attr in self._olerepr_.mapFuncs:
			return self._make_method_(attr)

		# Delegate to property maps/cached items
		retEntry = None
		if self._olerepr_ and self._oleobj_:
			# first check general property map, then specific "put" map.
			retEntry = self._olerepr_.propMap.get(attr)
			if retEntry is None:
				retEntry = self._olerepr_.propMapGet.get(attr)
			# Not found so far - See what COM says.
			if retEntry is None:
				try:
					if self.__LazyMap__(attr):
						if attr in self._olerepr_.mapFuncs: return self._make_method_(attr)
						retEntry = self._olerepr_.propMap.get(attr)
						if retEntry is None:
							retEntry = self._olerepr_.propMapGet.get(attr)
					if retEntry is None:
						retEntry = build.MapEntry(self.__AttrToID__(attr), (attr,))
				except pythoncom.ole_error:
					pass # No prop by that name - retEntry remains None.

		if not retEntry is None: # see if in my cache
			try:
				ret = self._mapCachedItems_[retEntry.dispid]
				debug_attr_print ("Cached items has attribute!", ret)
				return ret
			except (KeyError, AttributeError):
				debug_attr_print("Attribute %s not in cache" % attr)

		# If we are still here, and have a retEntry, get the OLE item
		if not retEntry is None:
			invoke_type = _GetDescInvokeType(retEntry, pythoncom.INVOKE_PROPERTYGET)
			debug_attr_print("Getting property Id 0x%x from OLE object" % retEntry.dispid)
			try:
				ret = self._oleobj_.Invoke(retEntry.dispid,0,invoke_type,1)
			except pythoncom.com_error, details:
				if details.hresult in ERRORS_BAD_CONTEXT:
					# May be a method.
					self._olerepr_.mapFuncs[attr] = retEntry
					return self._make_method_(attr)
				raise
			debug_attr_print("OLE returned ", ret)
			return self._get_good_object_(ret)

		# no where else to look.
		raise AttributeError("%s.%s" % (self._username_, attr))

	def __setattr__(self, attr, value):
		if attr in self.__dict__: # Fast-track - if already in our dict, just make the assignment.
			# XXX - should maybe check method map - if someone assigns to a method,
			# it could mean something special (not sure what, tho!)
			self.__dict__[attr] = value
			return
		# Allow property assignment.
		debug_attr_print("SetAttr called for %s.%s=%s on DispatchContainer" % (self._username_, attr, repr(value)))

		if self._olerepr_:
			# Check the "general" property map.
			if attr in self._olerepr_.propMap:
				entry = self._olerepr_.propMap[attr]
				invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
				self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
				return
			# Check the specific "put" map.
			if attr in self._olerepr_.propMapPut:
				entry = self._olerepr_.propMapPut[attr]
				invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
				self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
				return

		# Try the OLE Object
		if self._oleobj_:
			if self.__LazyMap__(attr):
				# Check the "general" property map.
				if attr in self._olerepr_.propMap:
					entry = self._olerepr_.propMap[attr]
					invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
					self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
					return
				# Check the specific "put" map.
				if attr in self._olerepr_.propMapPut:
					entry = self._olerepr_.propMapPut[attr]
					invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
					self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
					return
			try:
				entry = build.MapEntry(self.__AttrToID__(attr),(attr,))
			except pythoncom.com_error:
				# No attribute of that name
				entry = None
			if entry is not None:
				try:
					invoke_type = _GetDescInvokeType(entry, pythoncom.INVOKE_PROPERTYPUT)
					self._oleobj_.Invoke(entry.dispid, 0, invoke_type, 0, value)
					self._olerepr_.propMap[attr] = entry
					debug_attr_print("__setattr__ property %s (id=0x%x) in Dispatch container %s" % (attr, entry.dispid, self._username_))
					return
				except pythoncom.com_error:
					pass
		raise AttributeError("Property '%s.%s' can not be set." % (self._username_, attr))