codekingpro/portable-devtools
114k
1"""genpy.py - The worker for makepy. See makepy.py for more details2 3This code was moved simply to speed Python in normal circumstances. As the makepy.py4is normally run from the command line, it reparses the code each time. Now makepy5is nothing more than the command line handler and public interface.6 7The makepy command line etc handling is also getting large enough in its own right!8"""9 10# NOTE - now supports a "demand" mechanism - the top-level is a package, and11# each class etc can be made individually.12# This should eventually become the default.13# Then the old non-package technique should be removed.14# There should be no b/w compat issues, and will just help clean the code.15# This will be done once the new "demand" mechanism gets a good workout.16import os17import sys18import time19 20import pythoncom21import win32com22 23from . import build24 25error = "makepy.error"26makepy_version = "0.5.01" # Written to generated file.27 28GEN_FULL = "full"29GEN_DEMAND_BASE = "demand(base)"30GEN_DEMAND_CHILD = "demand(child)"31 32# This map is used purely for the users benefit -it shows the33# raw, underlying type of Alias/Enums, etc. The COM implementation34# does not use this map at runtime - all Alias/Enum have already35# been translated.36mapVTToTypeString = {37 pythoncom.VT_I2: "types.IntType",38 pythoncom.VT_I4: "types.IntType",39 pythoncom.VT_R4: "types.FloatType",40 pythoncom.VT_R8: "types.FloatType",41 pythoncom.VT_BSTR: "types.StringType",42 pythoncom.VT_BOOL: "types.IntType",43 pythoncom.VT_VARIANT: "types.TypeType",44 pythoncom.VT_I1: "types.IntType",45 pythoncom.VT_UI1: "types.IntType",46 pythoncom.VT_UI2: "types.IntType",47 pythoncom.VT_UI4: "types.IntType",48 pythoncom.VT_I8: "types.LongType",49 pythoncom.VT_UI8: "types.LongType",50 pythoncom.VT_INT: "types.IntType",51 pythoncom.VT_DATE: "pythoncom.PyTimeType",52 pythoncom.VT_UINT: "types.IntType",53}54 55 56# Given a propget function's arg desc, return the default parameters for all57# params bar the first. Eg, then Python does a:58# object.Property = "foo"59# Python can only pass the "foo" value. If the property has60# multiple args, and the rest have default values, this allows61# Python to correctly pass those defaults.62def MakeDefaultArgsForPropertyPut(argsDesc):63 ret = []64 for desc in argsDesc[1:]:65 default = build.MakeDefaultArgRepr(desc)66 if default is None:67 break68 ret.append(default)69 return tuple(ret)70 71 72def MakeMapLineEntry(dispid, wFlags, retType, argTypes, user, resultCLSID):73 # Strip the default value74 argTypes = tuple([what[:2] for what in argTypes])75 return '(%s, %d, %s, %s, "%s", %s)' % (76 dispid,77 wFlags,78 retType[:2],79 argTypes,80 user,81 resultCLSID,82 )83 84 85def MakeEventMethodName(eventName):86 if eventName[:2] == "On":87 return eventName88 else:89 return "On" + eventName90 91 92def WriteSinkEventMap(obj, stream):93 print("\t_dispid_to_func_ = {", file=stream)94 for name, entry in (95 list(obj.propMapGet.items())96 + list(obj.propMapPut.items())97 + list(obj.mapFuncs.items())98 ):99 fdesc = entry.desc100 print(101 '\t\t%9d : "%s",' % (fdesc.memid, MakeEventMethodName(entry.names[0])),102 file=stream,103 )104 print("\t\t}", file=stream)105 106 107# MI is used to join my writable helpers, and the OLE108# classes.109class WritableItem:110 # __cmp__ used for sorting in py2x...111 def __cmp__(self, other):112 "Compare for sorting"113 ret = cmp(self.order, other.order)114 if ret == 0 and self.doc:115 ret = cmp(self.doc[0], other.doc[0])116 return ret117 118 # ... but not used in py3k - __lt__ minimum needed there119 def __lt__(self, other): # py3k variant120 if self.order == other.order:121 return self.doc < other.doc122 return self.order < other.order123 124 def __repr__(self):125 return "OleItem: doc=%s, order=%d" % (repr(self.doc), self.order)126 127 128class RecordItem(build.OleItem, WritableItem):129 order = 9130 typename = "RECORD"131 132 def __init__(self, typeInfo, typeAttr, doc=None, bForUser=1):133 ## sys.stderr.write("Record %s: size %s\n" % (doc,typeAttr.cbSizeInstance))134 ## sys.stderr.write(" cVars = %s\n" % (typeAttr.cVars,))135 ## for i in range(typeAttr.cVars):136 ## vdesc = typeInfo.GetVarDesc(i)137 ## sys.stderr.write(" Var %d has value %s, type %d, desc=%s\n" % (i, vdesc.value, vdesc.varkind, vdesc.elemdescVar))138 ## sys.stderr.write(" Doc is %s\n" % (typeInfo.GetDocumentation(vdesc.memid),))139 140 build.OleItem.__init__(self, doc)141 self.clsid = typeAttr[0]142 143 def WriteClass(self, generator):144 pass145 146 147# Given an enum, write all aliases for it.148# (no longer necessary for new style code, but still used for old code.149def WriteAliasesForItem(item, aliasItems, stream):150 for alias in aliasItems.values():151 if item.doc and alias.aliasDoc and (alias.aliasDoc[0] == item.doc[0]):152 alias.WriteAliasItem(aliasItems, stream)153 154 155class AliasItem(build.OleItem, WritableItem):156 order = 2157 typename = "ALIAS"158 159 def __init__(self, typeinfo, attr, doc=None, bForUser=1):160 build.OleItem.__init__(self, doc)161 162 ai = attr[14]163 self.attr = attr164 if type(ai) == type(()) and type(ai[1]) == type(165 0166 ): # XXX - This is a hack - why tuples? Need to resolve?167 href = ai[1]168 alinfo = typeinfo.GetRefTypeInfo(href)169 self.aliasDoc = alinfo.GetDocumentation(-1)170 self.aliasAttr = alinfo.GetTypeAttr()171 else:172 self.aliasDoc = None173 self.aliasAttr = None174 175 def WriteAliasItem(self, aliasDict, stream):176 # we could have been written as part of an alias dependency177 if self.bWritten:178 return179 180 if self.aliasDoc:181 depName = self.aliasDoc[0]182 if depName in aliasDict:183 aliasDict[depName].WriteAliasItem(aliasDict, stream)184 print(self.doc[0] + " = " + depName, file=stream)185 else:186 ai = self.attr[14]187 if type(ai) == type(0):188 try:189 typeStr = mapVTToTypeString[ai]190 print("# %s=%s" % (self.doc[0], typeStr), file=stream)191 except KeyError:192 print(193 self.doc[0] + " = None # Can't convert alias info " + str(ai),194 file=stream,195 )196 print(file=stream)197 self.bWritten = 1198 199 200class EnumerationItem(build.OleItem, WritableItem):201 order = 1202 typename = "ENUMERATION"203 204 def __init__(self, typeinfo, attr, doc=None, bForUser=1):205 build.OleItem.__init__(self, doc)206 207 self.clsid = attr[0]208 self.mapVars = {}209 typeFlags = attr[11]210 self.hidden = (211 typeFlags & pythoncom.TYPEFLAG_FHIDDEN212 or typeFlags & pythoncom.TYPEFLAG_FRESTRICTED213 )214 215 for j in range(attr[7]):216 vdesc = typeinfo.GetVarDesc(j)217 name = typeinfo.GetNames(vdesc[0])[0]218 self.mapVars[name] = build.MapEntry(vdesc)219 220 ## def WriteEnumerationHeaders(self, aliasItems, stream):221 ## enumName = self.doc[0]222 ## print >> stream "%s=constants # Compatibility with previous versions." % (enumName)223 ## WriteAliasesForItem(self, aliasItems)224 225 def WriteEnumerationItems(self, stream):226 num = 0227 enumName = self.doc[0]228 # Write in name alpha order229 names = list(self.mapVars.keys())230 names.sort()231 for name in names:232 entry = self.mapVars[name]233 vdesc = entry.desc234 if vdesc[4] == pythoncom.VAR_CONST:235 val = vdesc[1]236 237 use = repr(val)238 # Make sure the repr of the value is valid python syntax239 # still could cause an error on import if it contains a module or type name240 # not available in the global namespace241 try:242 compile(use, "<makepy>", "eval")243 except SyntaxError:244 # At least add the repr as a string, so it can be investigated further245 # Sanitize it, in case the repr contains its own quotes. (??? line breaks too ???)246 use = use.replace('"', "'")247 use = (248 '"'249 + use250 + '"'251 + " # This VARIANT type cannot be converted automatically"252 )253 print(254 "\t%-30s=%-10s # from enum %s"255 % (build.MakePublicAttributeName(name, True), use, enumName),256 file=stream,257 )258 num += 1259 return num260 261 262class VTableItem(build.VTableItem, WritableItem):263 order = 4264 265 def WriteClass(self, generator):266 self.WriteVTableMap(generator)267 self.bWritten = 1268 269 def WriteVTableMap(self, generator):270 stream = generator.file271 print(272 "%s_vtables_dispatch_ = %d" % (self.python_name, self.bIsDispatch),273 file=stream,274 )275 print("%s_vtables_ = [" % (self.python_name,), file=stream)276 for v in self.vtableFuncs:277 names, dispid, desc = v278 assert desc.desckind == pythoncom.DESCKIND_FUNCDESC279 arg_reprs = []280 # more hoops so we don't generate huge lines.281 item_num = 0282 print("\t((", end=" ", file=stream)283 for name in names:284 print(repr(name), ",", end=" ", file=stream)285 item_num = item_num + 1286 if item_num % 5 == 0:287 print("\n\t\t\t", end=" ", file=stream)288 print(289 "), %d, (%r, %r, [" % (dispid, desc.memid, desc.scodeArray),290 end=" ",291 file=stream,292 )293 for arg in desc.args:294 item_num = item_num + 1295 if item_num % 5 == 0:296 print("\n\t\t\t", end=" ", file=stream)297 defval = build.MakeDefaultArgRepr(arg)298 if arg[3] is None:299 arg3_repr = None300 else:301 arg3_repr = repr(arg[3])302 print(303 repr((arg[0], arg[1], defval, arg3_repr)), ",", end=" ", file=stream304 )305 print("],", end=" ", file=stream)306 print(repr(desc.funckind), ",", end=" ", file=stream)307 print(repr(desc.invkind), ",", end=" ", file=stream)308 print(repr(desc.callconv), ",", end=" ", file=stream)309 print(repr(desc.cParamsOpt), ",", end=" ", file=stream)310 print(repr(desc.oVft), ",", end=" ", file=stream)311 print(repr(desc.rettype), ",", end=" ", file=stream)312 print(repr(desc.wFuncFlags), ",", end=" ", file=stream)313 print(")),", file=stream)314 print("]", file=stream)315 print(file=stream)316 317 318class DispatchItem(build.DispatchItem, WritableItem):319 order = 3320 321 def __init__(self, typeinfo, attr, doc=None):322 build.DispatchItem.__init__(self, typeinfo, attr, doc)323 self.type_attr = attr324 self.coclass_clsid = None325 326 def WriteClass(self, generator):327 if (328 not self.bIsDispatch329 and not self.type_attr.typekind == pythoncom.TKIND_DISPATCH330 ):331 return332 # This is pretty screwey - now we have vtable support we333 # should probably rethink this (ie, maybe write both sides for sinks, etc)334 if self.bIsSink:335 self.WriteEventSinkClassHeader(generator)336 self.WriteCallbackClassBody(generator)337 else:338 self.WriteClassHeader(generator)339 self.WriteClassBody(generator)340 print(file=generator.file)341 self.bWritten = 1342 343 def WriteClassHeader(self, generator):344 generator.checkWriteDispatchBaseClass()345 doc = self.doc346 stream = generator.file347 print("class " + self.python_name + "(DispatchBaseClass):", file=stream)348 if doc[1]:349 print("\t" + build._makeDocString(doc[1]), file=stream)350 try:351 progId = pythoncom.ProgIDFromCLSID(self.clsid)352 print(353 "\t# This class is creatable by the name '%s'" % (progId), file=stream354 )355 except pythoncom.com_error:356 pass357 print("\tCLSID = " + repr(self.clsid), file=stream)358 if self.coclass_clsid is None:359 print("\tcoclass_clsid = None", file=stream)360 else:361 print("\tcoclass_clsid = " + repr(self.coclass_clsid), file=stream)362 print(file=stream)363 self.bWritten = 1364 365 def WriteEventSinkClassHeader(self, generator):366 generator.checkWriteEventBaseClass()367 doc = self.doc368 stream = generator.file369 print("class " + self.python_name + ":", file=stream)370 if doc[1]:371 print("\t" + build._makeDocString(doc[1]), file=stream)372 try:373 progId = pythoncom.ProgIDFromCLSID(self.clsid)374 print(375 "\t# This class is creatable by the name '%s'" % (progId), file=stream376 )377 except pythoncom.com_error:378 pass379 print("\tCLSID = CLSID_Sink = " + repr(self.clsid), file=stream)380 if self.coclass_clsid is None:381 print("\tcoclass_clsid = None", file=stream)382 else:383 print("\tcoclass_clsid = " + repr(self.coclass_clsid), file=stream)384 print("\t_public_methods_ = [] # For COM Server support", file=stream)385 WriteSinkEventMap(self, stream)386 print(file=stream)387 print("\tdef __init__(self, oobj = None):", file=stream)388 print("\t\tif oobj is None:", file=stream)389 print("\t\t\tself._olecp = None", file=stream)390 print("\t\telse:", file=stream)391 print("\t\t\timport win32com.server.util", file=stream)392 print(393 "\t\t\tfrom win32com.server.policy import EventHandlerPolicy", file=stream394 )395 print(396 "\t\t\tcpc=oobj._oleobj_.QueryInterface(pythoncom.IID_IConnectionPointContainer)",397 file=stream,398 )399 print("\t\t\tcp=cpc.FindConnectionPoint(self.CLSID_Sink)", file=stream)400 print(401 "\t\t\tcookie=cp.Advise(win32com.server.util.wrap(self, usePolicy=EventHandlerPolicy))",402 file=stream,403 )404 print("\t\t\tself._olecp,self._olecp_cookie = cp,cookie", file=stream)405 print("\tdef __del__(self):", file=stream)406 print("\t\ttry:", file=stream)407 print("\t\t\tself.close()", file=stream)408 print("\t\texcept pythoncom.com_error:", file=stream)409 print("\t\t\tpass", file=stream)410 print("\tdef close(self):", file=stream)411 print("\t\tif self._olecp is not None:", file=stream)412 print(413 "\t\t\tcp,cookie,self._olecp,self._olecp_cookie = self._olecp,self._olecp_cookie,None,None",414 file=stream,415 )416 print("\t\t\tcp.Unadvise(cookie)", file=stream)417 print("\tdef _query_interface_(self, iid):", file=stream)418 print("\t\timport win32com.server.util", file=stream)419 print(420 "\t\tif iid==self.CLSID_Sink: return win32com.server.util.wrap(self)",421 file=stream,422 )423 print(file=stream)424 self.bWritten = 1425 426 def WriteCallbackClassBody(self, generator):427 stream = generator.file428 print("\t# Event Handlers", file=stream)429 print(430 "\t# If you create handlers, they should have the following prototypes:",431 file=stream,432 )433 for name, entry in (434 list(self.propMapGet.items())435 + list(self.propMapPut.items())436 + list(self.mapFuncs.items())437 ):438 fdesc = entry.desc439 methName = MakeEventMethodName(entry.names[0])440 print(441 "#\tdef "442 + methName443 + "(self"444 + build.BuildCallList(445 fdesc,446 entry.names,447 "defaultNamedOptArg",448 "defaultNamedNotOptArg",449 "defaultUnnamedArg",450 "pythoncom.Missing",451 is_comment=True,452 )453 + "):",454 file=stream,455 )456 if entry.doc and entry.doc[1]:457 print("#\t\t" + build._makeDocString(entry.doc[1]), file=stream)458 print(file=stream)459 self.bWritten = 1460 461 def WriteClassBody(self, generator):462 stream = generator.file463 # Write in alpha order.464 names = list(self.mapFuncs.keys())465 names.sort()466 specialItems = {467 "count": None,468 "item": None,469 "value": None,470 "_newenum": None,471 } # If found, will end up with (entry, invoke_tupe)472 itemCount = None473 for name in names:474 entry = self.mapFuncs[name]475 assert entry.desc.desckind == pythoncom.DESCKIND_FUNCDESC476 # skip [restricted] methods, unless it is the477 # enumerator (which, being part of the "system",478 # we know about and can use)479 dispid = entry.desc.memid480 if (481 entry.desc.wFuncFlags & pythoncom.FUNCFLAG_FRESTRICTED482 and dispid != pythoncom.DISPID_NEWENUM483 ):484 continue485 # If not accessible via IDispatch, then we can't use it here.486 if entry.desc.funckind != pythoncom.FUNC_DISPATCH:487 continue488 if dispid == pythoncom.DISPID_VALUE:489 lkey = "value"490 elif dispid == pythoncom.DISPID_NEWENUM:491 specialItems["_newenum"] = (entry, entry.desc.invkind, None)492 continue # Dont build this one now!493 else:494 lkey = name.lower()495 if (496 lkey in specialItems and specialItems[lkey] is None497 ): # remember if a special one.498 specialItems[lkey] = (entry, entry.desc.invkind, None)499 if generator.bBuildHidden or not entry.hidden:500 if entry.GetResultName():501 print("\t# Result is of type " + entry.GetResultName(), file=stream)502 if entry.wasProperty:503 print(504 "\t# The method %s is actually a property, but must be used as a method to correctly pass the arguments"505 % name,506 file=stream,507 )508 ret = self.MakeFuncMethod(entry, build.MakePublicAttributeName(name))509 for line in ret:510 print(line, file=stream)511 print("\t_prop_map_get_ = {", file=stream)512 names = list(self.propMap.keys())513 names.sort()514 for key in names:515 entry = self.propMap[key]516 if generator.bBuildHidden or not entry.hidden:517 resultName = entry.GetResultName()518 if resultName:519 print(520 "\t\t# Property '%s' is an object of type '%s'"521 % (key, resultName),522 file=stream,523 )524 lkey = key.lower()525 details = entry.desc526 resultDesc = details[2]527 argDesc = ()528 mapEntry = MakeMapLineEntry(529 details.memid,530 pythoncom.DISPATCH_PROPERTYGET,531 resultDesc,532 argDesc,533 key,534 entry.GetResultCLSIDStr(),535 )536 537 if details.memid == pythoncom.DISPID_VALUE:538 lkey = "value"539 elif details.memid == pythoncom.DISPID_NEWENUM:540 lkey = "_newenum"541 else:542 lkey = key.lower()543 if (544 lkey in specialItems and specialItems[lkey] is None545 ): # remember if a special one.546 specialItems[lkey] = (547 entry,548 pythoncom.DISPATCH_PROPERTYGET,549 mapEntry,550 )551 # All special methods, except _newenum, are written552 # "normally". This is a mess!553 if details.memid == pythoncom.DISPID_NEWENUM:554 continue555 556 print(557 '\t\t"%s": %s,' % (build.MakePublicAttributeName(key), mapEntry),558 file=stream,559 )560 names = list(self.propMapGet.keys())561 names.sort()562 for key in names:563 entry = self.propMapGet[key]564 if generator.bBuildHidden or not entry.hidden:565 if entry.GetResultName():566 print(567 "\t\t# Method '%s' returns object of type '%s'"568 % (key, entry.GetResultName()),569 file=stream,570 )571 details = entry.desc572 assert details.desckind == pythoncom.DESCKIND_FUNCDESC573 lkey = key.lower()574 argDesc = details[2]575 resultDesc = details[8]576 mapEntry = MakeMapLineEntry(577 details[0],578 pythoncom.DISPATCH_PROPERTYGET,579 resultDesc,580 argDesc,581 key,582 entry.GetResultCLSIDStr(),583 )584 if details.memid == pythoncom.DISPID_VALUE:585 lkey = "value"586 elif details.memid == pythoncom.DISPID_NEWENUM:587 lkey = "_newenum"588 else:589 lkey = key.lower()590 if (591 lkey in specialItems and specialItems[lkey] is None592 ): # remember if a special one.593 specialItems[lkey] = (594 entry,595 pythoncom.DISPATCH_PROPERTYGET,596 mapEntry,597 )598 # All special methods, except _newenum, are written599 # "normally". This is a mess!600 if details.memid == pythoncom.DISPID_NEWENUM:601 continue602 print(603 '\t\t"%s": %s,' % (build.MakePublicAttributeName(key), mapEntry),604 file=stream,605 )606 607 print("\t}", file=stream)608 609 print("\t_prop_map_put_ = {", file=stream)610 # These are "Invoke" args611 names = list(self.propMap.keys())612 names.sort()613 for key in names:614 entry = self.propMap[key]615 if generator.bBuildHidden or not entry.hidden:616 lkey = key.lower()617 details = entry.desc618 # If default arg is None, write an empty tuple619 defArgDesc = build.MakeDefaultArgRepr(details[2])620 if defArgDesc is None:621 defArgDesc = ""622 else:623 defArgDesc = defArgDesc + ","624 print(625 '\t\t"%s" : ((%s, LCID, %d, 0),(%s)),'626 % (627 build.MakePublicAttributeName(key),628 details[0],629 pythoncom.DISPATCH_PROPERTYPUT,630 defArgDesc,631 ),632 file=stream,633 )634 635 names = list(self.propMapPut.keys())636 names.sort()637 for key in names:638 entry = self.propMapPut[key]639 if generator.bBuildHidden or not entry.hidden:640 details = entry.desc641 defArgDesc = MakeDefaultArgsForPropertyPut(details[2])642 print(643 '\t\t"%s": ((%s, LCID, %d, 0),%s),'644 % (645 build.MakePublicAttributeName(key),646 details[0],647 details[4],648 defArgDesc,649 ),650 file=stream,651 )652 print("\t}", file=stream)653 654 if specialItems["value"]:655 entry, invoketype, propArgs = specialItems["value"]656 if propArgs is None:657 typename = "method"658 ret = self.MakeFuncMethod(entry, "__call__")659 else:660 typename = "property"661 ret = [662 "\tdef __call__(self):\n\t\treturn self._ApplyTypes_(*%s)"663 % propArgs664 ]665 print(666 "\t# Default %s for this class is '%s'" % (typename, entry.names[0]),667 file=stream,668 )669 for line in ret:670 print(line, file=stream)671 print("\tdef __str__(self, *args):", file=stream)672 print("\t\treturn str(self.__call__(*args))", file=stream)673 print("\tdef __int__(self, *args):", file=stream)674 print("\t\treturn int(self.__call__(*args))", file=stream)675 676 # _NewEnum (DISPID_NEWENUM) does not appear in typelib for many office objects,677 # but it can still be retrieved at runtime, so always create __iter__.678 # Also, some of those same objects use 1-based indexing, causing the old-style679 # __getitem__ iteration to fail for index 0 where the dynamic iteration succeeds.680 if specialItems["_newenum"]:681 enumEntry, invoketype, propArgs = specialItems["_newenum"]682 assert enumEntry.desc.desckind == pythoncom.DESCKIND_FUNCDESC683 invkind = enumEntry.desc.invkind684 # ??? Wouldn't this be the resultCLSID for the iterator itself, rather than the resultCLSID685 # for the result of each Next() call, which is what it's used for ???686 resultCLSID = enumEntry.GetResultCLSIDStr()687 else:688 invkind = pythoncom.DISPATCH_METHOD | pythoncom.DISPATCH_PROPERTYGET689 resultCLSID = "None"690 # If we dont have a good CLSID for the enum result, assume it is the same as the Item() method.691 if resultCLSID == "None" and "Item" in self.mapFuncs:692 resultCLSID = self.mapFuncs["Item"].GetResultCLSIDStr()693 print("\tdef __iter__(self):", file=stream)694 print('\t\t"Return a Python iterator for this object"', file=stream)695 print("\t\ttry:", file=stream)696 print(697 "\t\t\tob = self._oleobj_.InvokeTypes(%d,LCID,%d,(13, 10),())"698 % (pythoncom.DISPID_NEWENUM, invkind),699 file=stream,700 )701 print("\t\texcept pythoncom.error:", file=stream)702 print(703 '\t\t\traise TypeError("This object does not support enumeration")',704 file=stream,705 )706 # Iterator is wrapped as PyIEnumVariant, and each result of __next__ is Dispatch'ed if necessary707 print(708 "\t\treturn win32com.client.util.Iterator(ob, %s)" % resultCLSID,709 file=stream,710 )711 712 if specialItems["item"]:713 entry, invoketype, propArgs = specialItems["item"]714 resultCLSID = entry.GetResultCLSIDStr()715 print(716 "\t#This class has Item property/method which allows indexed access with the object[key] syntax.",717 file=stream,718 )719 print(720 "\t#Some objects will accept a string or other type of key in addition to integers.",721 file=stream,722 )723 print(724 "\t#Note that many Office objects do not use zero-based indexing.",725 file=stream,726 )727 print("\tdef __getitem__(self, key):", file=stream)728 print(729 '\t\treturn self._get_good_object_(self._oleobj_.Invoke(*(%d, LCID, %d, 1, key)), "Item", %s)'730 % (entry.desc.memid, invoketype, resultCLSID),731 file=stream,732 )733 734 if specialItems["count"]:735 entry, invoketype, propArgs = specialItems["count"]736 if propArgs is None:737 typename = "method"738 ret = self.MakeFuncMethod(entry, "__len__")739 else:740 typename = "property"741 ret = [742 "\tdef __len__(self):\n\t\treturn self._ApplyTypes_(*%s)" % propArgs743 ]744 print(745 "\t#This class has Count() %s - allow len(ob) to provide this"746 % (typename),747 file=stream,748 )749 for line in ret:750 print(line, file=stream)751 # Also include a __nonzero__752 print(753 "\t#This class has a __len__ - this is needed so 'if object:' always returns TRUE.",754 file=stream,755 )756 print("\tdef __nonzero__(self):", file=stream)757 print("\t\treturn True", file=stream)758 759 760class CoClassItem(build.OleItem, WritableItem):761 order = 5762 typename = "COCLASS"763 764 def __init__(self, typeinfo, attr, doc=None, sources=[], interfaces=[], bForUser=1):765 build.OleItem.__init__(self, doc)766 self.clsid = attr[0]767 self.sources = sources768 self.interfaces = interfaces769 self.bIsDispatch = 1 # Pretend it is so it is written to the class map.770 771 def WriteClass(self, generator):772 generator.checkWriteCoClassBaseClass()773 doc = self.doc774 stream = generator.file775 if generator.generate_type == GEN_DEMAND_CHILD:776 # Some special imports we must setup.777 referenced_items = []778 for ref, flag in self.sources:779 referenced_items.append(ref)780 for ref, flag in self.interfaces:781 referenced_items.append(ref)782 print("import sys", file=stream)783 for ref in referenced_items:784 print(785 "__import__('%s.%s')" % (generator.base_mod_name, ref.python_name),786 file=stream,787 )788 print(789 "%s = sys.modules['%s.%s'].%s"790 % (791 ref.python_name,792 generator.base_mod_name,793 ref.python_name,794 ref.python_name,795 ),796 file=stream,797 )798 # And pretend we have written it - the name is now available as if we had!799 ref.bWritten = 1800 try:801 progId = pythoncom.ProgIDFromCLSID(self.clsid)802 print("# This CoClass is known by the name '%s'" % (progId), file=stream)803 except pythoncom.com_error:804 pass805 print(806 "class %s(CoClassBaseClass): # A CoClass" % (self.python_name), file=stream807 )808 if doc and doc[1]:809 print("\t# " + doc[1], file=stream)810 print("\tCLSID = %r" % (self.clsid,), file=stream)811 print("\tcoclass_sources = [", file=stream)812 defItem = None813 for item, flag in self.sources:814 if flag & pythoncom.IMPLTYPEFLAG_FDEFAULT:815 defItem = item816 # If we have written a Python class, reference the name -817 # otherwise just the IID.818 if item.bWritten:819 key = item.python_name820 else:821 key = repr(str(item.clsid)) # really the iid.822 print("\t\t%s," % (key), file=stream)823 print("\t]", file=stream)824 if defItem:825 if defItem.bWritten:826 defName = defItem.python_name827 else:828 defName = repr(str(defItem.clsid)) # really the iid.829 print("\tdefault_source = %s" % (defName,), file=stream)830 print("\tcoclass_interfaces = [", file=stream)831 defItem = None832 for item, flag in self.interfaces:833 if flag & pythoncom.IMPLTYPEFLAG_FDEFAULT: # and dual:834 defItem = item835 # If we have written a class, reference its name, otherwise the IID836 if item.bWritten:837 key = item.python_name838 else:839 key = repr(str(item.clsid)) # really the iid.840 print("\t\t%s," % (key,), file=stream)841 print("\t]", file=stream)842 if defItem:843 if defItem.bWritten:844 defName = defItem.python_name845 else:846 defName = repr(str(defItem.clsid)) # really the iid.847 print("\tdefault_interface = %s" % (defName,), file=stream)848 self.bWritten = 1849 print(file=stream)850 851 852class GeneratorProgress:853 def __init__(self):854 pass855 856 def Starting(self, tlb_desc):857 """Called when the process starts."""858 self.tlb_desc = tlb_desc859 860 def Finished(self):861 """Called when the process is complete."""862 863 def SetDescription(self, desc, maxticks=None):864 """We are entering a major step. If maxticks, then this865 is how many ticks we expect to make until finished866 """867 868 def Tick(self, desc=None):869 """Minor progress step. Can provide new description if necessary"""870 871 def VerboseProgress(self, desc):872 """Verbose/Debugging output."""873 874 def LogWarning(self, desc):875 """If a warning is generated"""876 877 def LogBeginGenerate(self, filename):878 pass879 880 def Close(self):881 pass882 883 884class Generator:885 def __init__(886 self,887 typelib,888 sourceFilename,889 progressObject,890 bBuildHidden=1,891 bUnicodeToString=None,892 ):893 assert bUnicodeToString is None, "this is deprecated and will go away"894 self.bHaveWrittenDispatchBaseClass = 0895 self.bHaveWrittenCoClassBaseClass = 0896 self.bHaveWrittenEventBaseClass = 0897 self.typelib = typelib898 self.sourceFilename = sourceFilename899 self.bBuildHidden = bBuildHidden900 self.progress = progressObject901 # These 2 are later additions and most of the code still 'print's...902 self.file = None903 904 def CollectOleItemInfosFromType(self):905 ret = []906 for i in range(self.typelib.GetTypeInfoCount()):907 info = self.typelib.GetTypeInfo(i)908 infotype = self.typelib.GetTypeInfoType(i)909 doc = self.typelib.GetDocumentation(i)910 attr = info.GetTypeAttr()911 ret.append((info, infotype, doc, attr))912 return ret913 914 def _Build_CoClass(self, type_info_tuple):915 info, infotype, doc, attr = type_info_tuple916 # find the source and dispinterfaces for the coclass917 child_infos = []918 for j in range(attr[8]):919 flags = info.GetImplTypeFlags(j)920 try:921 refType = info.GetRefTypeInfo(info.GetRefTypeOfImplType(j))922 except pythoncom.com_error:923 # Can't load a dependent typelib?924 continue925 refAttr = refType.GetTypeAttr()926 child_infos.append(927 (928 info,929 refAttr.typekind,930 refType,931 refType.GetDocumentation(-1),932 refAttr,933 flags,934 )935 )936 937 # Done generating children - now the CoClass itself.938 newItem = CoClassItem(info, attr, doc)939 return newItem, child_infos940 941 def _Build_CoClassChildren(self, coclass, coclass_info, oleItems, vtableItems):942 sources = {}943 interfaces = {}944 for info, info_type, refType, doc, refAttr, flags in coclass_info:945 # sys.stderr.write("Attr typeflags for coclass referenced object %s=%d (%d), typekind=%d\n" % (name, refAttr.wTypeFlags, refAttr.wTypeFlags & pythoncom.TYPEFLAG_FDUAL,refAttr.typekind))946 if refAttr.typekind == pythoncom.TKIND_DISPATCH or (947 refAttr.typekind == pythoncom.TKIND_INTERFACE948 and refAttr[11] & pythoncom.TYPEFLAG_FDISPATCHABLE949 ):950 clsid = refAttr[0]951 if clsid in oleItems:952 dispItem = oleItems[clsid]953 else:954 dispItem = DispatchItem(refType, refAttr, doc)955 oleItems[dispItem.clsid] = dispItem956 dispItem.coclass_clsid = coclass.clsid957 if flags & pythoncom.IMPLTYPEFLAG_FSOURCE:958 dispItem.bIsSink = 1959 sources[dispItem.clsid] = (dispItem, flags)960 else:961 interfaces[dispItem.clsid] = (dispItem, flags)962 # If dual interface, make do that too.963 if clsid not in vtableItems and refAttr[11] & pythoncom.TYPEFLAG_FDUAL:964 refType = refType.GetRefTypeInfo(refType.GetRefTypeOfImplType(-1))965 refAttr = refType.GetTypeAttr()966 assert (967 refAttr.typekind == pythoncom.TKIND_INTERFACE968 ), "must be interface bynow!"969 vtableItem = VTableItem(refType, refAttr, doc)970 vtableItems[clsid] = vtableItem971 coclass.sources = list(sources.values())972 coclass.interfaces = list(interfaces.values())973 974 def _Build_Interface(self, type_info_tuple):975 info, infotype, doc, attr = type_info_tuple976 oleItem = vtableItem = None977 if infotype == pythoncom.TKIND_DISPATCH or (978 infotype == pythoncom.TKIND_INTERFACE979 and attr[11] & pythoncom.TYPEFLAG_FDISPATCHABLE980 ):981 oleItem = DispatchItem(info, attr, doc)982 # If this DISPATCH interface dual, then build that too.983 if attr.wTypeFlags & pythoncom.TYPEFLAG_FDUAL:984 # Get the vtable interface985 refhtype = info.GetRefTypeOfImplType(-1)986 info = info.GetRefTypeInfo(refhtype)987 attr = info.GetTypeAttr()988 infotype = pythoncom.TKIND_INTERFACE989 else:990 infotype = None991 assert infotype in [992 None,993 pythoncom.TKIND_INTERFACE,994 ], "Must be a real interface at this point"995 if infotype == pythoncom.TKIND_INTERFACE:996 vtableItem = VTableItem(info, attr, doc)997 return oleItem, vtableItem998 999 def BuildOleItemsFromType(self):1000 assert (1001 self.bBuildHidden1002 ), "This code doesnt look at the hidden flag - I thought everyone set it true!?!?!"1003 oleItems = {}1004 enumItems = {}1005 recordItems = {}1006 vtableItems = {}1007 1008 for type_info_tuple in self.CollectOleItemInfosFromType():1009 info, infotype, doc, attr = type_info_tuple1010 clsid = attr[0]1011 if infotype == pythoncom.TKIND_ENUM or infotype == pythoncom.TKIND_MODULE:1012 newItem = EnumerationItem(info, attr, doc)1013 enumItems[newItem.doc[0]] = newItem1014 # We never hide interfaces (MSAccess, for example, nominates interfaces as1015 # hidden, assuming that you only ever use them via the CoClass)1016 elif infotype in [pythoncom.TKIND_DISPATCH, pythoncom.TKIND_INTERFACE]:1017 if clsid not in oleItems:1018 oleItem, vtableItem = self._Build_Interface(type_info_tuple)1019 oleItems[clsid] = oleItem # Even "None" goes in here.1020 if vtableItem is not None:1021 vtableItems[clsid] = vtableItem1022 elif (1023 infotype == pythoncom.TKIND_RECORD or infotype == pythoncom.TKIND_UNION1024 ):1025 newItem = RecordItem(info, attr, doc)1026 recordItems[newItem.clsid] = newItem1027 elif infotype == pythoncom.TKIND_ALIAS:1028 # We dont care about alias' - handled intrinsicly.1029 continue1030 elif infotype == pythoncom.TKIND_COCLASS:1031 newItem, child_infos = self._Build_CoClass(type_info_tuple)1032 self._Build_CoClassChildren(newItem, child_infos, oleItems, vtableItems)1033 oleItems[newItem.clsid] = newItem1034 else:1035 self.progress.LogWarning("Unknown TKIND found: %d" % infotype)1036 1037 return oleItems, enumItems, recordItems, vtableItems1038 1039 def open_writer(self, filename, encoding="mbcs"):1040 # A place to put code to open a file with the appropriate encoding.1041 # Does *not* set self.file - just opens and returns a file.1042 # Actually returns a handle to a temp file - finish_writer then deletes1043 # the filename asked for and puts everything back in place. This1044 # is so errors don't leave a 1/2 generated file around causing bizarre1045 # errors later, and so that multiple processes writing the same file1046 # don't step on each others' toes.1047 # Could be a classmethod one day...1048 temp_filename = self.get_temp_filename(filename)1049 return open(temp_filename, "wt", encoding=encoding)1050 1051 def finish_writer(self, filename, f, worked):1052 f.close()1053 try:1054 os.unlink(filename)1055 except os.error:1056 pass1057 temp_filename = self.get_temp_filename(filename)1058 if worked:1059 try:1060 os.rename(temp_filename, filename)1061 except os.error:1062 # If we are really unlucky, another process may have written the1063 # file in between our calls to os.unlink and os.rename. So try1064 # again, but only once.1065 # There are still some race conditions, but they seem difficult to1066 # fix, and they probably occur much less frequently:1067 # * The os.rename failure could occur more than once if more than1068 # two processes are involved.1069 # * In between os.unlink and os.rename, another process could try1070 # to import the module, having seen that it already exists.1071 # * If another process starts a COM server while we are still1072 # generating __init__.py, that process sees that the folder1073 # already exists and assumes that __init__.py is already there1074 # as well.1075 try:1076 os.unlink(filename)1077 except os.error:1078 pass1079 os.rename(temp_filename, filename)1080 else:1081 os.unlink(temp_filename)1082 1083 def get_temp_filename(self, filename):1084 return "%s.%d.temp" % (filename, os.getpid())1085 1086 def generate(self, file, is_for_demand=0):1087 if is_for_demand:1088 self.generate_type = GEN_DEMAND_BASE1089 else:1090 self.generate_type = GEN_FULL1091 self.file = file1092 self.do_generate()1093 self.file = None1094 self.progress.Finished()1095 1096 def do_gen_file_header(self):1097 la = self.typelib.GetLibAttr()1098 moduleDoc = self.typelib.GetDocumentation(-1)1099 docDesc = ""1100 if moduleDoc[1]:1101 docDesc = moduleDoc[1]1102 1103 # Reset all the 'per file' state1104 self.bHaveWrittenDispatchBaseClass = 01105 self.bHaveWrittenCoClassBaseClass = 01106 self.bHaveWrittenEventBaseClass = 01107 # You must provide a file correctly configured for writing unicode.1108 # We assert this is it may indicate somewhere in pywin32 that needs1109 # upgrading.1110 assert self.file.encoding, self.file1111 encoding = self.file.encoding # or "mbcs"1112 1113 print("# -*- coding: %s -*-" % (encoding,), file=self.file)1114 print("# Created by makepy.py version %s" % (makepy_version,), file=self.file)1115 print(1116 "# By python version %s" % (sys.version.replace("\n", "-"),), file=self.file1117 )1118 if self.sourceFilename:1119 print(1120 "# From type library '%s'" % (os.path.split(self.sourceFilename)[1],),1121 file=self.file,1122 )1123 print("# On %s" % time.ctime(time.time()), file=self.file)1124 1125 print(build._makeDocString(docDesc), file=self.file)1126 1127 print("makepy_version =", repr(makepy_version), file=self.file)1128 print("python_version = 0x%x" % (sys.hexversion,), file=self.file)1129 print(file=self.file)1130 print(1131 "import win32com.client.CLSIDToClass, pythoncom, pywintypes", file=self.file1132 )1133 print("import win32com.client.util", file=self.file)1134 print("from pywintypes import IID", file=self.file)1135 print("from win32com.client import Dispatch", file=self.file)1136 print(file=self.file)1137 print(1138 "# The following 3 lines may need tweaking for the particular server",1139 file=self.file,1140 )1141 print(1142 "# Candidates are pythoncom.Missing, .Empty and .ArgNotFound",1143 file=self.file,1144 )1145 print("defaultNamedOptArg=pythoncom.Empty", file=self.file)1146 print("defaultNamedNotOptArg=pythoncom.Empty", file=self.file)1147 print("defaultUnnamedArg=pythoncom.Empty", file=self.file)1148 print(file=self.file)1149 print("CLSID = " + repr(la[0]), file=self.file)1150 print("MajorVersion = " + str(la[3]), file=self.file)1151 print("MinorVersion = " + str(la[4]), file=self.file)1152 print("LibraryFlags = " + str(la[5]), file=self.file)1153 print("LCID = " + hex(la[1]), file=self.file)1154 print(file=self.file)1155 1156 def do_generate(self):1157 moduleDoc = self.typelib.GetDocumentation(-1)1158 stream = self.file1159 docDesc = ""1160 if moduleDoc[1]:1161 docDesc = moduleDoc[1]1162 self.progress.Starting(docDesc)1163 self.progress.SetDescription("Building definitions from type library...")1164 1165 self.do_gen_file_header()1166 1167 oleItems, enumItems, recordItems, vtableItems = self.BuildOleItemsFromType()1168 1169 self.progress.SetDescription(1170 "Generating...", len(oleItems) + len(enumItems) + len(vtableItems)1171 )1172 1173 # Generate the constants and their support.1174 if enumItems:1175 print("class constants:", file=stream)1176 items = list(enumItems.values())1177 items.sort()1178 num_written = 01179 for oleitem in items:1180 num_written += oleitem.WriteEnumerationItems(stream)1181 self.progress.Tick()1182 if not num_written:1183 print("\tpass", file=stream)1184 print(file=stream)1185 1186 if self.generate_type == GEN_FULL:1187 items = [l for l in oleItems.values() if l is not None]1188 items.sort()1189 for oleitem in items:1190 self.progress.Tick()1191 oleitem.WriteClass(self)1192 1193 items = list(vtableItems.values())1194 items.sort()1195 for oleitem in items:1196 self.progress.Tick()1197 oleitem.WriteClass(self)1198 else:1199 self.progress.Tick(len(oleItems) + len(vtableItems))1200 