1############################################################################ 2# Joshua Boverhof<JRBoverhof@lbl.gov>, LBNL 3# Monte Goode <MMGoode@lbl.gov>, LBNL 4# See Copyright for copyright notice! 5############################################################################ 6 7import exceptions, sys, optparse, os, warnings, traceback 8from os.path import isfile, join, split 9 10#from operator import xor 11import ZSI 12from ConfigParser import ConfigParser 13from ZSI.generate.wsdl2python import WriteServiceModule, ServiceDescription as wsdl2pyServiceDescription 14from ZSI.wstools import WSDLTools, XMLSchema 15from ZSI.wstools.logging import setBasicLoggerDEBUG 16from ZSI.generate import containers, utility 17from ZSI.generate.utility import NCName_to_ClassName as NC_to_CN, TextProtect 18from ZSI.generate.wsdl2dispatch import ServiceModuleWriter as ServiceDescription 19from ZSI.generate.wsdl2dispatch import WSAServiceModuleWriter as ServiceDescriptionWSA 20 21 22warnings.filterwarnings('ignore', '', exceptions.UserWarning) 23def SetDebugCallback(option, opt, value, parser, *args, **kwargs): 24 setBasicLoggerDEBUG() 25 warnings.resetwarnings() 26 27def SetPyclassMetaclass(option, opt, value, parser, *args, **kwargs): 28 """set up pyclass metaclass for complexTypes""" 29 from ZSI.generate.containers import ServiceHeaderContainer,\ 30 TypecodeContainerBase, TypesHeaderContainer 31 32 TypecodeContainerBase.metaclass = kwargs['metaclass'] 33 TypesHeaderContainer.imports.append(\ 34 'from %(module)s import %(metaclass)s' %kwargs 35 ) 36 ServiceHeaderContainer.imports.append(\ 37 'from %(module)s import %(metaclass)s' %kwargs 38 ) 39 40def SetUpLazyEvaluation(option, opt, value, parser, *args, **kwargs): 41 from ZSI.generate.containers import TypecodeContainerBase 42 TypecodeContainerBase.lazy = True 43 44 45 46def wsdl2py(args=None): 47 """Utility for automatically generating client/service interface code from 48 a wsdl definition, and a set of classes representing element declarations 49 and type definitions. By default invoking this script produces three files, 50 each named after the wsdl definition name, in the current working directory. 51 52 Generated Modules Suffix: 53 _client.py -- client locator, rpc proxy port, messages 54 _types.py -- typecodes representing 55 _server.py -- server-side bindings 56 57 Parameters: 58 args -- optional can provide arguments, rather than parsing 59 command-line. 60 61 return: 62 Default behavior is to return None, if args are provided then 63 return names of the generated files. 64 65 """ 66 op = optparse.OptionParser(usage="USAGE: %wsdl2py [options] WSDL", 67 description=wsdl2py.__doc__) 68 69 # Basic options 70 op.add_option("-x", "--schema", 71 action="store_true", dest="schema", default=False, 72 help="process just the schema from an xsd file [no services]") 73 74 op.add_option("-d", "--debug", 75 action="callback", callback=SetDebugCallback, 76 help="debug output") 77 78 # WS Options 79 op.add_option("-a", "--address", 80 action="store_true", dest="address", default=False, 81 help="ws-addressing support, must include WS-Addressing schema.") 82 83 # pyclass Metaclass 84 op.add_option("-b", "--complexType", 85 action="callback", callback=SetPyclassMetaclass, 86 callback_kwargs={'module':'ZSI.generate.pyclass', 87 'metaclass':'pyclass_type'}, 88 help="add convenience functions for complexTypes, including Getters, Setters, factory methods, and properties (via metaclass). *** DONT USE WITH --simple-naming ***") 89 90 # Lazy Evaluation of Typecodes (done at serialization/parsing when needed). 91 op.add_option("-l", "--lazy", 92 action="callback", callback=SetUpLazyEvaluation, 93 callback_kwargs={}, 94 help="EXPERIMENTAL: recursion error solution, lazy evalution of typecodes") 95 96 # Use Twisted 97 op.add_option("-w", "--twisted", 98 action="store_true", dest='twisted', default=False, 99 help="generate a twisted.web client/server, dependencies python>=2.4, Twisted>=2.0.0, TwistedWeb>=0.5.0") 100 101 op.add_option("-o", "--output-dir", 102 action="store", dest="output_dir", default=".", type="string", 103 help="save files in directory") 104 105 op.add_option("-s", "--simple-naming", 106 action="store_true", dest="simple_naming", default=False, 107 help="map element names directly to python attributes") 108 109 op.add_option("-p", "--pydoc", 110 action="store_true", dest="pydoc", default=False, 111 help="top-level directory for pydoc documentation.") 112 113 114 is_cmdline = args is None 115 if is_cmdline: 116 (options, args) = op.parse_args() 117 else: 118 (options, args) = op.parse_args(args) 119 120 if len(args) != 1: 121 print>>sys.stderr, 'Expecting a file/url as argument (WSDL).' 122 sys.exit(os.EX_USAGE) 123 124 location = args[0] 125 if options.schema is True: 126 reader = XMLSchema.SchemaReader(base_url=location) 127 else: 128 reader = WSDLTools.WSDLReader() 129 130 load = reader.loadFromFile 131 if not isfile(location): 132 load = reader.loadFromURL 133 134 try: 135 wsdl = load(location) 136 except Exception, e: 137 print >> sys.stderr, "Error loading %s: \n\t%s" % (location, e) 138 traceback.print_exc(sys.stderr) 139 # exit code UNIX specific, Windows? 140 if hasattr(os, 'EX_NOINPUT'): sys.exit(os.EX_NOINPUT) 141 sys.exit("error loading %s" %location) 142 143 if isinstance(wsdl, XMLSchema.XMLSchema): 144 wsdl.location = location 145 files = _wsdl2py(options, wsdl) 146 else: 147 files = _wsdl2py(options, wsdl) 148 files.append(_wsdl2dispatch(options, wsdl)) 149 150 if getattr(options, 'pydoc', False): 151 _writepydoc(os.path.join('docs', 'API'), *files) 152 153 if is_cmdline: 154 return 155 156 return files 157 158 159#def wsdl2dispatch(args=None): 160# """Deprecated: wsdl2py now generates everything 161# A utility for automatically generating service skeleton code from a wsdl 162# definition. 163# """ 164# op = optparse.OptionParser() 165# op.add_option("-a", "--address", 166# action="store_true", dest="address", default=False, 167# help="ws-addressing support, must include WS-Addressing schema.") 168# op.add_option("-d", "--debug", 169# action="callback", callback=SetDebugCallback, 170# help="debug output") 171# op.add_option("-t", "--types", 172# action="store", dest="types", default=None, type="string", 173# help="Write generated files to OUTPUT_DIR") 174# op.add_option("-o", "--output-dir", 175# action="store", dest="output_dir", default=".", type="string", 176# help="file to load types from") 177# op.add_option("-s", "--simple-naming", 178# action="store_true", dest="simple_naming", default=False, 179# help="Simplify generated naming.") 180# 181# if args is None: 182# (options, args) = op.parse_args() 183# else: 184# (options, args) = op.parse_args(args) 185# 186# if len(args) != 1: 187# print>>sys.stderr, 'Expecting a file/url as argument (WSDL).' 188# sys.exit(os.EX_USAGE) 189# 190# reader = WSDLTools.WSDLReader() 191# if isfile(args[0]): 192# _wsdl2dispatch(options, reader.loadFromFile(args[0])) 193# return 194# 195# _wsdl2dispatch(options, reader.loadFromURL(args[0])) 196 197 198def _wsdl2py(options, wsdl): 199 200 if options.twisted: 201 from ZSI.generate.containers import ServiceHeaderContainer 202 try: 203 ServiceHeaderContainer.imports.remove('from ZSI import client') 204 except ValueError: 205 pass 206 ServiceHeaderContainer.imports.append('from ZSI.twisted import client') 207 208 209 if options.simple_naming: 210 # Use a different client suffix 211 # WriteServiceModule.client_module_suffix = "_client" 212 # Write messages definitions to a separate file. 213 #wsdl2pyServiceDescription.separate_messages = True 214 # Use more simple type and element class names 215 containers.SetTypeNameFunc( lambda n: '%s_' %(NC_to_CN(n)) ) 216 containers.SetElementNameFunc( lambda n: '%s' %(NC_to_CN(n)) ) 217 # Don't add "_" to the attribute name (remove when --aname works well) 218 containers.ContainerBase.func_aname = lambda instnc,n: TextProtect(str(n)) 219 # write out the modules with their names rather than their number. 220 utility.namespace_name = lambda cls, ns: utility.Namespace2ModuleName(ns) 221 222 files = [] 223 append = files.append 224 if isinstance(wsdl, XMLSchema.XMLSchema): 225 wsm = WriteServiceModule(_XMLSchemaAdapter(wsdl.location, wsdl), 226 addressing=options.address) 227 else: 228 wsm = WriteServiceModule(wsdl, addressing=options.address) 229 client_mod = wsm.getClientModuleName() 230 client_file = join(options.output_dir, '%s.py' %client_mod) 231 append(client_file) 232 fd = open(client_file, 'w+') 233 wsm.writeClient(fd) 234 fd.close() 235 236 types_mod = wsm.getTypesModuleName() 237 types_file = join(options.output_dir, '%s.py' %types_mod) 238 append(types_file) 239 fd = open(types_file, 'w+' ) 240 wsm.writeTypes(fd) 241 fd.close() 242 243 return files 244 245 246def _wsdl2dispatch(options, wsdl): 247 """TOOD: Remove ServiceContainer stuff, and replace with WSGI. 248 """ 249 kw = dict() 250 if options.twisted: 251 from ZSI.twisted.WSresource import WSResource 252 kw['base'] = WSResource 253 ss = ServiceDescription(**kw) 254 if options.address is True: 255 raise RuntimeError, 'WS-Address w/twisted currently unsupported, edit the "factory" attribute by hand' 256 else: 257 # TODO: make all this handler arch 258 if options.address is True: 259 ss = ServiceDescriptionWSA() 260 else: 261 ss = ServiceDescription(**kw) 262 263 ss.fromWSDL(wsdl) 264 file_name = ss.getServiceModuleName()+'.py' 265 fd = open( join(options.output_dir, file_name), 'w+') 266 ss.write(fd) 267 fd.close() 268 269 return file_name 270 271 272class _XMLSchemaAdapter: 273 """Adapts an obj XMLSchema.XMLSchema to look like a WSDLTools.WSDL, 274 just setting a couple attributes code expects to see. 275 """ 276 def __init__(self, location, schema): 277 """Parameters: 278 location -- base location, file path 279 schema -- XMLSchema instance 280 """ 281 self.name = '_'.join(split(location)[-1].split('.')) 282 self.types = {schema.targetNamespace:schema} 283 284 285 286 287import os, pydoc, sys, warnings, inspect 288import os.path 289 290from distutils import log 291from distutils.command.build_py import build_py 292from distutils.util import convert_path 293 294#from setuptools import find_packages 295#from setuptools import Command 296from ZSI.schema import ElementDeclaration, TypeDefinition 297#from pyGridWare.utility.generate.Modules import NR 298#from pyGridWare.utility.generate.Modules import CLIENT, TYPES 299 300#def find_packages_modules(where='.'): 301# #pack,mod,mod_file 302# """Return a list all Python packages found within directory 'where' 303# """ 304# out = [] 305# stack=[(convert_path(where), '')] 306# while stack: 307# where,prefix = stack.pop(0) 308# for name in os.listdir(where): 309# fn = os.path.join(where,name) 310# #if (os.path.isdir(fn) and 311# # os.path.isfile(os.path.join(fn,'__init__.py')) 312# #): 313# # out.append(prefix+name); stack.append((fn,prefix+name+'.')) 314# if (os.path.isdir(fn) and 315# os.path.isfile(os.path.join(fn,'__init__.py'))): 316# stack.append((fn,prefix+name+'.')) 317# continue 318# 319# if name == '__init__.py' or not name.endswith('.py'): 320# continue 321# 322# out.append((prefix, name.split('.py')[0])) 323# 324# return out 325 326 327def _writedoc(doc, thing, forceload=0): 328 """Write HTML documentation to a file in the current directory. 329 """ 330 try: 331 object, name = pydoc.resolve(thing, forceload) 332 page = pydoc.html.page(pydoc.describe(object), pydoc.html.document(object, name)) 333 fname = os.path.join(doc, name + '.html') 334 file = open(fname, 'w') 335 file.write(page) 336 file.close() 337 except (ImportError, pydoc.ErrorDuringImport), value: 338 traceback.print_exc(sys.stderr) 339 else: 340 return name + '.html' 341 342 343def _writeclientdoc(doc, thing, forceload=0): 344 """Write HTML documentation to a file in the current directory. 345 """ 346 docmodule = pydoc.HTMLDoc.docmodule 347 def strongarm(self, object, name=None, mod=None, *ignored): 348 result = docmodule(self, object, name, mod, *ignored) 349 350 # Grab all the aliases to pyclasses and create links. 351 nonmembers = [] 352 push = nonmembers.append 353 for k,v in inspect.getmembers(object, inspect.isclass): 354 if inspect.getmodule(v) is not object and getattr(v,'typecode',None) is not None: 355 push('<a href="%s.html">%s</a>: pyclass alias<br/>' %(v.__name__,k)) 356 357 result += self.bigsection('Aliases', '#ffffff', '#eeaa77', ''.join(nonmembers)) 358 return result 359 360 pydoc.HTMLDoc.docmodule = strongarm 361 try: 362 object, name = pydoc.resolve(thing, forceload) 363 page = pydoc.html.page(pydoc.describe(object), pydoc.html.document(object, name)) 364 name = os.path.join(doc, name + '.html') 365 file = open(name, 'w') 366 file.write(page) 367 file.close() 368 except (ImportError, pydoc.ErrorDuringImport), value: 369 log.debug(str(value)) 370 371 pydoc.HTMLDoc.docmodule = docmodule 372 373def _writetypesdoc(doc, thing, forceload=0): 374 """Write HTML documentation to a file in the current directory. 375 """ 376 try: 377 object, name = pydoc.resolve(thing, forceload) 378 name = os.path.join(doc, name + '.html') 379 except (ImportError, pydoc.ErrorDuringImport), value: 380 log.debug(str(value)) 381 return 382 383 # inner classes 384 cdict = {} 385 fdict = {} 386 elements_dict = {} 387 types_dict = {} 388 for kname,klass in inspect.getmembers(thing, inspect.isclass): 389 if thing is not inspect.getmodule(klass): 390 continue 391 392 cdict[kname] = inspect.getmembers(klass, inspect.isclass) 393 for iname,iklass in cdict[kname]: 394 key = (kname,iname) 395 fdict[key] = _writedoc(doc, iklass) 396 if issubclass(iklass, ElementDeclaration): 397 398 try: 399 typecode = iklass() 400 except (AttributeError,RuntimeError), ex: 401 elements_dict[iname] = _writebrokedoc(doc, ex, iname) 402 continue 403 404 elements_dict[iname] = None 405 if typecode.pyclass is not None: 406 elements_dict[iname] = _writedoc(doc, typecode.pyclass) 407 408 continue 409 410 if issubclass(iklass, TypeDefinition): 411 try: 412 typecode = iklass(None) 413 except (AttributeError,RuntimeError), ex: 414 types_dict[iname] = _writebrokedoc(doc, ex, iname) 415 continue 416 417 types_dict[iname] = None 418 if typecode.pyclass is not None: 419 types_dict[iname] = _writedoc(doc, typecode.pyclass) 420 421 continue 422 423 424 def strongarm(self, object, name=None, mod=None, funcs={}, classes={}, *ignored): 425 """Produce HTML documentation for a class object.""" 426 realname = object.__name__ 427 name = name or realname 428 bases = object.__bases__ 429 object, name = pydoc.resolve(object, forceload) 430 contents = [] 431 push = contents.append 432 if name == realname: 433 title = '<a name="%s">class <strong>%s</strong></a>' % ( 434 name, realname) 435 else: 436 title = '<strong>%s</strong> = <a name="%s">class %s</a>' % ( 437 name, name, realname) 438 439 mdict = {} 440 if bases: 441 parents = [] 442 for base in bases: 443 parents.append(self.classlink(base, object.__module__)) 444 title = title + '(%s)' % pydoc.join(parents, ', ') 445 446 doc = self.markup(pydoc.getdoc(object), self.preformat, funcs, classes, mdict) 447 doc = doc and '<tt>%s<br> </tt>' % doc 448 for iname,iclass in cdict[name]: 449 fname = fdict[(name,iname)] 450 451 if elements_dict.has_key(iname): 452 push('class <a href="%s">%s</a>: element declaration typecode<br/>'\ 453 %(fname,iname)) 454 pyclass = elements_dict[iname] 455 if pyclass is not None: 456 push('<ul>instance attributes:') 457 push('<li><a href="%s">pyclass</a>: instances serializable to XML<br/></li>'\ 458 %elements_dict[iname]) 459 push('</ul>') 460 elif types_dict.has_key(iname): 461 push('class <a href="%s">%s</a>: type definition typecode<br/>' %(fname,iname)) 462 pyclass = types_dict[iname] 463 if pyclass is not None: 464 push('<ul>instance attributes:') 465 push('<li><a href="%s">pyclass</a>: instances serializable to XML<br/></li>'\ 466 %types_dict[iname]) 467 push('</ul>') 468 else: 469 push('class <a href="%s">%s</a>: TODO not sure what this is<br/>' %(fname,iname)) 470 471 contents = ''.join(contents) 472 return self.section(title, '#000000', '#ffc8d8', contents, 3, doc) 473 474 doclass = pydoc.HTMLDoc.docclass 475 pydoc.HTMLDoc.docclass = strongarm 476 477 try: 478 page = pydoc.html.page(pydoc.describe(object), pydoc.html.document(object, name)) 479 file = open(name, 'w') 480 file.write(page) 481 file.close() 482 except (ImportError, pydoc.ErrorDuringImport), value: 483 log.debug(str(value)) 484 485 pydoc.HTMLDoc.docclass = doclass 486 487 488 489def _writebrokedoc(doc, ex, name, forceload=0): 490 try: 491 fname = os.path.join(doc, name + '.html') 492 page = pydoc.html.page(pydoc.describe(ex), pydoc.html.document(str(ex), fname)) 493 file = open(fname, 'w') 494 file.write(page) 495 file.close() 496 except (ImportError, pydoc.ErrorDuringImport), value: 497 log.debug(str(value)) 498 499 return name + '.html' 500 501def _writepydoc(doc, *args): 502 """create pydoc html pages 503 doc -- destination directory for documents 504 *args -- modules run thru pydoc 505 """ 506 ok = True 507 if not os.path.isdir(doc): 508 os.makedirs(doc) 509 510 if os.path.curdir not in sys.path: 511 sys.path.append(os.path.curdir) 512 513 for f in args: 514 if f.startswith('./'): f = f[2:] 515 name = os.path.sep.join(f.strip('.py').split(os.path.sep)) 516 try: 517 e = __import__(name) 518 except Exception,ex: 519 raise 520# _writebrokedoc(doc, ex, name) 521# continue 522 523 if name.endswith('_client'): 524 _writeclientdoc(doc, e) 525 continue 526 527 if name.endswith('_types'): 528 _writetypesdoc(doc, e) 529 continue 530 531 try: 532 _writedoc(doc, e) 533 except IndexError,ex: 534 _writebrokedoc(doc, ex, name) 535 continue 536 537 538