Add arrays support in soap
This commit is contained in:
@@ -151,7 +151,8 @@ class WSRoot(object):
|
||||
|
||||
def _select_protocol(self, request):
|
||||
log.debug("Selecting a protocol for the following request :\n"
|
||||
"headers: %s\nbody: %s", request.headers, request.body)
|
||||
"headers: %s\nbody: %s", request.headers,
|
||||
len(request.body) > 512 and request.body[:512] or request.body)
|
||||
protocol = None
|
||||
if 'wsmeproto' in request.params:
|
||||
protocol = self.protocols[request.params['wsmeproto']]
|
||||
|
||||
@@ -46,6 +46,37 @@ type_registry = {
|
||||
wsme.types.binary: "xsd:base64Binary",
|
||||
}
|
||||
|
||||
array_registry = {
|
||||
basestring: "String_Array",
|
||||
str: "String_Array",
|
||||
unicode: "String_Array",
|
||||
int: "Int_Array",
|
||||
long: "Long_Array",
|
||||
float: "Float_Array",
|
||||
bool: "Boolean_Array",
|
||||
}
|
||||
|
||||
|
||||
def soap_array(datatype):
|
||||
if datatype in array_registry:
|
||||
return array_registry[datatype]
|
||||
return 'types:' + datatype.__name__ + '_Array'
|
||||
|
||||
|
||||
def soap_type(datatype):
|
||||
if type(datatype) == list:
|
||||
return soap_array(datatype[0])
|
||||
if datatype in type_registry:
|
||||
return type_registry[datatype]
|
||||
if wsme.types.iscomplex(datatype):
|
||||
return "types:%s" % datatype.__name__
|
||||
|
||||
|
||||
def soap_fname(funcdef):
|
||||
return "%s%s" % (
|
||||
"".join((i.capitalize() for i in funcdef.path)),
|
||||
funcdef.name.capitalize())
|
||||
|
||||
|
||||
def make_soap_element(datatype, tag, value):
|
||||
el = Element(tag)
|
||||
@@ -67,21 +98,36 @@ def tosoap(datatype, tag, value):
|
||||
response output"""
|
||||
return make_soap_element(datatype, tag, value)
|
||||
|
||||
|
||||
@tosoap.when_type(list)
|
||||
def array_tosoap(datatype, tag, value):
|
||||
el = Element(tag)
|
||||
el(**{'xsi:type': soap_array(datatype[0])})
|
||||
if value is None:
|
||||
el(**{'xsi:nil': 'true'})
|
||||
for item in value:
|
||||
el.append(tosoap(datatype[0], 'item', item))
|
||||
return el
|
||||
|
||||
|
||||
@tosoap.when_object(datetime.datetime)
|
||||
def datetime_tosoap(datatype, tag, value):
|
||||
return make_soap_element(datatype, tag,
|
||||
value is not None and value.isoformat() or None)
|
||||
|
||||
|
||||
@tosoap.when_object(wsme.types.binary)
|
||||
def binary_tosoap(datatype, tag, value):
|
||||
return make_soap_element(datatype, tag,
|
||||
value is not None and base64.encodestring(value)
|
||||
or None)
|
||||
|
||||
|
||||
@tosoap.when_object(None)
|
||||
def None_tosoap(datatype, tag, value):
|
||||
return make_soap_element(datatype, tag, None)
|
||||
|
||||
|
||||
@generic
|
||||
def fromsoap(datatype, el, ns):
|
||||
if el.get(nil_qn) == 'true':
|
||||
@@ -100,6 +146,11 @@ def fromsoap(datatype, el, ns):
|
||||
setattr(value, name, fromsoap(attr.datatype, child, ns))
|
||||
return value
|
||||
|
||||
|
||||
@fromsoap.when_type(list)
|
||||
def array_fromsoap(datatype, el, ns):
|
||||
return [fromsoap(datatype[0], child, ns) for child in el]
|
||||
|
||||
@fromsoap.when_object(datetime.date)
|
||||
def date_fromsoap(datatype, el, ns):
|
||||
if el.get(nil_qn) == 'true':
|
||||
@@ -108,6 +159,7 @@ def date_fromsoap(datatype, el, ns):
|
||||
raise exc.InvalidInput(el.tag, et.tostring(el))
|
||||
return parse_isodate(el.text)
|
||||
|
||||
|
||||
@fromsoap.when_object(datetime.time)
|
||||
def time_fromsoap(datatype, el, ns):
|
||||
if el.get(nil_qn) == 'true':
|
||||
@@ -116,6 +168,7 @@ def time_fromsoap(datatype, el, ns):
|
||||
raise exc.InvalidInput(el.tag, et.tostring(el))
|
||||
return parse_isotime(el.text)
|
||||
|
||||
|
||||
@fromsoap.when_object(datetime.datetime)
|
||||
def datetime_fromsoap(datatype, el, ns):
|
||||
if el.get(nil_qn) == 'true':
|
||||
@@ -124,6 +177,7 @@ def datetime_fromsoap(datatype, el, ns):
|
||||
raise exc.InvalidInput(el.tag, et.tostring(el))
|
||||
return parse_isodatetime(el.text)
|
||||
|
||||
|
||||
@fromsoap.when_object(wsme.types.binary)
|
||||
def binary_fromsoap(datatype, el, ns):
|
||||
if el.get(nil_qn) == 'true':
|
||||
@@ -132,6 +186,7 @@ def binary_fromsoap(datatype, el, ns):
|
||||
raise exc.InvalidInput(el.tag, et.tostring(el))
|
||||
return base64.decodestring(el.text)
|
||||
|
||||
|
||||
class SoapProtocol(object):
|
||||
name = 'SOAP'
|
||||
content_types = ['application/soap+xml']
|
||||
@@ -144,8 +199,7 @@ class SoapProtocol(object):
|
||||
|
||||
def __init__(self, tns=None,
|
||||
typenamespace=None,
|
||||
baseURL=None
|
||||
):
|
||||
baseURL=None):
|
||||
self.tns = tns
|
||||
self.typenamespace = typenamespace
|
||||
self.servicename = 'MyApp'
|
||||
@@ -155,13 +209,11 @@ class SoapProtocol(object):
|
||||
def get_name_mapping(self, service=None):
|
||||
if service not in self._name_mapping:
|
||||
self._name_mapping[service] = dict(
|
||||
(self.soap_fname(f), f.path + [f.name])
|
||||
for f in self.root.getapi() if service is None or (f.path and f.path[0] == service)
|
||||
)
|
||||
print self._name_mapping
|
||||
(soap_fname(f), f.path + [f.name])
|
||||
for f in self.root.getapi()
|
||||
if service is None or (f.path and f.path[0] == service))
|
||||
return self._name_mapping[service]
|
||||
|
||||
|
||||
def accept(self, req):
|
||||
if req.path.endswith('.wsdl'):
|
||||
return True
|
||||
@@ -181,7 +233,7 @@ class SoapProtocol(object):
|
||||
message = list(body)[0]
|
||||
fname = message.tag
|
||||
if fname.startswith('{%s}' % self.typenamespace):
|
||||
fname = fname[len(self.typenamespace)+2:]
|
||||
fname = fname[len(self.typenamespace) + 2:]
|
||||
mapping = self.get_name_mapping()
|
||||
if fname not in mapping:
|
||||
raise exc.UnknownFunction(fname)
|
||||
@@ -196,20 +248,19 @@ class SoapProtocol(object):
|
||||
return kw
|
||||
msg = request._wsme_soap_message
|
||||
parameters = msg.find('{%s}parameters' % self.typenamespace)
|
||||
print parameters
|
||||
if parameters:
|
||||
for param in parameters:
|
||||
name = param.tag[len(self.typenamespace)+2:]
|
||||
name = param.tag[len(self.typenamespace) + 2:]
|
||||
arg = funcdef.get_arg(name)
|
||||
value = fromsoap(arg.datatype, param, {
|
||||
'type': self.typenamespace,
|
||||
})
|
||||
kw[name] = value
|
||||
|
||||
|
||||
return kw
|
||||
|
||||
def soap_response(self, funcdef, result):
|
||||
r = Element(self.soap_fname(funcdef) + 'Response')
|
||||
r = Element(soap_fname(funcdef) + 'Response')
|
||||
r.append(tosoap(funcdef.return_type, 'result', result))
|
||||
return r
|
||||
|
||||
@@ -234,7 +285,7 @@ class SoapProtocol(object):
|
||||
return self.render_template('fault',
|
||||
typenamespace=self.typenamespace,
|
||||
**infos)
|
||||
|
||||
|
||||
@pexpose(contenttype="text/xml")
|
||||
def api_wsdl(self, service=None):
|
||||
if service is None:
|
||||
@@ -242,28 +293,18 @@ class SoapProtocol(object):
|
||||
else:
|
||||
servicename = self.servicename + service.capitalize()
|
||||
return self.render_template('wsdl',
|
||||
tns = self.tns,
|
||||
typenamespace = self.typenamespace,
|
||||
soapenc = self.ns['soapenc'],
|
||||
service_name = servicename,
|
||||
complex_types = (t() for t in wsme.types.complex_types),
|
||||
funclist = self.root.getapi(),
|
||||
arrays = [],
|
||||
list_attributes = wsme.types.list_attributes,
|
||||
baseURL = self.baseURL,
|
||||
soap_type = self.soap_type,
|
||||
soap_fname = self.soap_fname,
|
||||
tns=self.tns,
|
||||
typenamespace=self.typenamespace,
|
||||
soapenc=self.ns['soapenc'],
|
||||
service_name=servicename,
|
||||
complex_types=(t() for t in wsme.types.complex_types),
|
||||
funclist=self.root.getapi(),
|
||||
arrays=wsme.types.array_types,
|
||||
list_attributes=wsme.types.list_attributes,
|
||||
baseURL=self.baseURL,
|
||||
soap_array=soap_array,
|
||||
soap_type=soap_type,
|
||||
soap_fname=soap_fname,
|
||||
)
|
||||
|
||||
def soap_type(self, datatype):
|
||||
if datatype in type_registry:
|
||||
return type_registry[datatype]
|
||||
if wsme.types.iscomplex(datatype):
|
||||
return "types:%s" % datatype.__name__
|
||||
|
||||
def soap_fname(self, funcdef):
|
||||
return "%s%s" % (
|
||||
"".join((i.capitalize() for i in funcdef.path)),
|
||||
funcdef.name.capitalize())
|
||||
|
||||
register_protocol(SoapProtocol)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</py:for>
|
||||
|
||||
<!-- !Declare all of the array types. -->
|
||||
<xsd:complexType py:for="array in arrays" name="${soap_array(array, arrays)}">
|
||||
<xsd:complexType py:for="array in arrays" name="${soap_array(array)}">
|
||||
<xsd:sequence>
|
||||
<xsd:element maxOccurs="unbounded" name="item" nillable="true"
|
||||
type="${soap_type(array)}"/>
|
||||
|
||||
@@ -347,7 +347,7 @@ class ProtocolTestCase(unittest.TestCase):
|
||||
r = self.call('argtypes/setstrarray',
|
||||
value=(value, [str]),
|
||||
_rt=[str])
|
||||
assert r == value
|
||||
assert r == value, r
|
||||
|
||||
def test_setdatetimearray(self):
|
||||
value = [
|
||||
|
||||
@@ -25,6 +25,7 @@ faultdetail_qn = '{%s}detail' % soapenv_ns
|
||||
type_qn = '{%s}type' % xsi_ns
|
||||
nil_qn = '{%s}nil' % xsi_ns
|
||||
|
||||
|
||||
def build_soap_message(method, params=""):
|
||||
message = """<?xml version="1.0"?>
|
||||
<soap:Envelope
|
||||
@@ -57,15 +58,32 @@ python_types = {
|
||||
datetime.datetime: ('xsd:dateTime', datetime.datetime.isoformat),
|
||||
}
|
||||
|
||||
array_types = {
|
||||
basestring: "String_Array",
|
||||
str: "String_Array",
|
||||
unicode: "String_Array",
|
||||
int: "Int_Array",
|
||||
long: "Long_Array",
|
||||
float: "Float_Array",
|
||||
bool: "Boolean_Array",
|
||||
}
|
||||
|
||||
|
||||
def tosoap(tag, value):
|
||||
if isinstance(value, tuple):
|
||||
value, datatype = value
|
||||
else:
|
||||
datatype = type(value)
|
||||
print datatype
|
||||
el = et.Element(tag)
|
||||
if value is None:
|
||||
el.set('xsi:nil', True)
|
||||
elif isinstance(datatype, list):
|
||||
if datatype[0] in array_types:
|
||||
el.set('xsi:type', array_types[datatype[0]])
|
||||
else:
|
||||
el.set('xsi:type', 'types:' + datatype[0].__name__)
|
||||
for item in value:
|
||||
el.append(tosoap('item', (item, datatype[0])))
|
||||
elif datatype in python_types:
|
||||
stype, conv = python_types[datatype]
|
||||
el.text = conv(value)
|
||||
@@ -75,9 +93,10 @@ def tosoap(tag, value):
|
||||
for name, attr in datatype._wsme_attributes:
|
||||
if name in value:
|
||||
el.append(tosoap(name, (value[name], attr.datatype)))
|
||||
|
||||
|
||||
return el
|
||||
|
||||
|
||||
def read_bool(value):
|
||||
return value == 'true'
|
||||
|
||||
@@ -94,22 +113,22 @@ soap_types = {
|
||||
'xsd:base64Binary': base64.decodestring,
|
||||
}
|
||||
|
||||
|
||||
def fromsoap(el):
|
||||
if el.get(nil_qn) == 'true':
|
||||
return None
|
||||
t = el.get(type_qn)
|
||||
print t
|
||||
if t in soap_types:
|
||||
print t, el.text
|
||||
return soap_types[t](el.text)
|
||||
elif t and t.endswith('_Array'):
|
||||
return [fromsoap(i) for i in el]
|
||||
else:
|
||||
d = {}
|
||||
for child in el:
|
||||
name = child.tag
|
||||
assert name.startswith('{%s}' % typenamespace)
|
||||
name = name[len(typenamespace)+2:]
|
||||
name = name[len(typenamespace) + 2:]
|
||||
d[name] = fromsoap(child)
|
||||
print d
|
||||
return d
|
||||
|
||||
|
||||
@@ -121,9 +140,8 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
message = build_soap_message('Touch')
|
||||
print message
|
||||
res = self.app.post('/', message,
|
||||
headers={
|
||||
"Content-Type": "application/soap+xml; charset=utf-8"
|
||||
}, expect_errors=True)
|
||||
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
|
||||
expect_errors=True)
|
||||
print res.body
|
||||
assert res.status.startswith('200')
|
||||
|
||||
@@ -134,7 +152,7 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
el = et.Element('parameters')
|
||||
for key, value in kw.items():
|
||||
el.append(tosoap(key, value))
|
||||
|
||||
|
||||
params = et.tostring(el)
|
||||
else:
|
||||
params = ""
|
||||
@@ -142,15 +160,14 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
message = build_soap_message(methodname, params)
|
||||
print message
|
||||
res = self.app.post('/', message,
|
||||
headers={
|
||||
"Content-Type": "application/soap+xml; charset=utf-8"
|
||||
}, expect_errors=True)
|
||||
headers={"Content-Type": "application/soap+xml; charset=utf-8"},
|
||||
expect_errors=True)
|
||||
print "Status: ", res.status, "Received:", res.body
|
||||
|
||||
el = et.fromstring(res.body)
|
||||
body = el.find(body_qn)
|
||||
print body
|
||||
|
||||
|
||||
if res.status_int == 200:
|
||||
r = body.find('{%s}%sResponse' % (typenamespace, methodname))
|
||||
result = r.find('{%s}result' % typenamespace)
|
||||
@@ -162,14 +179,13 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
fault.find(faultcode_qn).text,
|
||||
fault.find(faultstring_qn).text,
|
||||
"")
|
||||
|
||||
|
||||
elif res.status_int == 500:
|
||||
fault = body.find(fault_qn)
|
||||
raise wsme.tests.protocol.CallException(
|
||||
fault.find(faultcode_qn).text,
|
||||
fault.find(faultstring_qn).text,
|
||||
fault.find(faultdetail_qn).text)
|
||||
|
||||
|
||||
if el.tag == 'error':
|
||||
raise wsme.tests.protocol.CallException(
|
||||
@@ -184,4 +200,6 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
def test_wsdl(self):
|
||||
res = self.app.get('/api.wsdl')
|
||||
print res.body
|
||||
assert False
|
||||
assert res.body.find('NestedOuter_Array') != -1
|
||||
assert 'ReturntypesGetunicode' in res.body
|
||||
|
||||
Reference in New Issue
Block a user