Making progress on the soap implementation. Will have to choose between Genshi and ElementTree though, it is getting anoying to juggle with both
This commit is contained in:
@@ -132,6 +132,7 @@ class WSRoot(object):
|
||||
if isinstance(protocol, str):
|
||||
protocol = registered_protocols[protocol]()
|
||||
self.protocols[protocol.name] = protocol
|
||||
protocol.root = weakref.proxy(self)
|
||||
|
||||
self._api = None
|
||||
|
||||
@@ -146,7 +147,7 @@ class WSRoot(object):
|
||||
protocol = self.protocols[request.params['wsmeproto']]
|
||||
else:
|
||||
for p in self.protocols.values():
|
||||
if p.accept(self, request):
|
||||
if p.accept(request):
|
||||
protocol = p
|
||||
break
|
||||
return protocol
|
||||
@@ -164,11 +165,12 @@ class WSRoot(object):
|
||||
log.error(msg)
|
||||
return res
|
||||
path = protocol.extract_path(request)
|
||||
if path is None:
|
||||
raise exc.ClientSideError(
|
||||
u'The %s protocol was unable to extract a function '
|
||||
u'path from the request' % protocol.name)
|
||||
func, funcdef = self._lookup_function(path)
|
||||
kw = protocol.read_arguments(request, funcdef)
|
||||
|
||||
if funcdef.protocol_specific:
|
||||
kw['root'] = self
|
||||
kw = protocol.read_arguments(funcdef, request)
|
||||
|
||||
result = func(**kw)
|
||||
|
||||
@@ -178,12 +180,15 @@ class WSRoot(object):
|
||||
res.body = result
|
||||
else:
|
||||
# TODO make sure result type == a._wsme_definition.return_type
|
||||
res.body = protocol.encode_result(result, funcdef)
|
||||
res.body = protocol.encode_result(funcdef, result)
|
||||
res_content_type = funcdef.contenttype
|
||||
except Exception, e:
|
||||
infos = self._format_exception(sys.exc_info())
|
||||
log.error(str(infos))
|
||||
res.status = 500
|
||||
if isinstance(e, exc.ClientSideError):
|
||||
res.status = 400
|
||||
else:
|
||||
res.status = 500
|
||||
res.body = protocol.encode_error(infos)
|
||||
|
||||
if res_content_type is None:
|
||||
@@ -237,6 +242,7 @@ class WSRoot(object):
|
||||
r = dict(faultcode="Client",
|
||||
faultstring=unicode(excinfo[1]))
|
||||
log.warning("Client-side error: %s" % r['faultstring'])
|
||||
r['debuginfo'] = None
|
||||
return r
|
||||
else:
|
||||
faultstring = str(excinfo[1])
|
||||
@@ -248,6 +254,8 @@ class WSRoot(object):
|
||||
r = dict(faultcode="Server", faultstring=faultstring)
|
||||
if self._debug:
|
||||
r['debuginfo'] = debuginfo
|
||||
else:
|
||||
r['debuginfo'] = None
|
||||
return r
|
||||
|
||||
def _html_format(self, content, content_types):
|
||||
|
||||
@@ -5,6 +5,9 @@ if '_' not in __builtin__.__dict__:
|
||||
|
||||
|
||||
class ClientSideError(RuntimeError):
|
||||
def __unicode__(self):
|
||||
return RuntimeError.__str__(self)
|
||||
|
||||
def __str__(self):
|
||||
return unicode(self).encode('utf8', 'ignore')
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@ class RestProtocol(object):
|
||||
dataformat = None
|
||||
content_types = []
|
||||
|
||||
def accept(self, root, request):
|
||||
def accept(self, request):
|
||||
if request.path.endswith('.' + self.dataformat):
|
||||
return True
|
||||
return request.headers.get('Content-Type') in self.content_types
|
||||
|
||||
def read_arguments(self, request, funcdef):
|
||||
def read_arguments(self, funcdef, request):
|
||||
if len(request.params) and request.body:
|
||||
raise ClientSideError(
|
||||
"Cannot read parameters from both a body and GET/POST params")
|
||||
|
||||
@@ -106,7 +106,7 @@ class RestJsonProtocol(RestProtocol):
|
||||
raw_args = json.loads(body)
|
||||
return raw_args
|
||||
|
||||
def encode_result(self, result, funcdef):
|
||||
def encode_result(self, funcdef, result):
|
||||
r = tojson(funcdef.return_type, result)
|
||||
return json.dumps({'result': r}, ensure_ascii=False).encode('utf8')
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ class RestXmlProtocol(RestProtocol):
|
||||
def parse_args(self, body):
|
||||
return dict((sub.tag, sub) for sub in et.fromstring(body))
|
||||
|
||||
def encode_result(self, result, funcdef):
|
||||
def encode_result(self, funcdef, result):
|
||||
return et.tostring(toxml(funcdef.return_type, 'result', result))
|
||||
|
||||
def encode_error(self, errordetail):
|
||||
|
||||
95
wsme/soap.py
95
wsme/soap.py
@@ -1,21 +1,63 @@
|
||||
"""
|
||||
A SOAP implementation for wsme.
|
||||
Parts of the code were taken from the tgwebservices soap implmentation.
|
||||
"""
|
||||
|
||||
import pkg_resources
|
||||
import datetime
|
||||
import decimal
|
||||
|
||||
from simplegeneric import generic
|
||||
|
||||
from xml.etree import ElementTree as et
|
||||
from genshi.template import MarkupTemplate
|
||||
from wsme.controller import register_protocol, pexpose
|
||||
import wsme.types
|
||||
|
||||
nativetypes = {
|
||||
type_registry = {
|
||||
basestring: 'xsd:string',
|
||||
str: 'xsd:string',
|
||||
int: 'xsd:int',
|
||||
long: "xsd:long",
|
||||
float: "xsd:float",
|
||||
bool: "xsd:boolean",
|
||||
#unsigned: "xsd:unsignedInt",
|
||||
datetime.datetime: "xsd:dateTime",
|
||||
datetime.date: "xsd:date",
|
||||
datetime.time: "xsd:time",
|
||||
decimal.Decimal: "xsd:decimal",
|
||||
wsme.types.binary: "xsd:base64Binary",
|
||||
}
|
||||
|
||||
|
||||
def make_soap_element(datatype, tag, value):
|
||||
el = et.Element(tag)
|
||||
if value is None:
|
||||
el.set('xsi:nil', 'true')
|
||||
else:
|
||||
el.set('xsi:type', type_registry.get(datatype))
|
||||
el.text = str(value)
|
||||
return el
|
||||
|
||||
|
||||
@generic
|
||||
def tosoap(datatype, tag, value):
|
||||
"""Converts a value into xml Element objects for inclusion in the SOAP
|
||||
response output"""
|
||||
return make_soap_element(datatype, tag, value)
|
||||
|
||||
@tosoap.when_object(None)
|
||||
def None_tosoap(datatype, tag, value):
|
||||
return make_soap_element(datatype, tag, None)
|
||||
|
||||
class SoapProtocol(object):
|
||||
name = 'SOAP'
|
||||
content_types = ['application/soap+xml']
|
||||
|
||||
ns = {
|
||||
"soap": "http://www.w3.org/2001/12/soap-envelope"
|
||||
"soap": "http://www.w3.org/2001/12/soap-envelope",
|
||||
"soapenv": "http://schemas.xmlsoap.org/soap/envelope/",
|
||||
"soapenc": "http://schemas.xmlsoap.org/soap/encoding/",
|
||||
}
|
||||
|
||||
def __init__(self, tns=None,
|
||||
@@ -25,8 +67,19 @@ class SoapProtocol(object):
|
||||
self.tns = tns
|
||||
self.typenamespace = typenamespace
|
||||
self.servicename = 'MyApp'
|
||||
self._name_mapping = {}
|
||||
|
||||
def accept(self, root, req):
|
||||
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
|
||||
return self._name_mapping[service]
|
||||
|
||||
|
||||
def accept(self, req):
|
||||
if req.path.endswith('.wsdl'):
|
||||
return True
|
||||
for ct in self.content_types:
|
||||
@@ -36,20 +89,30 @@ class SoapProtocol(object):
|
||||
|
||||
def extract_path(self, request):
|
||||
if request.path.endswith('.wsdl'):
|
||||
print "Here !!"
|
||||
return ['_protocol', self.name, 'api_wsdl']
|
||||
el = et.fromstring(request.body)
|
||||
body = el.find('{http://schemas.xmlsoap.org/soap/envelope/}Body')
|
||||
body = el.find('{%(soapenv)s}Body' % self.ns)
|
||||
# Extract the service name from the tns
|
||||
fname = list(body)[0].tag
|
||||
print fname
|
||||
return [fname]
|
||||
if fname.startswith('{%s}' % self.tns):
|
||||
fname = fname[len(self.tns)+2:]
|
||||
return self.get_name_mapping()[fname]
|
||||
return None
|
||||
|
||||
def read_arguments(self, request, funcdef):
|
||||
def read_arguments(self, funcdef, request):
|
||||
return {}
|
||||
|
||||
def encode_result(self, result, funcdef):
|
||||
envelope = self.render_template('soap')
|
||||
print envelope
|
||||
def soap_response(self, funcdef, result):
|
||||
r = et.Element('{' + self.tns + '}' + self.soap_fname(funcdef) + 'Response')
|
||||
r.append(tosoap(funcdef.return_type, 'result', result))
|
||||
return et.tostring(r)
|
||||
|
||||
def encode_result(self, funcdef, result):
|
||||
envelope = self.render_template('soap',
|
||||
typenamespace=self.typenamespace,
|
||||
result=result,
|
||||
funcdef=funcdef,
|
||||
soap_response=self.soap_response)
|
||||
return envelope
|
||||
|
||||
def get_template(self, name):
|
||||
@@ -67,7 +130,7 @@ class SoapProtocol(object):
|
||||
**infos)
|
||||
|
||||
@pexpose(contenttype="text/xml")
|
||||
def api_wsdl(self, root, service=None):
|
||||
def api_wsdl(self, service=None):
|
||||
if service is None:
|
||||
servicename = self.servicename
|
||||
else:
|
||||
@@ -75,10 +138,10 @@ class SoapProtocol(object):
|
||||
return self.render_template('wsdl',
|
||||
tns = self.tns,
|
||||
typenamespace = self.typenamespace,
|
||||
soapenc = 'http://schemas.xmlsoap.org/soap/encoding/',
|
||||
soapenc = self.ns['soapenc'],
|
||||
service_name = servicename,
|
||||
complex_types = (t() for t in wsme.types.complex_types),
|
||||
funclist = root.getapi(),
|
||||
funclist = self.root.getapi(),
|
||||
arrays = [],
|
||||
list_attributes = wsme.types.list_attributes,
|
||||
baseURL = service,
|
||||
@@ -87,8 +150,8 @@ class SoapProtocol(object):
|
||||
)
|
||||
|
||||
def soap_type(self, datatype):
|
||||
if datatype in nativetypes:
|
||||
return nativetypes[datatype]
|
||||
if datatype in type_registry:
|
||||
return type_registry[datatype]
|
||||
if wsme.types.iscomplex(datatype):
|
||||
return "types:%s" % datatype.__name__
|
||||
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
xmlns:py="http://genshi.edgewall.org/"
|
||||
py:attrs="{'xmlns' : typenamespace}">
|
||||
<soapenv:Body>
|
||||
${soap_body(methodname, function_info, output)}
|
||||
${Markup(soap_response(funcdef, result))}
|
||||
</soapenv:Body>
|
||||
</soapenv:Envelope>
|
||||
|
||||
@@ -14,18 +14,18 @@ class DummyProtocol(object):
|
||||
def __init__(self):
|
||||
self.hits = 0
|
||||
|
||||
def accept(self, root, req):
|
||||
def accept(self, req):
|
||||
return True
|
||||
|
||||
def extract_path(self, request):
|
||||
return ['touch']
|
||||
|
||||
def read_arguments(self, request, arguments):
|
||||
def read_arguments(self, funcdef, request):
|
||||
self.lastreq = request
|
||||
self.hits += 1
|
||||
return {}
|
||||
|
||||
def encode_result(self, result, return_type):
|
||||
def encode_result(self, funcdef, result):
|
||||
return str(result)
|
||||
|
||||
def encode_error(self, infos):
|
||||
|
||||
@@ -10,6 +10,10 @@ except:
|
||||
|
||||
import wsme.soap
|
||||
|
||||
tns = "http://foo.bar.baz/soap/"
|
||||
|
||||
soapenv_ns = 'http://schemas.xmlsoap.org/soap/envelope/'
|
||||
body_qn = '{%s}Body' % soapenv_ns
|
||||
|
||||
def build_soap_message(method, params=""):
|
||||
message = """<?xml version="1.0"?>
|
||||
@@ -18,14 +22,14 @@ xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
|
||||
|
||||
<soap:Body xmlns="http://foo.bar.baz/soap/">
|
||||
<soap:Body xmlns="%(tns)s">
|
||||
<%(method)s>
|
||||
%(params)s
|
||||
</%(method)s>
|
||||
</soap:Body>
|
||||
|
||||
</soap:Envelope>
|
||||
""" % dict(method=method, params=params)
|
||||
""" % dict(method=method, params=params, tns=tns)
|
||||
return message
|
||||
|
||||
|
||||
@@ -53,8 +57,19 @@ def loadxml(el):
|
||||
return el.text
|
||||
|
||||
|
||||
soap_types = {
|
||||
'xsi:int': int
|
||||
}
|
||||
|
||||
def fromsoap(el):
|
||||
t = el.get('type')
|
||||
if t in soap_types:
|
||||
return soap_types[t](el.text)
|
||||
return None
|
||||
|
||||
|
||||
class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
protocol = 'SOAP'
|
||||
protocol = wsme.soap.SoapProtocol(tns=tns)
|
||||
|
||||
def test_simple_call(self):
|
||||
message = build_soap_message('Touch')
|
||||
@@ -67,19 +82,33 @@ class TestSOAP(wsme.tests.protocol.ProtocolTestCase):
|
||||
assert res.status.startswith('200')
|
||||
|
||||
def call(self, fpath, **kw):
|
||||
path = fpath.strip('/').split('/')
|
||||
# get the actual definition so we can build the adequate request
|
||||
|
||||
el = dumpxml('parameters', kw)
|
||||
content = et.tostring(el)
|
||||
res = self.app.post(
|
||||
'/' + fpath,
|
||||
content,
|
||||
params = ""
|
||||
methodname = ''.join((i.capitalize() for i in path))
|
||||
message = build_soap_message(methodname, params)
|
||||
res = self.app.post('/', message,
|
||||
headers={
|
||||
'Content-Type': 'text/xml',
|
||||
},
|
||||
expect_errors=True)
|
||||
"Content-Type": "application/soap+xml; charset=utf-8"
|
||||
}, expect_errors=True)
|
||||
print "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' % (tns, methodname))
|
||||
result = r.find('{%s}result' % tns)
|
||||
return fromsoap(result)
|
||||
elif res.status_int == 400:
|
||||
pass
|
||||
elif res.status_int == 500:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
if el.tag == 'error':
|
||||
raise wsme.tests.protocol.CallException(
|
||||
el.find('faultcode').text,
|
||||
|
||||
Reference in New Issue
Block a user