2 ################################################################################
3 # Copyright (c) 2003, Pfizer
4 # Copyright (c) 2001, Cayce Ullman.
5 # Copyright (c) 2001, Brian Matthews.
9 # Redistribution and use in source and binary forms, with or without
10 # modification, are permitted provided that the following conditions are met:
11 # Redistributions of source code must retain the above copyright notice, this
12 # list of conditions and the following disclaimer.
14 # Redistributions in binary form must reproduce the above copyright notice,
15 # this list of conditions and the following disclaimer in the documentation
16 # and/or other materials provided with the distribution.
18 # Neither the name of actzero, inc. nor the names of its contributors may
19 # be used to endorse or promote products derived from this software without
20 # specific prior written permission.
22 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 # ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR
26 # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
27 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
29 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
31 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33 ################################################################################
36 ident = '$Id: SOAPBuilder.py 1498 2010-03-12 02:13:19Z pooryorick $'
37 from version import __version__
40 from wstools.XMLname import toXMLname, fromXMLname
44 from Config import Config
48 # Test whether this Python version has Types.BooleanType
49 # If it doesn't have it, then False and True are serialized as integers
52 pythonHasBooleanType = 1
54 pythonHasBooleanType = 0
56 ################################################################################
58 ################################################################################
60 _xml_top = '<?xml version="1.0"?>\n'
61 _xml_enc_top = '<?xml version="1.0" encoding="%s"?>\n'
62 _env_top = ( '%(ENV_T)s:Envelope\n' + \
63 ' %(ENV_T)s:encodingStyle="%(ENC)s"\n' ) % \
65 _env_bot = '</%(ENV_T)s:Envelope>\n' % NS.__dict__
67 # Namespaces potentially defined in the Envelope tag.
69 _env_ns = {NS.ENC: NS.ENC_T, NS.ENV: NS.ENV_T,
70 NS.XSD: NS.XSD_T, NS.XSD2: NS.XSD2_T, NS.XSD3: NS.XSD3_T,
71 NS.XSI: NS.XSI_T, NS.XSI2: NS.XSI2_T, NS.XSI3: NS.XSI3_T}
73 def __init__(self, args = (), kw = {}, method = None, namespace = None,
74 header = None, methodattrs = None, envelope = 1, encoding = 'UTF-8',
75 use_refs = 0, config = Config, noroot = 0):
77 # Test the encoding, raising an exception if it's not known
83 self.envelope = envelope
84 self.encoding = encoding
86 self.namespace = namespace
88 self.methodattrs= methodattrs
89 self.use_refs = use_refs
100 self.body = not isinstance(args, bodyType)
104 if Config.debug: print "In build."
107 # Cache whether typing is on or not
108 typed = self.config.typed
112 self.dump(self.header, "Header", typed = typed)
113 #self.header = None # Wipe it out so no one is using it.
116 # Call genns to record that we've used SOAP-ENV.
118 body_ns = self.genns(ns_map, NS.ENV)[0]
119 self.out.append("<%sBody>\n" % body_ns)
122 # Save the NS map so that it can be restored when we
123 # fall out of the scope of the method definition
124 save_ns_map = ns_map.copy()
128 for (k, v) in self.methodattrs.items():
129 a += ' %s="%s"' % (k, v)
131 if self.namespace: # Use the namespace info handed to us
132 methodns, n = self.genns(ns_map, self.namespace)
136 self.out.append('<%s%s%s%s%s>\n' % (
137 methodns, self.method, n, a, self.genroot(ns_map)))
140 if type(self.args) != TupleType:
146 self.dump(i, typed = typed, ns_map = ns_map)
148 if hasattr(self.config, "argsOrdering") and self.config.argsOrdering.has_key(self.method):
149 for k in self.config.argsOrdering.get(self.method):
150 self.dump(self.kw.get(k), k, typed = typed, ns_map = ns_map)
152 for (k, v) in self.kw.items():
153 self.dump(v, k, typed = typed, ns_map = ns_map)
155 except RecursionError:
156 if self.use_refs == 0:
158 b = SOAPBuilder(args = self.args, kw = self.kw,
159 method = self.method, namespace = self.namespace,
160 header = self.header, methodattrs = self.methodattrs,
161 envelope = self.envelope, encoding = self.encoding,
162 use_refs = 1, config = self.config)
167 self.out.append("</%s%s>\n" % (methodns, self.method))
168 # End of the method definition; drop any local namespaces
173 # dump may add to self.multirefs, but the for loop will keep
174 # going until it has used all of self.multirefs, even those
175 # entries added while in the loop.
179 for obj, tag in self.multirefs:
180 self.dump(obj, tag, typed = typed, ns_map = ns_map)
182 self.out.append("</%sBody>\n" % body_ns)
186 e = map (lambda ns: ' xmlns:%s="%s"\n' % (ns[1], ns[0]),
189 self.out = ['<', self._env_top] + e + ['>\n'] + \
193 if self.encoding != None:
194 self.out.insert(0, self._xml_enc_top % self.encoding)
195 return ''.join(self.out).encode(self.encoding)
197 self.out.insert(0, self._xml_top)
198 return ''.join(self.out)
201 if Config.debug: print "In gentag."
203 return "v%d" % self.tcounter
205 def genns(self, ns_map, nsURI):
209 if type(nsURI) == TupleType: # already a tuple
213 ns, nsURI = None, nsURI[0]
217 if ns_map.has_key(nsURI):
218 return (ns_map[nsURI] + ':', '')
220 if self._env_ns.has_key(nsURI):
221 ns = self.envns[nsURI] = ns_map[nsURI] = self._env_ns[nsURI]
222 return (ns + ':', '')
225 ns = "ns%d" % self.ncounter
228 if self.config.buildWithNamespacePrefix:
229 return (ns + ':', ' xmlns:%s="%s"' % (ns, nsURI))
231 return ('', ' xmlns="%s"' % (nsURI))
233 def genroot(self, ns_map):
240 ns, n = self.genns(ns_map, NS.ENC)
241 return ' %sroot="%d"%s' % (ns, not self.multis, n)
243 # checkref checks an element to see if it needs to be encoded as a
244 # multi-reference element or not. If it returns None, the element has
245 # been handled and the caller can continue with subsequent elements.
246 # If it returns a string, the string should be included in the opening
247 # tag of the marshaled element.
249 def checkref(self, obj, tag, ns_map):
253 if not self.ids.has_key(id(obj)):
254 n = self.ids[id(obj)] = self.icounter
255 self.icounter = n + 1
257 if self.use_refs == 0:
261 return ' id="i%d"' % n
263 self.multirefs.append((obj, tag))
265 if self.use_refs == 0:
266 raise RecursionError, "Cannot serialize recursive object"
268 n = self.ids[id(obj)]
270 if self.multis and self.depth == 2:
271 return ' id="i%d"' % n
273 self.out.append('<%s href="#i%d"%s/>\n' %
274 (tag, n, self.genroot(ns_map)))
279 def dump(self, obj, tag = None, typed = 1, ns_map = {}):
280 if Config.debug: print "In dump.", "obj=", obj
281 ns_map = ns_map.copy()
284 if type(tag) not in (NoneType, StringType, UnicodeType):
285 raise KeyError, "tag must be a string or None"
287 self.dump_dispatch(obj, tag, typed, ns_map)
291 def dumper(self, nsURI, obj_type, obj, tag, typed = 1, ns_map = {},
292 rootattr = '', id = '',
293 xml = '<%(tag)s%(type)s%(id)s%(attrs)s%(root)s>%(data)s</%(tag)s>\n'):
294 if Config.debug: print "In dumper."
297 nsURI = self.config.typesNamespaceURI
299 tag = tag or self.gentag()
301 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
304 if typed and obj_type:
305 ns, n = self.genns(ns_map, nsURI)
306 ins = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
307 t = ' %stype="%s%s"%s' % (ins, ns, obj_type, n)
309 try: a = obj._marshalAttrs(ns_map, self)
312 try: data = obj._marshalData()
314 if (obj_type != "string"): # strings are already encoded
315 data = cgi.escape(str(obj))
321 return xml % {"tag": tag, "type": t, "data": data, "root": rootattr,
322 "id": id, "attrs": a}
324 def dump_float(self, obj, tag, typed = 1, ns_map = {}):
325 if Config.debug: print "In dump_float."
326 tag = tag or self.gentag()
328 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
330 if Config.strict_range:
333 if fpconst.isPosInf(obj):
335 elif fpconst.isNegInf(obj):
337 elif fpconst.isNaN(obj):
342 # Note: python 'float' is actually a SOAP 'double'.
343 self.out.append(self.dumper(
344 None, "double", obj, tag, typed, ns_map, self.genroot(ns_map)))
346 def dump_int(self, obj, tag, typed = 1, ns_map = {}):
347 if Config.debug: print "In dump_int."
348 self.out.append(self.dumper(None, 'integer', obj, tag, typed,
349 ns_map, self.genroot(ns_map)))
351 def dump_bool(self, obj, tag, typed = 1, ns_map = {}):
352 if Config.debug: print "In dump_bool."
353 self.out.append(self.dumper(None, 'boolean', obj, tag, typed,
354 ns_map, self.genroot(ns_map)))
356 def dump_string(self, obj, tag, typed = 0, ns_map = {}):
357 if Config.debug: print "In dump_string."
358 tag = tag or self.gentag()
359 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
361 id = self.checkref(obj, tag, ns_map)
365 try: data = obj._marshalData()
368 self.out.append(self.dumper(None, "string", cgi.escape(data), tag,
369 typed, ns_map, self.genroot(ns_map), id))
371 dump_str = dump_string # For Python 2.2+
372 dump_unicode = dump_string
374 def dump_None(self, obj, tag, typed = 0, ns_map = {}):
375 if Config.debug: print "In dump_None."
376 tag = tag or self.gentag()
377 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
378 ns = self.genns(ns_map, self.config.schemaNamespaceURI)[0]
380 self.out.append('<%s %snull="1"%s/>\n' %
381 (tag, ns, self.genroot(ns_map)))
383 dump_NoneType = dump_None # For Python 2.2+
385 def dump_list(self, obj, tag, typed = 1, ns_map = {}):
386 if Config.debug: print "In dump_list.", "obj=", obj
387 tag = tag or self.gentag()
388 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
390 if type(obj) == InstanceType:
396 id = self.checkref(obj, tag, ns_map)
404 # preserve type if present
405 if getattr(obj,"_typed",None) and getattr(obj,"_type",None):
406 if getattr(obj, "_complexType", None):
407 sample = typedArrayType(typed=obj._type,
408 complexType = obj._complexType)
409 sample._typename = obj._type
410 if not getattr(obj,"_ns",None): obj._ns = NS.URN
412 sample = typedArrayType(typed=obj._type)
414 sample = structType()
417 # First scan list to see if all are the same type
422 if type(sample) != type(i) or \
423 (type(sample) == InstanceType and \
424 sample.__class__ != i.__class__):
430 if (isinstance(sample, structType)) or \
431 type(sample) == DictType or \
432 (isinstance(sample, anyType) and \
433 (getattr(sample, "_complexType", None) and \
434 sample._complexType)): # force to urn struct
436 tns = obj._ns or NS.URN
440 ns, ndecl = self.genns(ns_map, tns)
443 typename = sample._typename
445 typename = "SOAPStruct"
449 elif isinstance(sample, anyType):
450 ns = sample._validNamespaceURI(self.config.typesNamespaceURI,
451 self.config.strictNamespaces)
453 ns, ndecl = self.genns(ns_map, ns)
454 t = ns + str(sample._type)
458 typename = type(sample).__name__
461 if type(sample) == StringType: typename = 'string'
463 # HACK: unicode is a SOAP string
464 if type(sample) == UnicodeType: typename = 'string'
466 # HACK: python 'float' is actually a SOAP 'double'.
467 if typename=="float": typename="double"
469 ns_map, self.config.typesNamespaceURI)[0] + typename
472 t = self.genns(ns_map, self.config.typesNamespaceURI)[0] + \
475 try: a = obj._marshalAttrs(ns_map, self)
478 ens, edecl = self.genns(ns_map, NS.ENC)
479 ins, idecl = self.genns(ns_map, self.config.schemaNamespaceURI)
483 '<%s %sarrayType="%s[%d]" %stype="%sArray"%s%s%s%s%s%s>\n' %
484 (tag, ens, t, len(data), ins, ens, ndecl, edecl, idecl,
485 self.genroot(ns_map), id, a))
488 try: elemsname = obj._elemsname
489 except: elemsname = "item"
493 if isinstance(data, (list, tuple, arrayType)):
496 should_drill = not same_type
499 self.dump(i, elemsname, should_drill, ns_map)
501 if typed: self.out.append('</%s>\n' % tag)
503 dump_tuple = dump_list
505 def dump_exception(self, obj, tag, typed = 0, ns_map = {}):
506 if isinstance(obj, faultType): # Fault
507 cns, cdecl = self.genns(ns_map, NS.ENC)
508 vns, vdecl = self.genns(ns_map, NS.ENV)
509 self.out.append('<%sFault %sroot="1"%s%s>' % (vns, cns, vdecl, cdecl))
510 self.dump(obj.faultcode, "faultcode", typed, ns_map)
511 self.dump(obj.faultstring, "faultstring", typed, ns_map)
512 if hasattr(obj, "detail"):
513 self.dump(obj.detail, "detail", typed, ns_map)
514 self.out.append("</%sFault>\n" % vns)
516 def dump_dictionary(self, obj, tag, typed = 1, ns_map = {}):
517 if Config.debug: print "In dump_dictionary."
518 tag = tag or self.gentag()
519 tag = toXMLname(tag) # convert from SOAP 1.2 XML name encoding
521 id = self.checkref(obj, tag, ns_map)
525 try: a = obj._marshalAttrs(ns_map, self)
528 self.out.append('<%s%s%s%s>\n' %
529 (tag, id, a, self.genroot(ns_map)))
531 for (k, v) in obj.items():
533 self.dump(v, k, 1, ns_map)
535 self.out.append('</%s>\n' % tag)
537 dump_dict = dump_dictionary # For Python 2.2+
539 def dump_dispatch(self, obj, tag, typed = 1, ns_map = {}):
541 # If it has a name use it.
542 if isinstance(obj, anyType) and obj._name:
547 # watch out for order!
549 (Exception, self.dump_exception),
550 (arrayType, self.dump_list),
551 (basestring, self.dump_string),
552 (NoneType, self.dump_None),
553 (bool, self.dump_bool),
554 (int, self.dump_int),
555 (long, self.dump_int),
556 (list, self.dump_list),
557 (tuple, self.dump_list),
558 (dict, self.dump_dictionary),
559 (float, self.dump_float),
561 for dtype, func in dumpmap:
562 if isinstance(obj, dtype):
563 func(obj, tag, typed, ns_map)
566 r = self.genroot(ns_map)
568 try: a = obj._marshalAttrs(ns_map, self)
571 if isinstance(obj, voidType): # void
572 self.out.append("<%s%s%s></%s>\n" % (tag, a, r, tag))
574 id = self.checkref(obj, tag, ns_map)
578 if isinstance(obj, structType):
579 # Check for namespace
581 ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
582 self.config.strictNamespaces)
584 ns, ndecl = self.genns(ns_map, ns)
586 self.out.append("<%s%s%s%s%s>\n" % (tag, ndecl, id, a, r))
588 keylist = obj.__dict__.keys()
590 # first write out items with order information
591 if hasattr(obj, '_keyord'):
592 for i in range(len(obj._keyord)):
593 self.dump(obj._aslist(i), obj._keyord[i], 1, ns_map)
594 keylist.remove(obj._keyord[i])
596 # now write out the rest
599 self.dump(getattr(obj,k), k, 1, ns_map)
601 if isinstance(obj, bodyType):
604 for v, k in self.multirefs:
605 self.dump(v, k, typed = typed, ns_map = ns_map)
607 self.out.append('</%s>\n' % tag)
609 elif isinstance(obj, anyType):
613 ns = obj._validNamespaceURI(self.config.typesNamespaceURI,
614 self.config.strictNamespaces)
616 ons, ondecl = self.genns(ns_map, ns)
617 ins, indecl = self.genns(ns_map,
618 self.config.schemaNamespaceURI)
619 t = ' %stype="%s%s"%s%s' % \
620 (ins, ons, obj._type, ondecl, indecl)
622 self.out.append('<%s%s%s%s%s>%s</%s>\n' %
623 (tag, t, id, a, r, obj._marshalData(), tag))
626 self.out.append('<%s%s%s>\n' % (tag, id, r))
628 d1 = getattr(obj, '__dict__', None)
632 self.dump(v, k, 1, ns_map)
634 self.out.append('</%s>\n' % tag)
638 ################################################################################
639 # SOAPBuilder's more public interface
640 ################################################################################
642 def buildSOAP(args=(), kw={}, method=None, namespace=None,
643 header=None, methodattrs=None, envelope=1, encoding='UTF-8',
644 config=Config, noroot = 0):
645 t = SOAPBuilder(args=args, kw=kw, method=method, namespace=namespace,
646 header=header, methodattrs=methodattrs,envelope=envelope,
647 encoding=encoding, config=config,noroot=noroot)