• Main Page
  • Related Pages
  • Modules
  • Namespaces
  • Data Structures
  • Files
  • File List
  • Globals

contrib/opal/ZSI/build/lib/ZSI/ServiceProxy.py

00001 # Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
00002 #
00003 # This software is subject to the provisions of the Zope Public License,
00004 # Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
00005 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
00006 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
00007 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
00008 # FOR A PARTICULAR PURPOSE.
00009 
00010 import weakref, re, os, sys
00011 from ConfigParser import SafeConfigParser as ConfigParser,\
00012     NoSectionError, NoOptionError
00013 from urlparse import urlparse
00014 
00015 from ZSI import TC
00016 from ZSI.client import _Binding
00017 from ZSI.generate import commands,containers
00018 from ZSI.schema import GED, GTD
00019 
00020 import wstools
00021 
00022 
00023 #url_to_mod = re.compile(r'<([^ \t\n\r\f\v:]+:)?include\s+location\s*=\s*"(\S+)"')
00024 def _urn_to_module(urn): return '%s_types' %re.sub(_urn_to_module.regex, '_', urn)
00025 _urn_to_module.regex = re.compile(r'[\W]')
00026     
00027 
00028 class ServiceProxy:
00029     """A ServiceProxy provides a convenient way to call a remote web
00030        service that is described with WSDL. The proxy exposes methods
00031        that reflect the methods of the remote web service."""
00032 
00033     def __init__(self, wsdl, url=None, service=None, port=None,
00034                  cachedir=os.path.join(os.path.expanduser('~'), '.zsi_service_proxy_dir'), 
00035                  asdict=True, lazy=False, pyclass=False, force=False, **kw):
00036         """
00037         Parameters:
00038            wsdl -- URL of WSDL.
00039            url -- override WSDL SOAP address location
00040            service -- service name or index
00041            port -- port name or index
00042            cachedir -- where to store generated files
00043            asdict -- use dicts, else use generated pyclass
00044            lazy -- use lazy typecode evaluation
00045            pyclass -- use pyclass_type metaclass adds properties, "new_", "set_, 
00046                "get_" methods for schema element and attribute declarations.
00047            force -- regenerate all WSDL code, write over cache.
00048            
00049         NOTE: all other **kw will be passed to the underlying 
00050         ZSI.client._Binding constructor.
00051            
00052         """
00053         self._asdict = asdict
00054         
00055         # client._Binding
00056         self._url = url
00057         self._kw = kw
00058         
00059         # WSDL
00060         self._wsdl = wstools.WSDLTools.WSDLReader().loadFromURL(wsdl)
00061         self._service = self._wsdl.services[service or 0]
00062         self.__doc__ = self._service.documentation
00063         self._port = self._service.ports[port or 0]
00064         self._name = self._service.name
00065         self._methods = {}
00066         self._cachedir = cachedir
00067         self._lazy = lazy
00068         self._pyclass = pyclass
00069         self._force = force
00070         
00071         # Set up rpc methods for service/port
00072         port = self._port
00073         binding = port.getBinding()
00074         portType = binding.getPortType()
00075         for port in self._service.ports:
00076             for item in port.getPortType().operations:
00077                 try:
00078                     callinfo = wstools.WSDLTools.callInfoFromWSDL(port, item.name)
00079                 except:
00080                     # ignore non soap-1.1  bindings
00081                     continue
00082                 
00083                 method = MethodProxy(self, callinfo)
00084                 setattr(self, item.name, method)
00085                 self._methods.setdefault(item.name, []).append(method)
00086        
00087         self._mod = self._load(wsdl)
00088         
00089     def _load(self, location):
00090         """
00091         location -- URL or file location
00092         isxsd -- is this a xsd file?
00093         """
00094         cachedir = self._cachedir
00095         # wsdl2py: deal with XML Schema
00096         if not os.path.isdir(cachedir): os.mkdir(cachedir)
00097     
00098         file = os.path.join(cachedir, '.cache')
00099         section = 'TYPES'
00100         cp = ConfigParser()
00101         try:
00102             cp.readfp(open(file, 'r'))
00103         except IOError:
00104             del cp;  cp = None
00105             
00106         option = location.replace(':', '-') # colons seem to screw up option
00107         if (not self._force and cp is not None and cp.has_section(section) and 
00108             cp.has_option(section, option)):
00109             types = cp.get(section, option)
00110         else:
00111             # dont do anything to anames
00112             if not self._pyclass:
00113                 containers.ContainerBase.func_aname = lambda instnc,n: str(n)
00114                 
00115             args = ['-o', cachedir, location]
00116             if self._lazy: args.insert(0, '-l')
00117             if self._pyclass: args.insert(0, '-b')
00118             files = commands.wsdl2py(args)
00119 
00120             if cp is None: cp = ConfigParser()
00121             if not cp.has_section(section): cp.add_section(section)
00122             types = filter(lambda f: f.endswith('_types.py'), files)[0]
00123             cp.set(section, option, types)
00124             cp.write(open(file, 'w'))
00125             
00126         if os.path.abspath(cachedir) not in sys.path:
00127             sys.path.append(os.path.abspath(cachedir))
00128             
00129         mod = os.path.split(types)[-1].rstrip('.py')
00130         return __import__(mod)
00131     
00132     def _load_schema(self, location, xml=None):
00133         """
00134         location -- location of schema, also used as a key
00135         xml -- optional string representation of schema
00136         """
00137         cachedir = self._cachedir
00138         # wsdl2py: deal with XML Schema
00139         if not os.path.isdir(cachedir): os.mkdir(cachedir)
00140     
00141         file = os.path.join(cachedir, '.cache')
00142         section = 'TYPES'
00143         cp = ConfigParser()
00144         try:
00145             cp.readfp(open(file, 'r'))
00146         except IOError:
00147             del cp;  cp = None
00148             
00149         option = location.replace(':', '-') # colons seem to screw up option
00150         if (cp is not None and cp.has_section(section) and 
00151             cp.has_option(section, option)):
00152             types = cp.get(section, option)
00153         else:
00154             # dont do anything to anames
00155             if not self._pyclass:
00156                 containers.ContainerBase.func_aname = lambda instnc,n: str(n)
00157                 
00158             from ZSI.wstools import XMLSchema
00159             reader = XMLSchema.SchemaReader(base_url=location)
00160             if xml is not None and isinstance(xml, basestring):
00161                 schema = reader.loadFromString(xml)
00162             elif xml is not None:
00163                 raise RuntimeError, 'Unsupported: XML must be string'
00164             elif not os.path.isfile(location):
00165                 schema = reader.loadFromURL(location)
00166             else:
00167                 schema = reader.reader.loadFromFile(location)
00168                 
00169             # TODO: change this to keyword list
00170             class options:
00171                 output_dir = cachedir
00172                 schema = True
00173                 simple_naming = False
00174                 address = False
00175                 lazy = self._lazy
00176                 complexType = self._pyclass
00177 
00178             schema.location = location
00179             files = commands._wsdl2py(options, schema)
00180             if cp is None: cp = ConfigParser()
00181             if not cp.has_section(section): cp.add_section(section)
00182             types = filter(lambda f: f.endswith('_types.py'), files)[0]
00183             cp.set(section, option, types)
00184             cp.write(open(file, 'w'))
00185             
00186         if os.path.abspath(cachedir) not in sys.path:
00187             sys.path.append(os.path.abspath(cachedir))
00188             
00189         mod = os.path.split(types)[-1].rstrip('.py')
00190         return __import__(mod)
00191             
00192     def _call(self, name, soapheaders):
00193         """return the Call to the named remote web service method.
00194         closure used to prevent multiple values for name and soapheaders 
00195         parameters 
00196         """
00197         
00198         def call_closure(*args, **kwargs):
00199             """Call the named remote web service method."""
00200             if len(args) and len(kwargs):
00201                 raise TypeError, 'Use positional or keyword argument only.'
00202                 
00203             if len(args) > 0:
00204                 raise TypeError, 'Not supporting SOAPENC:Arrays or XSD:List'
00205             
00206             if len(kwargs): 
00207                 args = kwargs
00208 
00209             callinfo = getattr(self, name).callinfo
00210     
00211             # go through the list of defined methods, and look for the one with
00212             # the same number of arguments as what was passed.  this is a weak
00213             # check that should probably be improved in the future to check the
00214             # types of the arguments to allow for polymorphism
00215             for method in self._methods[name]:
00216                 if len(method.callinfo.inparams) == len(kwargs):
00217                     callinfo = method.callinfo
00218     
00219             binding = _Binding(url=self._url or callinfo.location,
00220                               soapaction=callinfo.soapAction,
00221                               **self._kw)
00222     
00223             kw = dict(unique=True)
00224             if callinfo.use == 'encoded':
00225                 kw['unique'] = False
00226     
00227             if callinfo.style == 'rpc':
00228                 request = TC.Struct(None, ofwhat=[], 
00229                                  pname=(callinfo.namespace, name), **kw)
00230                 
00231                 response = TC.Struct(None, ofwhat=[], 
00232                                  pname=(callinfo.namespace, name+"Response"), **kw)
00233                 
00234                 if len(callinfo.getInParameters()) != len(args):
00235                     raise RuntimeError('expecting "%s" parts, got %s' %(
00236                            str(callinfo.getInParameters(), str(args))))
00237                 
00238                 for msg,pms in ((request,callinfo.getInParameters()), 
00239                                 (response,callinfo.getOutParameters())):
00240                     msg.ofwhat = []
00241                     for part in pms:
00242                         klass = GTD(*part.type)
00243                         if klass is None:
00244                             if part.type:
00245                                 klass = filter(lambda gt: part.type==gt.type,TC.TYPES)
00246                                 if len(klass) == 0:
00247                                     klass = filter(lambda gt: part.type[1]==gt.type[1],TC.TYPES)
00248                                     if not len(klass):klass = [TC.Any]
00249                                 if len(klass) > 1: #Enumerations, XMLString, etc
00250                                     klass = filter(lambda i: i.__dict__.has_key('type'), klass)
00251                                 klass = klass[0]
00252                             else:
00253                                 klass = TC.Any
00254                     
00255                         msg.ofwhat.append(klass(part.name))
00256                         
00257                     msg.ofwhat = tuple(msg.ofwhat)
00258                 if not args: args = {}
00259             else:
00260                 # Grab <part element> attribute
00261                 ipart,opart = callinfo.getInParameters(),callinfo.getOutParameters()
00262                 if ( len(ipart) != 1 or not ipart[0].element_type or 
00263                     ipart[0].type is None ):
00264                     raise RuntimeError, 'Bad Input Message "%s"' %callinfo.name
00265         
00266                 if ( len(opart) not in (0,1) or not opart[0].element_type or 
00267                     opart[0].type is None ):
00268                     raise RuntimeError, 'Bad Output Message "%s"' %callinfo.name
00269                 
00270 #                if ( len(args) > 1 ):
00271 #                    raise RuntimeError, 'Message has only one part:  %s' %str(args)
00272                 
00273                 ipart = ipart[0]
00274                 request,response = GED(*ipart.type),None
00275                 if opart: response = GED(*opart[0].type)
00276     
00277             msg = args
00278             if self._asdict: 
00279                 if not msg: msg = dict()
00280                 self._nullpyclass(request)
00281             elif request.pyclass is not None:
00282                 if type(args) is dict:
00283                     msg = request.pyclass()
00284                     msg.__dict__.update(args)
00285                 elif type(args) is list and len(args) == 1: 
00286                     msg = request.pyclass(args[0])
00287                 else: 
00288                     msg = request.pyclass()
00289                     
00290             binding.Send(None, None, msg,
00291                          requesttypecode=request,
00292                          soapheaders=soapheaders,
00293                          encodingStyle=callinfo.encodingStyle)
00294             
00295             if response is None: 
00296                 return None
00297             
00298             if self._asdict: self._nullpyclass(response)
00299             return binding.Receive(replytype=response,
00300                          encodingStyle=callinfo.encodingStyle)
00301             
00302         return call_closure
00303 
00304     def _nullpyclass(cls, typecode):
00305         typecode.pyclass = None
00306         if not hasattr(typecode, 'ofwhat'): return
00307         if type(typecode.ofwhat) not in (list,tuple): #Array
00308             cls._nullpyclass(typecode.ofwhat)
00309         else: #Struct/ComplexType
00310             for i in typecode.ofwhat: cls._nullpyclass(i)    
00311     _nullpyclass = classmethod(_nullpyclass)
00312 
00313 
00314 class MethodProxy:
00315     """ """
00316     def __init__(self, parent, callinfo):
00317         self.__name__ = callinfo.methodName
00318         self.__doc__ = callinfo.documentation
00319         self.callinfo = callinfo
00320         self.parent = weakref.ref(parent)
00321         self.soapheaders = []
00322 
00323     def __call__(self, *args, **kwargs):
00324         return self.parent()._call(self.__name__, self.soapheaders)(*args, **kwargs)
00325 
00326     def add_headers(self, **headers):
00327         """packing dicts into typecode pyclass, may fail if typecodes are
00328         used in the body (when asdict=True)
00329         """
00330         class _holder: pass
00331         def _remap(pyobj, **d):
00332             pyobj.__dict__ = d
00333             for k,v in pyobj.__dict__.items():
00334                 if type(v) is not dict: continue
00335                 pyobj.__dict__[k] = p = _holder()
00336                 _remap(p, **v)
00337     
00338         for k,v in headers.items():
00339             h = filter(lambda i: k in i.type, self.callinfo.inheaders)[0]
00340             if h.element_type != 1: 
00341                 raise RuntimeError, 'not implemented'
00342 
00343             typecode = GED(*h.type)
00344             if typecode is None: 
00345                 raise RuntimeError, 'no matching element for %s' %str(h.type)
00346 
00347             pyclass = typecode.pyclass
00348             if pyclass is None: 
00349                 raise RuntimeError, 'no pyclass for typecode %s' %str(h.type)
00350 
00351             if type(v) is not dict:
00352                 pyobj = pyclass(v)
00353             else:
00354                 pyobj = pyclass()
00355                 _remap(pyobj, **v)
00356 
00357             self.soapheaders.append(pyobj)

Generated on Wed Oct 20 2010 11:12:16 for APBS by  doxygen 1.7.2