Package suds :: Package bindings :: Module binding
[hide private]
[frames] | no frames]

Source Code for Module suds.bindings.binding

  1  # This program is free software; you can redistribute it and/or modify 
  2  # it under the terms of the (LGPL) GNU Lesser General Public License as 
  3  # published by the Free Software Foundation; either version 3 of the  
  4  # License, or (at your option) any later version. 
  5  # 
  6  # This program is distributed in the hope that it will be useful, 
  7  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
  8  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
  9  # GNU Library Lesser General Public License for more details at 
 10  # ( http://www.gnu.org/licenses/lgpl.html ). 
 11  # 
 12  # You should have received a copy of the GNU Lesser General Public License 
 13  # along with this program; if not, write to the Free Software 
 14  # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 
 15  # written by: Jeff Ortel ( jortel@redhat.com ) 
 16   
 17  """ 
 18  Provides classes for (WS) SOAP bindings. 
 19  """ 
 20   
 21  from logging import getLogger 
 22  from suds import * 
 23  from suds.sax import Namespace 
 24  from suds.sax.parser import Parser 
 25  from suds.sax.document import Document 
 26  from suds.sax.element import Element 
 27  from suds.sudsobject import Factory, Object 
 28  from suds.mx import Content 
 29  from suds.mx.literal import Literal as MxLiteral 
 30  from suds.umx.basic import Basic as UmxBasic 
 31  from suds.umx.typed import Typed as UmxTyped 
 32  from suds.bindings.multiref import MultiRef 
 33  from suds.xsd.query import TypeQuery, ElementQuery 
 34  from suds.xsd.sxbasic import Element as SchemaElement 
 35  from suds.options import Options 
 36  from suds.plugin import PluginContainer 
 37  from copy import deepcopy  
 38   
 39  log = getLogger(__name__) 
 40   
 41  envns = ('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/') 
 42   
 43   
