00001
00002
00003
00004
00005
00006
00007
00008
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
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
00056 self._url = url
00057 self._kw = kw
00058
00059
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
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
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
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(':', '-')
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
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
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(':', '-')
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
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
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
00212
00213
00214
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:
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
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
00271
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):
00308 cls._nullpyclass(typecode.ofwhat)
00309 else:
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)