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>&nbsp;</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