diff --git a/wsme/controller.py b/wsme/controller.py
index e53f34a..609d874 100644
--- a/wsme/controller.py
+++ b/wsme/controller.py
@@ -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):
diff --git a/wsme/exc.py b/wsme/exc.py
index 6a3fda4..033d30b 100644
--- a/wsme/exc.py
+++ b/wsme/exc.py
@@ -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')
diff --git a/wsme/rest.py b/wsme/rest.py
index 19a0a9c..f86c9d5 100644
--- a/wsme/rest.py
+++ b/wsme/rest.py
@@ -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")
diff --git a/wsme/restjson.py b/wsme/restjson.py
index 8d3463c..3a40e3d 100644
--- a/wsme/restjson.py
+++ b/wsme/restjson.py
@@ -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')
diff --git a/wsme/restxml.py b/wsme/restxml.py
index 2c77474..108c7c2 100644
--- a/wsme/restxml.py
+++ b/wsme/restxml.py
@@ -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):
diff --git a/wsme/soap.py b/wsme/soap.py
index b8e6660..6c97f59 100644
--- a/wsme/soap.py
+++ b/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__
diff --git a/wsme/templates/soap.html b/wsme/templates/soap.html
index d4114ed..113f4c4 100644
--- a/wsme/templates/soap.html
+++ b/wsme/templates/soap.html
@@ -5,6 +5,6 @@
xmlns:py="http://genshi.edgewall.org/"
py:attrs="{'xmlns' : typenamespace}">
- ${soap_body(methodname, function_info, output)}
+ ${Markup(soap_response(funcdef, result))}
diff --git a/wsme/tests/test_controller.py b/wsme/tests/test_controller.py
index c8101fc..7ad8719 100644
--- a/wsme/tests/test_controller.py
+++ b/wsme/tests/test_controller.py
@@ -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):
diff --git a/wsme/tests/test_soap.py b/wsme/tests/test_soap.py
index 6204c16..ba66743 100644
--- a/wsme/tests/test_soap.py
+++ b/wsme/tests/test_soap.py
@@ -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 = """
@@ -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/">
-
+
<%(method)s>
%(params)s
%(method)s>
-""" % 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,