1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
61 """
62 @param wsdl: A wsdl.
63 @type wsdl: L{wsdl.Definitions}
64 """
65 self.wsdl = wsdl
66 self.multiref = MultiRef()
67
70
73
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
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
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
129
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
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
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
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
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
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
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
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
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
488
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
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
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
526
529
532
533 - def resolve(self, nobuiltin=False):
534 if nobuiltin and self.__resolved.builtin():
535 return self
536 else:
537 return self.__resolved
538