44 -class Binding:
45 """ 46 The soap binding class used to process outgoing and imcoming 47 soap messages per the WSDL port binding. 48 @cvar replyfilter: The reply filter function. 49 @type replyfilter: (lambda s,r: r) 50 @ivar wsdl: The wsdl. 51 @type wsdl: L{suds.wsdl.Definitions} 52 @ivar schema: The collective schema contained within the wsdl. 53 @type schema: L{xsd.schema.Schema} 54 @ivar options: A dictionary options. 55 @type options: L{Options} 56 """ 57 58 replyfilter = (lambda s,r: r) 59
60 - def __init__(self, wsdl):
61 """ 62 @param wsdl: A wsdl. 63 @type wsdl: L{wsdl.Definitions} 64 """ 65 self.wsdl = wsdl 66 self.multiref = MultiRef()
67
68 - def schema(self):
69 return self.wsdl.schema
70
71 - def options(self):
72 return self.wsdl.options
73
74 - def unmarshaller(self, typed=True):
75 """ 76 Get the appropriate XML decoder. 77 @return: Either the (basic|typed) unmarshaller. 78 @rtype: L{UmxTyped} 79 """ 80 if typed: 81 return UmxTyped(self.schema()) 82 else: 83 return UmxBasic()
84
85 - def marshaller(self):
86 """ 87 Get the appropriate XML encoder. 88 @return: An L{MxLiteral} marshaller. 89 @rtype: L{MxLiteral} 90 """ 91 return MxLiteral(self.schema(), self.options().xstq)
92
93 - def param_defs(self, method):
94 """ 95 Get parameter definitions. 96 Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) 97 @param method: A servic emethod. 98 @type method: I{service.Method} 99 @return: A collection of parameter definitions 100 @rtype: [I{pdef},..] 101 """ 102 raise Exception, 'not implemented'
103
104 - def get_message(self, method, args, kwargs):
105 """ 106 Get the soap message for the specified method, args and soapheaders. 107 This is the entry point for creating the outbound soap message. 108 @param method: The method being invoked. 109 @type method: I{service.Method} 110 @param args: A list of args for the method invoked. 111 @type args: list 112 @param kwargs: Named (keyword) args for the method invoked. 113 @type kwargs: dict 114 @return: The soap envelope. 115 @rtype: L{Document} 116 """ 117 118 content = self.headercontent(method) 119 header = self.header(content) 120 content = self.bodycontent(method, args, kwargs) 121 body = self.body(content) 122 env = self.envelope(header, body) 123 if self.options().prefixes: 124 body.normalizePrefixes() 125 env.promotePrefixes() 126 else: 127 env.refitPrefixes() 128 return Document(env)
129
130 - def get_reply(self, method, reply):
131 """ 132 Process the I{reply} for the specified I{method} by sax parsing the I{reply} 133 and then unmarshalling into python object(s). 134 @param method: The name of the invoked method. 135 @type method: str 136 @param reply: The reply XML received after invoking the specified method. 137 @type reply: str 138 @return: The unmarshalled reply. The returned value is an L{Object} for a 139 I{list} depending on whether the service returns a single object or a 140 collection. 141 @rtype: tuple ( L{Element}, L{Object} ) 142 """ 143 reply = self.replyfilter(reply) 144 sax = Parser() 145 replyroot = sax.parse(string=reply) 146 plugins = PluginContainer(self.options().plugins) 147 plugins.message.parsed(reply=replyroot) 148 soapenv = replyroot.getChild('Envelope') 149 soapenv.promotePrefixes() 150 soapbody = soapenv.getChild('Body') 151 self.detect_fault(soapbody) 152 soapbody = self.multiref.process(soapbody) 153 nodes = self.replycontent(method, soapbody) 154 rtypes = self.returned_types(method) 155 if len(rtypes) > 1: 156 result = self.replycomposite(rtypes, nodes) 157 return (replyroot, result) 158 if len(rtypes) == 1: 159 if rtypes[0].unbounded(): 160 result = self.replylist(rtypes[0], nodes) 161 return (replyroot, result) 162 if len(nodes): 163 unmarshaller = self.unmarshaller() 164 resolved = rtypes[0].resolve(nobuiltin=True) 165 result = unmarshaller.process(nodes[0], resolved) 166 return (replyroot, result) 167 return (replyroot, None)
168
169 - def detect_fault(self, body):
170 """ 171 Detect I{hidden} soapenv:Fault element in the soap body. 172 @param body: The soap envelope body. 173 @type body: L{Element} 174 @raise WebFault: When found. 175 """ 176 fault = body.getChild('Fault', envns) 177 if fault is None: 178 return 179 unmarshaller = self.unmarshaller(False) 180 p = unmarshaller.process(fault) 181 if self.options().faults: 182 raise WebFault(p, fault) 183 return self
184 185
186 - def replylist(self, rt, nodes):
187 """ 188 Construct a I{list} reply. This mehod is called when it has been detected 189 that the reply is a list. 190 @param rt: The return I{type}. 191 @type rt: L{suds.xsd.sxbase.SchemaObject} 192 @param nodes: A collection of XML nodes. 193 @type nodes: [L{Element},...] 194 @return: A list of I{unmarshalled} objects. 195 @rtype: [L{Object},...] 196 """ 197 result = [] 198 resolved = rt.resolve(nobuiltin=True) 199 unmarshaller = self.unmarshaller() 200 for node in nodes: 201 sobject = unmarshaller.process(node, resolved) 202 result.append(sobject) 203 return result
204
205 - def replycomposite(self, rtypes, nodes):
206 """ 207 Construct a I{composite} reply. This method is called when it has been 208 detected that the reply has multiple root nodes. 209 @param rtypes: A list of known return I{types}. 210 @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...] 211 @param nodes: A collection of XML nodes. 212 @type nodes: [L{Element},...] 213 @return: The I{unmarshalled} composite object. 214 @rtype: L{Object},... 215 """ 216 dictionary = {} 217 for rt in rtypes: 218 dictionary[rt.name] = rt 219 unmarshaller = self.unmarshaller() 220 composite = Factory.object('reply') 221 for node in nodes: 222 tag = node.name 223 rt = dictionary.get(tag, None) 224 if rt is None: 225 if node.get('id') is None: 226 raise Exception('<%s/> not mapped to message part' % tag) 227 else: 228 continue 229 resolved = rt.resolve(nobuiltin=True) 230 sobject = unmarshaller.process(node, resolved) 231 value = getattr(composite, tag, None) 232 if value is None: 233 if rt.unbounded(): 234 value = [] 235 setattr(composite, tag, value) 236 value.append(sobject) 237 else: 238 setattr(composite, tag, sobject) 239 else: 240 if not isinstance(value, list): 241 value = [value,] 242 setattr(composite, tag, value) 243 value.append(sobject) 244 return composite
245
246 - def get_fault(self, reply):
247 """ 248 Extract the fault from the specified soap reply. If I{faults} is True, an 249 exception is raised. Otherwise, the I{unmarshalled} fault L{Object} is 250 returned. This method is called when the server raises a I{web fault}. 251 @param reply: A soap reply message. 252 @type reply: str 253 @return: A fault object. 254 @rtype: tuple ( L{Element}, L{Object} ) 255 """ 256 reply = self.replyfilter(reply) 257 sax = Parser() 258 faultroot = sax.parse(string=reply) 259 soapenv = faultroot.getChild('Envelope') 260 soapbody = soapenv.getChild('Body') 261 fault = soapbody.getChild('Fault') 262 unmarshaller = self.unmarshaller(False) 263 p = unmarshaller.process(fault) 264 if self.options().faults: 265 raise WebFault(p, faultroot) 266 return (faultroot, p.detail)
267
268 - def mkparam(self, method, pdef, object):
269 """ 270 Builds a parameter for the specified I{method} using the parameter 271 definition (pdef) and the specified value (object). 272 @param method: A method name. 273 @type method: str 274 @param pdef: A parameter definition. 275 @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) 276 @param object: The parameter value. 277 @type object: any 278 @return: The parameter fragment. 279 @rtype: L{Element} 280 """ 281 marshaller = self.marshaller() 282 content = \ 283 Content(tag=pdef[0], 284 value=object, 285 type=pdef[1], 286 real=pdef[1].resolve()) 287 return marshaller.process(content)
288
289 - def mkheader(self, method, hdef, object):
290 """ 291 Builds a soapheader for the specified I{method} using the header 292 definition (hdef) and the specified value (object). 293 @param method: A method name. 294 @type method: str 295 @param hdef: A header definition. 296 @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject}) 297 @param object: The header value. 298 @type object: any 299 @return: The parameter fragment. 300 @rtype: L{Element} 301 """ 302 marshaller = self.marshaller() 303 if isinstance(object, (list, tuple)): 304 tags = [] 305 for item in object: 306 tags.append(self.mkheader(method, hdef, item)) 307 return tags 308 content = Content(tag=hdef[0], value=object, type=hdef[1]) 309 return marshaller.process(content)
310
311 - def envelope(self, header, body):
312 """ 313 Build the B{<Envelope/>} for an soap outbound message. 314 @param header: The soap message B{header}. 315 @type header: L{Element} 316 @param body: The soap message B{body}. 317 @type body: L{Element} 318 @return: The soap envelope containing the body and header. 319 @rtype: L{Element} 320 """ 321 env = Element('Envelope', ns=envns) 322 env.addPrefix(Namespace.xsins[0], Namespace.xsins[1]) 323 env.append(header) 324 env.append(body) 325 return env
326
327 - def header(self, content):
328 """ 329 Build the B{<Body/>} for an soap outbound message. 330 @param content: The header content. 331 @type content: L{Element} 332 @return: the soap body fragment. 333 @rtype: L{Element} 334 """ 335 header = Element('Header', ns=envns) 336 header.append(content) 337 return header
338
339 - def bodycontent(self, method, args, kwargs):
340 """ 341 Get the content for the soap I{body} node. 342 @param method: A service method. 343 @type method: I{service.Method} 344 @param args: method parameter values 345 @type args: list 346 @param kwargs: Named (keyword) args for the method invoked. 347 @type kwargs: dict 348 @return: The xml content for the <body/> 349 @rtype: [L{Element},..] 350 """ 351 raise Exception, 'not implemented'
352
353 - def headercontent(self, method):
354 """ 355 Get the content for the soap I{Header} node. 356 @param method: A service method. 357 @type method: I{service.Method} 358 @return: The xml content for the <body/> 359 @rtype: [L{Element},..] 360 """ 361 n = 0 362 content = [] 363 wsse = self.options().wsse 364 if wsse is not None: 365 content.append(wsse.xml()) 366 headers = self.options().soapheaders 367 if not isinstance(headers, (tuple,list,dict)): 368 headers = (headers,) 369 if len(headers) == 0: 370 return content 371 pts = self.headpart_types(method) 372 if isinstance(headers, (tuple,list)): 373 for header in headers: 374 if isinstance(header, Element): 375 content.append(deepcopy(header)) 376 continue 377 if len(pts) == n: break 378 h = self.mkheader(method, pts[n], header) 379 ns = pts[n][1].namespace('ns0') 380 h.setPrefix(ns[0], ns[1]) 381 content.append(h) 382 n += 1 383 else: 384 for pt in pts: 385 header = headers.get(pt[0]) 386 if header is None: 387 continue 388 h = self.mkheader(method, pt, header) 389 ns = pt[1].namespace('ns0') 390 h.setPrefix(ns[0], ns[1]) 391 content.append(h) 392 return content
393
394 - def replycontent(self, method, body):
395 """ 396 Get the reply body content. 397 @param method: A service method. 398 @type method: I{service.Method} 399 @param body: The soap body 400 @type body: L{Element} 401 @return: the body content 402 @rtype: [L{Element},...] 403 """ 404 raise Exception, 'not implemented'
405
406 - def body(self, content):
407 """ 408 Build the B{<Body/>} for an soap outbound message. 409 @param content: The body content. 410 @type content: L{Element} 411 @return: the soap body fragment. 412 @rtype: L{Element} 413 """ 414 body = Element('Body', ns=envns) 415 body.append(content) 416 return body
417
418 - def bodypart_types(self, method, input=True):
419 """ 420 Get a list of I{parameter definitions} (pdef) defined for the specified method. 421 Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) 422 @param method: A service method. 423 @type method: I{service.Method} 424 @param input: Defines input/output message. 425 @type input: boolean 426 @return: A list of parameter definitions 427 @rtype: [I{pdef},] 428 """ 429 result = [] 430 if input: 431 parts = method.soap.input.body.parts 432 else: 433 parts = method.soap.output.body.parts 434 for p in parts: 435 if p.element is not None: 436 query = ElementQuery(p.element) 437 else: 438 query = TypeQuery(p.type) 439 pt = query.execute(self.schema()) 440 if pt is None: 441 raise TypeNotFound(query.ref) 442 if p.type is not None: 443 pt = PartElement(p.name, pt) 444 if input: 445 if pt.name is None: 446 result.append((p.name, pt)) 447 else: 448 result.append((pt.name, pt)) 449 else: 450 result.append(pt) 451 return result
452
453 - def headpart_types(self, method, input=True):
454 """ 455 Get a list of I{parameter definitions} (pdef) defined for the specified method. 456 Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject}) 457 @param method: A service method. 458 @type method: I{service.Method} 459 @param input: Defines input/output message. 460 @type input: boolean 461 @return: A list of parameter definitions 462 @rtype: [I{pdef},] 463 """ 464 result = [] 465 if input: 466 headers = method.soap.input.headers 467 else: 468 headers = method.soap.output.headers 469 for header in headers: 470 part = header.part 471 if part.element is not None: 472 query = ElementQuery(part.element) 473 else: 474 query = TypeQuery(part.type) 475 pt = query.execute(self.schema()) 476 if pt is None: 477 raise TypeNotFound(query.ref) 478 if part.type is not None: 479 pt = PartElement(part.name, pt) 480 if input: 481 if pt.name is None: 482 result.append((part.name, pt)) 483 else: 484 result.append((pt.name, pt)) 485 else: 486 result.append(pt) 487 return result
488
489 - def returned_types(self, method):
490 """ 491 Get the L{xsd.sxbase.SchemaObject} returned by the I{method}. 492 @param method: A service method. 493 @type method: I{service.Method} 494 @return: The name of the type return by the method. 495 @rtype: [I{rtype},..] 496 """ 497 result = [] 498 for rt in self.bodypart_types(method, input=False): 499 result.append(rt) 500 return result
501 502
503 -class PartElement(SchemaElement):
504 """ 505 A part used to represent a message part when the part 506 references a schema type and thus assumes to be an element. 507 @ivar resolved: The part type. 508 @type resolved: L{suds.xsd.sxbase.SchemaObject} 509 """ 510
511 - def __init__(self, name, resolved):
512 """ 513 @param name: The part name. 514 @type name: str 515 @param resolved: The part type. 516 @type resolved: L{suds.xsd.sxbase.SchemaObject} 517 """ 518 root = Element('element', ns=Namespace.xsdns) 519 SchemaElement.__init__(self, resolved.schema, root) 520 self.__resolved = resolved 521 self.name = name 522 self.form_qualified = False
523
524 - def implany(self):
525 return self
526
527 - def optional(self):
528 return True
529
530 - def namespace(self, prefix=None):
531 return Namespace.default
532
533 - def resolve(self, nobuiltin=False):
534 if nobuiltin and self.__resolved.builtin(): 535 return self 536 else: 537 return self.__resolved
538