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:
Christophe de Vienne
2011-09-23 17:48:33 +02:00
parent 7343e158e6
commit 0125de0a79
9 changed files with 146 additions and 43 deletions

View File

@@ -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):

View File

@@ -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')

View File

@@ -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")

View File

@@ -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')

View File

@@ -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):

View File

@@ -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__

View File

@@ -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>

View File

@@ -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):

View File

@@ -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,