diff --git a/applications/admin/controllers/debug.py b/applications/admin/controllers/debug.py
index a33c0f8b..c5f9bd4f 100644
--- a/applications/admin/controllers/debug.py
+++ b/applications/admin/controllers/debug.py
@@ -54,9 +54,13 @@ def interact():
filename = web_debugger.filename
lineno = web_debugger.lineno
if filename:
+ # prevent IOError 2 on some circuntances (EAFP instead of os.access)
+ try:
+ lines = open(filename).readlines()
+ except:
+ lines = ""
lines = dict([(i + 1, l) for (i, l) in enumerate(
- [l.strip("\n").strip("\r") for l
- in open(filename).readlines()])])
+ [l.strip("\n").strip("\r") for l in lines])])
filename = os.path.basename(filename)
else:
lines = {}
diff --git a/applications/examples/controllers/soap_examples.py b/applications/examples/controllers/soap_examples.py
new file mode 100644
index 00000000..e9ab84a3
--- /dev/null
+++ b/applications/examples/controllers/soap_examples.py
@@ -0,0 +1,53 @@
+# coding: utf8
+
+# SOAP webservices (server and client) example and basic test
+# (using pysimplesoap contrib included in web2py)
+# for more info see: https://code.google.com/p/pysimplesoap/wiki/Web2Py
+
+from gluon.tools import Service
+service = Service(globals())
+
+# define the procedures to be exposed:
+
+@service.soap('AddStrings', returns={'AddResult': str}, args={'a': str, 'b': str})
+@service.soap('AddIntegers', returns={'AddResult': int}, args={'a': int, 'b': int})
+def add(a, b):
+ "Add two values"
+ return a+b
+
+@service.soap('SubIntegers', returns={'SubResult': int}, args={'a': int, 'b': int})
+def sub(a, b):
+ "Substract two values"
+ return a-b
+
+@service.soap('Division', returns={'divisionResult': float}, args={'a': float, 'b': float})
+def division(a, b):
+ "Divide two values "
+ return a / b
+
+
+# expose the soap methods
+
+def call():
+ return service()
+
+# sample function to test the SOAP RPC
+
+def test_soap_sub():
+ from gluon.contrib.pysimplesoap.client import SoapClient, SoapFault
+ # build the url to the WSDL (web service description)
+ # like "http://localhost:8000/webservices/sample/call/soap?WSDL"
+ url = URL(f="call/soap", vars={"WSDL": ""}, scheme=True)
+ # create a SOAP client
+ client = SoapClient(wsdl=url)
+ # call the SOAP remote method
+ try:
+ ret = client.SubIntegers(a=3, b=2)
+ result = ret['SubResult']
+ except SoapFault, sf:
+ result = sf
+ response.view = "soap_examples/generic.html"
+ return dict(xml_request=client.xml_request,
+ xml_response=client.xml_response,
+ result=result)
+
diff --git a/applications/examples/views/soap_examples/generic.html b/applications/examples/views/soap_examples/generic.html
new file mode 100644
index 00000000..03ea2fb4
--- /dev/null
+++ b/applications/examples/views/soap_examples/generic.html
@@ -0,0 +1,8 @@
+{{extend 'layout.html'}}
+
SOAP Examples
+Result
+{{=BEAUTIFY(result)}}
+XML Request
+{{=BEAUTIFY(xml_request)}}
+XML Response
+{{=BEAUTIFY(xml_response)}}
diff --git a/gluon/contrib/pysimplesoap/__init__.py b/gluon/contrib/pysimplesoap/__init__.py
index 6043241d..28bfee12 100755
--- a/gluon/contrib/pysimplesoap/__init__.py
+++ b/gluon/contrib/pysimplesoap/__init__.py
@@ -1,7 +1,16 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
-"PySimpleSOAP"
-import client
-import server
-import simplexml
-import transport
\ No newline at end of file
+
+"""PySimpleSOAP"""
+
+
+__author__ = "Mariano Reingart"
+__author_email__ = "reingart@gmail.com"
+__copyright__ = "Copyright (C) 2013 Mariano Reingart"
+__license__ = "LGPL 3.0"
+__version__ = "1.11"
+
+TIMEOUT = 60
+
+
+from . import client, server, simplexml, transport
diff --git a/gluon/contrib/pysimplesoap/client.py b/gluon/contrib/pysimplesoap/client.py
index 75bca191..88300eba 100755
--- a/gluon/contrib/pysimplesoap/client.py
+++ b/gluon/contrib/pysimplesoap/client.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: latin-1 -*-
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,114 +10,134 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
-"Pythonic simple SOAP Client implementation"
+"""Pythonic simple SOAP Client implementation"""
-__author__ = "Mariano Reingart (reingart@gmail.com)"
-__copyright__ = "Copyright (C) 2008 Mariano Reingart"
-__license__ = "LGPL 3.0"
-__version__ = "1.07a"
+from __future__ import unicode_literals
+import sys
+if sys.version > '3':
+ unicode = str
-TIMEOUT = 60
-
-import cPickle as pickle
+try:
+ import cPickle as pickle
+except ImportError:
+ import pickle
import hashlib
import logging
import os
import tempfile
-import urllib2
-from urlparse import urlsplit
-from simplexml import SimpleXMLElement, TYPE_MAP, REVERSE_TYPE_MAP, OrderedDict
-from transport import get_http_wrapper, set_http_wrapper, get_Http
+
+from . import __author__, __copyright__, __license__, __version__, TIMEOUT
+from .simplexml import SimpleXMLElement, TYPE_MAP, REVERSE_TYPE_MAP, OrderedDict
+from .transport import get_http_wrapper, set_http_wrapper, get_Http
+# Utility functions used throughout wsdl_parse, moved aside for readability
+from .helpers import fetch, sort_dict, make_key, process_element, \
+ postprocess_element, get_message, preprocess_schema, \
+ get_local_name, get_namespace_prefix, TYPE_MAP, urlsplit
+
log = logging.getLogger(__name__)
-logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
class SoapFault(RuntimeError):
- def __init__(self,faultcode,faultstring):
+ def __init__(self, faultcode, faultstring):
self.faultcode = faultcode
self.faultstring = faultstring
RuntimeError.__init__(self, faultcode, faultstring)
- def __str__(self):
- return self.__unicode__().encode("ascii", "ignore")
-
def __unicode__(self):
- return u'%s: %s' % (self.faultcode, self.faultstring)
+ return '%s: %s' % (self.faultcode, self.faultstring)
+
+ if sys.version > '3':
+ __str__ = __unicode__
+ else:
+ def __str__(self):
+ return self.__unicode__().encode('ascii', 'ignore')
def __repr__(self):
- return u"SoapFault(%s, %s)" % (repr(self.faultcode),
- repr(self.faultstring))
+ return "SoapFault(%s, %s)" % (repr(self.faultcode),
+ repr(self.faultstring))
# soap protocol specification & namespace
soap_namespaces = dict(
- soap11="http://schemas.xmlsoap.org/soap/envelope/",
- soap="http://schemas.xmlsoap.org/soap/envelope/",
- soapenv="http://schemas.xmlsoap.org/soap/envelope/",
- soap12="http://www.w3.org/2003/05/soap-env",
+ soap11='http://schemas.xmlsoap.org/soap/envelope/',
+ soap='http://schemas.xmlsoap.org/soap/envelope/',
+ soapenv='http://schemas.xmlsoap.org/soap/envelope/',
+ soap12='http://www.w3.org/2003/05/soap-env',
+ soap12env="http://www.w3.org/2003/05/soap-envelope",
)
-_USE_GLOBAL_DEFAULT = object()
class SoapClient(object):
- "Simple SOAP Client (simil PHP)"
- def __init__(self, location = None, action = None, namespace = None,
- cert = None, trace = False, exceptions = True, proxy = None, ns=False,
- soap_ns=None, wsdl = None, cache = False, cacert=None,
- sessions=False, soap_server=None, timeout=_USE_GLOBAL_DEFAULT,
- http_headers={}
+ """Simple SOAP Client (simil PHP)"""
+ def __init__(self, location=None, action=None, namespace=None,
+ cert=None, exceptions=True, proxy=None, ns=None,
+ soap_ns=None, wsdl=None, wsdl_basedir='', cache=False, cacert=None,
+ sessions=False, soap_server=None, timeout=TIMEOUT,
+ http_headers=None, trace=False,
+ username=None, password=None,
):
"""
:param http_headers: Additional HTTP Headers; example: {'Host': 'ipsec.example.com'}
"""
- self.certssl = cert
- self.keyssl = None
+ self.certssl = cert
+ self.keyssl = None
self.location = location # server location (url)
self.action = action # SOAP base action
- self.namespace = namespace # message
- self.trace = trace # show debug messages
+ self.namespace = namespace # message
self.exceptions = exceptions # lanzar execpiones? (Soap Faults)
self.xml_request = self.xml_response = ''
- self.http_headers = http_headers
+ self.http_headers = http_headers or {}
+ # extract the base directory / url for wsdl relative imports:
+ if wsdl and wsdl_basedir == '':
+ # parse the wsdl url, strip the scheme and filename
+ url_scheme, netloc, path, query, fragment = urlsplit(wsdl)
+ wsdl_basedir = os.path.dirname(netloc + path)
+
+ self.wsdl_basedir = wsdl_basedir
+
+ # shortcut to print all debugging info and sent / received xml messages
+ if trace:
+ logging.basicConfig(level=logging.DEBUG)
+
if not soap_ns and not ns:
- self.__soap_ns = 'soap' # 1.1
+ self.__soap_ns = 'soap' # 1.1
elif not soap_ns and ns:
- self.__soap_ns = 'soapenv' # 1.2
+ self.__soap_ns = 'soapenv' # 1.2
else:
self.__soap_ns = soap_ns
-
- # SOAP Server (special cases like oracle or jbossas6)
+
+ # SOAP Server (special cases like oracle, jbossas6 or jetty)
self.__soap_server = soap_server
-
+
# SOAP Header support
self.__headers = {} # general headers
self.__call_headers = None # OrderedDict to be marshalled for RPC Call
-
+
# check if the Certification Authority Cert is a string and store it
- if cacert and cacert.startswith("-----BEGIN CERTIFICATE-----"):
+ if cacert and cacert.startswith('-----BEGIN CERTIFICATE-----'):
fd, filename = tempfile.mkstemp()
f = os.fdopen(fd, 'w+b', -1)
- if self.trace: log.info(u"Saving CA certificate to %s" % filename)
+ log.debug("Saving CA certificate to %s" % filename)
f.write(cacert)
cacert = filename
f.close()
self.cacert = cacert
-
- if timeout is _USE_GLOBAL_DEFAULT:
- timeout = TIMEOUT
- else:
- timeout = timeout
# Create HTTP wrapper
Http = get_Http()
self.http = Http(timeout=timeout, cacert=cacert, proxy=proxy, sessions=sessions)
-
- self.__ns = ns # namespace prefix or False to not use it
+ if username and password:
+ if hasattr(self.http, 'add_credentials'):
+ self.http.add_credentials(username, password)
+
+
+ # namespace prefix, None to use xmlns attribute or False to not use it:
+ self.__ns = ns
if not ns:
- self.__xml = """
-<%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ self.__xml = """
+<%(soap_ns)s:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:%(soap_ns)s="%(soap_uri)s">
<%(soap_ns)s:Header/>
<%(soap_ns)s:Body>
@@ -136,127 +156,127 @@ class SoapClient(object):
%(soap_ns)s:Envelope>"""
# parse wsdl url
- self.services = wsdl and self.wsdl_parse(wsdl, debug=trace, cache=cache)
+ self.services = wsdl and self.wsdl_parse(wsdl, cache=cache)
self.service_port = None # service port for late binding
def __getattr__(self, attr):
- "Return a pseudo-method that can be called"
- if not self.services: # not using WSDL?
- return lambda self=self, *args, **kwargs: self.call(attr,*args,**kwargs)
- else: # using WSDL:
- return lambda *args, **kwargs: self.wsdl_call(attr,*args,**kwargs)
-
+ """Return a pseudo-method that can be called"""
+ if not self.services: # not using WSDL?
+ return lambda self=self, *args, **kwargs: self.call(attr, *args, **kwargs)
+ else: # using WSDL:
+ return lambda *args, **kwargs: self.wsdl_call(attr, *args, **kwargs)
+
def call(self, method, *args, **kwargs):
"""Prepare xml request and make SOAP call, returning a SimpleXMLElement.
-
+
If a keyword argument called "headers" is passed with a value of a
SimpleXMLElement object, then these headers will be inserted into the
request.
- """
+ """
#TODO: method != input_message
# Basic SOAP request:
- xml = self.__xml % dict(method=method, namespace=self.namespace, ns=self.__ns,
- soap_ns=self.__soap_ns, soap_uri=soap_namespaces[self.__soap_ns])
- request = SimpleXMLElement(xml,namespace=self.__ns and self.namespace, prefix=self.__ns)
-
- try:
- request_headers = kwargs.pop('headers')
- except KeyError:
- request_headers = None
-
+ xml = self.__xml % dict(method=method, # method tag name
+ namespace=self.namespace, # method ns uri
+ ns=self.__ns, # method ns prefix
+ soap_ns=self.__soap_ns, # soap prefix & uri
+ soap_uri=soap_namespaces[self.__soap_ns])
+ request = SimpleXMLElement(xml, namespace=self.__ns and self.namespace,
+ prefix=self.__ns)
+
+ request_headers = kwargs.pop('headers', None)
+
# serialize parameters
if kwargs:
- parameters = kwargs.items()
+ parameters = list(kwargs.items())
else:
parameters = args
if parameters and isinstance(parameters[0], SimpleXMLElement):
# merge xmlelement parameter ("raw" - already marshalled)
if parameters[0].children() is not None:
for param in parameters[0].children():
- getattr(request,method).import_node(param)
+ getattr(request, method).import_node(param)
+ for k,v in parameters[0].attributes().items():
+ getattr(request, method)[k] = v
elif parameters:
# marshall parameters:
- for k,v in parameters: # dict: tag=valor
- getattr(request,method).marshall(k,v)
- elif not self.__soap_server in ('oracle', ) or self.__soap_server in ('jbossas6',):
+ use_ns = None if (self.__soap_server == "jetty" or self.qualified is False) else True
+ for k, v in parameters: # dict: tag=valor
+ getattr(request, method).marshall(k, v, ns=use_ns)
+ elif not self.__soap_server in ('oracle',) or self.__soap_server in ('jbossas6',):
# JBossAS-6 requires no empty method parameters!
- delattr(request("Body", ns=soap_namespaces.values(),), method)
-
+ delattr(request("Body", ns=list(soap_namespaces.values()),), method)
+
# construct header and parameters (if not wsdl given) except wsse
if self.__headers and not self.services:
self.__call_headers = dict([(k, v) for k, v in self.__headers.items()
- if not k.startswith("wsse:")])
+ if not k.startswith('wsse:')])
# always extract WS Security header and send it
if 'wsse:Security' in self.__headers:
#TODO: namespaces too hardwired, clean-up...
- header = request('Header' , ns=soap_namespaces.values(),)
+ header = request('Header', ns=list(soap_namespaces.values()),)
k = 'wsse:Security'
v = self.__headers[k]
header.marshall(k, v, ns=False, add_children_ns=False)
header(k)['xmlns:wsse'] = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
- #
+ #
if self.__call_headers:
- header = request('Header' , ns=soap_namespaces.values(),)
+ header = request('Header', ns=list(soap_namespaces.values()),)
for k, v in self.__call_headers.items():
##if not self.__ns:
## header['xmlns']
- header.marshall(k, v, ns=self.__ns, add_children_ns=False)
-
+ if isinstance(v, SimpleXMLElement):
+ # allows a SimpleXMLElement to be constructed and inserted
+ # rather than a dictionary. marshall doesn't allow ns: prefixes
+ # in dict key names
+ header.import_node(v)
+ else:
+ header.marshall(k, v, ns=self.__ns, add_children_ns=False)
if request_headers:
- header = request('Header' , ns=soap_namespaces.values(),)
+ header = request('Header', ns=list(soap_namespaces.values()),)
for subheader in request_headers.children():
header.import_node(subheader)
-
+
self.xml_request = request.as_xml()
self.xml_response = self.send(method, self.xml_request)
- response = SimpleXMLElement(self.xml_response, namespace=self.namespace)
- if self.exceptions and response("Fault", ns=soap_namespaces.values(), error=False):
+ response = SimpleXMLElement(self.xml_response, namespace=self.namespace,
+ jetty=self.__soap_server in ('jetty',))
+ if self.exceptions and response("Fault", ns=list(soap_namespaces.values()), error=False):
raise SoapFault(unicode(response.faultcode), unicode(response.faultstring))
return response
-
-
+
def send(self, method, xml):
- "Send SOAP request using HTTP"
+ """Send SOAP request using HTTP"""
if self.location == 'test': return
- # location = "%s" % self.location #?op=%s" % (self.location, method)
- location = self.location
-
+ # location = '%s' % self.location #?op=%s" % (self.location, method)
+ location = str(self.location)
+
if self.services:
- soap_action = self.action
+ soap_action = str(self.action)
else:
- soap_action = self.action + method
-
- headers={
+ soap_action = str(self.action) + method
+
+ headers = {
'Content-type': 'text/xml; charset="UTF-8"',
'Content-length': str(len(xml)),
- "SOAPAction": "\"%s\"" % (soap_action)
+ 'SOAPAction': '"%s"' % soap_action
}
headers.update(self.http_headers)
log.info("POST %s" % location)
- log.info("Headers: %s" % headers)
-
- if self.trace:
- print "-"*80
- print "POST %s" % location
- print '\n'.join(["%s: %s" % (k,v) for k,v in headers.items()])
- print u"\n%s" % xml.decode("utf8","ignore")
-
+ log.debug('\n'.join(["%s: %s" % (k, v) for k, v in headers.items()]))
+ log.debug(xml)
+
response, content = self.http.request(
- location, "POST", body=xml, headers=headers)
+ location, 'POST', body=xml, headers=headers)
self.response = response
self.content = content
-
- if self.trace:
- print
- print '\n'.join(["%s: %s" % (k,v) for k,v in response.items()])
- print content#.decode("utf8","ignore")
- print "="*80
- return content
+ log.debug('\n'.join(["%s: %s" % (k, v) for k, v in response.items()]))
+ log.debug(content)
+ return content
def get_operation(self, method):
# try to find operation in wsdl file
- soap_ver = self.__soap_ns == 'soap12' and 'soap12' or 'soap11'
+ soap_ver = self.__soap_ns.startswith('soap12') and 'soap12' or 'soap11'
if not self.service_port:
for service_name, service in self.services.items():
for port_name, port in [port for port in service['ports'].items()]:
@@ -264,400 +284,347 @@ class SoapClient(object):
self.service_port = service_name, port_name
break
else:
- raise RuntimeError("Cannot determine service in WSDL: "
- "SOAP version: %s" % soap_ver)
+ raise RuntimeError('Cannot determine service in WSDL: '
+ 'SOAP version: %s' % soap_ver)
else:
port = self.services[self.service_port[0]]['ports'][self.service_port[1]]
- self.location = port['location']
- operation = port['operations'].get(unicode(method))
+ if not self.location:
+ self.location = port['location']
+ operation = port['operations'].get(method)
if not operation:
- raise RuntimeError("Operation %s not found in WSDL: "
- "Service/Port Type: %s" %
+ raise RuntimeError('Operation %s not found in WSDL: '
+ 'Service/Port Type: %s' %
(method, self.service_port))
return operation
-
+
def wsdl_call(self, method, *args, **kwargs):
- "Pre and post process SOAP call, input and output parameters using WSDL"
+ """Pre and post process SOAP call, input and output parameters using WSDL"""
soap_uri = soap_namespaces[self.__soap_ns]
operation = self.get_operation(method)
+
# get i/o type declarations:
input = operation['input']
output = operation['output']
header = operation.get('header')
if 'action' in operation:
self.action = operation['action']
- # sort parameters (same order as xsd:sequence)
- def sort_dict(od, d):
- if isinstance(od, dict):
- ret = OrderedDict()
- for k in od.keys():
- v = d.get(k)
- # don't append null tags!
- if v is not None:
- if isinstance(v, dict):
- v = sort_dict(od[k], v)
- elif isinstance(v, list):
- v = [sort_dict(od[k][0], v1)
- for v1 in v]
- ret[str(k)] = v
- return ret
- else:
- return d
+
+ if 'namespace' in operation:
+ self.namespace = operation['namespace'] or ''
+ self.qualified = operation['qualified']
+
# construct header and parameters
if header:
self.__call_headers = sort_dict(header, self.__headers)
+ method, params = self.wsdl_call_get_params(method, input, *args, **kwargs)
+
+ # call remote procedure
+ response = self.call(method, *params)
+ # parse results:
+ resp = response('Body', ns=soap_uri).children().unmarshall(output)
+ return resp and list(resp.values())[0] # pass Response tag children
+
+ def wsdl_call_get_params(self, method, input, *args, **kwargs):
+ """Build params from input and args/kwargs"""
+ params = inputname = inputargs = None
+ all_args = {}
+ if input:
+ inputname = list(input.keys())[0]
+ inputargs = input[inputname]
+
if input and args:
# convert positional parameters to named parameters:
- d = [(k, arg) for k, arg in zip(input.values()[0].keys(), args)]
- kwargs.update(dict(d))
- if input and kwargs:
- params = sort_dict(input.values()[0], kwargs).items()
- if self.__soap_server == "axis":
+ d = {}
+ for idx, arg in enumerate(args):
+ key = list(inputargs.keys())[idx]
+ if isinstance(arg, dict):
+ if key in arg:
+ d[key] = arg[key]
+ else:
+ raise KeyError('Unhandled key %s. use client.help(method)')
+ else:
+ d[key] = arg
+ all_args.update({inputname: d})
+
+ if input and (kwargs or all_args):
+ if kwargs:
+ all_args.update({inputname: kwargs})
+ valid, errors, warnings = self.wsdl_validate_params(input, all_args)
+ if not valid:
+ raise ValueError('Invalid Args Structure. Errors: %s' % errors)
+ params = list(sort_dict(input, all_args).values())[0].items()
+ # TODO: check style and document attributes
+ if self.__soap_server in ('axis', ):
# use the operation name
method = method
else:
# use the message (element) name
- method = input.keys()[0]
+ method = inputname
#elif not input:
- #TODO: no message! (see wsmtxca.dummy)
+ #TODO: no message! (see wsmtxca.dummy)
else:
params = kwargs and kwargs.items()
- # call remote procedure
- response = self.call(method, *params)
- # parse results:
- resp = response('Body',ns=soap_uri).children().unmarshall(output)
- return resp and resp.values()[0] # pass Response tag children
+
+ return (method, params)
+
+ def wsdl_validate_params(self, struct, value):
+ """Validate the arguments (actual values) for the parameters structure.
+ Fail for any invalid arguments or type mismatches."""
+ errors = []
+ warnings = []
+ valid = True
+
+ # Determine parameter type
+ if type(struct) == type(value):
+ typematch = True
+ if not isinstance(struct, dict) and isinstance(value, dict):
+ typematch = True # struct can be an OrderedDict
+ else:
+ typematch = False
+
+ if struct == str:
+ struct = unicode # fix for py2 vs py3 string handling
+
+ if not isinstance(struct, (list, dict, tuple)) and struct in TYPE_MAP.keys():
+ if not type(value) == struct:
+ try:
+ struct(value) # attempt to cast input to parameter type
+ except:
+ valid = False
+ errors.append('Type mismatch for argument value. parameter(%s): %s, value(%s): %s' % (type(struct), struct, type(value), value))
+
+ elif isinstance(struct, list) and len(struct) == 1 and not isinstance(value, list):
+ # parameter can have a dict in a list: [{}] indicating a list is allowed, but not needed if only one argument.
+ next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct[0], value)
+ if not next_valid:
+ valid = False
+ errors.extend(next_errors)
+ warnings.extend(next_warnings)
+
+ # traverse tree
+ elif isinstance(struct, dict):
+ if struct and value:
+ for key in value:
+ if key not in struct:
+ valid = False
+ errors.append('Argument key %s not in parameter. parameter: %s, args: %s' % (key, struct, value))
+ else:
+ next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct[key], value[key])
+ if not next_valid:
+ valid = False
+ errors.extend(next_errors)
+ warnings.extend(next_warnings)
+ for key in struct:
+ if key not in value:
+ warnings.append('Parameter key %s not in args. parameter: %s, value: %s' % (key, struct, value))
+ elif struct and not value:
+ warnings.append('parameter keys not in args. parameter: %s, args: %s' % (struct, value))
+ elif not struct and value:
+ valid = False
+ errors.append('Args keys not in parameter. parameter: %s, args: %s' % (struct, value))
+ else:
+ pass
+ elif isinstance(struct, list):
+ struct_list_value = struct[0]
+ for item in value:
+ next_valid, next_errors, next_warnings = self.wsdl_validate_params(struct_list_value, item)
+ if not next_valid:
+ valid = False
+ errors.extend(next_errors)
+ warnings.extend(next_warnings)
+ elif not typematch:
+ valid = False
+ errors.append('Type mismatch. parameter(%s): %s, value(%s): %s' % (type(struct), struct, type(value), value))
+
+ return (valid, errors, warnings)
def help(self, method):
- "Return operation documentation and invocation/returned value example"
+ """Return operation documentation and invocation/returned value example"""
operation = self.get_operation(method)
input = operation.get('input')
- input = input and input.values() and input.values()[0]
+ input = input and input.values() and list(input.values())[0]
if isinstance(input, dict):
- input = ", ".join("%s=%s" % (k,repr(v)) for k,v
- in input.items())
+ input = ", ".join("%s=%s" % (k, repr(v)) for k, v in input.items())
elif isinstance(input, list):
input = repr(input)
output = operation.get('output')
if output:
- output = operation['output'].values()[0]
+ output = list(operation['output'].values())[0]
headers = operation.get('headers') or None
- return u"%s(%s)\n -> %s:\n\n%s\nHeaders: %s" % (
- method,
- input or "",
- output and output or "",
- operation.get("documentation",""),
+ return "%s(%s)\n -> %s:\n\n%s\nHeaders: %s" % (
+ method,
+ input or '',
+ output and output or '',
+ operation.get('documentation', ''),
headers,
- )
+ )
- def wsdl_parse(self, url, debug=False, cache=False):
- "Parse Web Service Description v1.1"
+ def wsdl_parse(self, url, cache=False):
+ """Parse Web Service Description v1.1"""
- log.debug("wsdl url: %s" % url)
+ log.debug('Parsing wsdl url: %s' % url)
# Try to load a previously parsed wsdl:
force_download = False
if cache:
- # make md5 hash of the url for caching...
- filename_pkl = "%s.pkl" % hashlib.md5(url).hexdigest()
+ # make md5 hash of the url for caching...
+ filename_pkl = '%s.pkl' % hashlib.md5(url).hexdigest()
if isinstance(cache, basestring):
- filename_pkl = os.path.join(cache, filename_pkl)
+ filename_pkl = os.path.join(cache, filename_pkl)
if os.path.exists(filename_pkl):
- log.debug("Unpickle file %s" % (filename_pkl, ))
- f = open(filename_pkl, "r")
+ log.debug('Unpickle file %s' % (filename_pkl, ))
+ f = open(filename_pkl, 'r')
pkl = pickle.load(f)
f.close()
# sanity check:
- if pkl['version'][:-1] != __version__.split(" ")[0][:-1] or pkl['url'] != url:
+ if pkl['version'][:-1] != __version__.split(' ')[0][:-1] or pkl['url'] != url:
import warnings
- warnings.warn('version or url mismatch! discarding cached wsdl', RuntimeWarning)
- if debug:
- log.debug('Version: %s %s' % (pkl['version'], __version__))
- log.debug('URL: %s %s' % (pkl['url'], url))
+ warnings.warn('version or url mismatch! discarding cached wsdl', RuntimeWarning)
+ log.debug('Version: %s %s' % (pkl['version'], __version__))
+ log.debug('URL: %s %s' % (pkl['url'], url))
force_download = True
else:
self.namespace = pkl['namespace']
self.documentation = pkl['documentation']
return pkl['services']
-
+
soap_ns = {
- "http://schemas.xmlsoap.org/wsdl/soap/": 'soap11',
- "http://schemas.xmlsoap.org/wsdl/soap12/": 'soap12',
- }
- wsdl_uri="http://schemas.xmlsoap.org/wsdl/"
- xsd_uri="http://www.w3.org/2001/XMLSchema"
- xsi_uri="http://www.w3.org/2001/XMLSchema-instance"
-
- get_local_name = lambda s: s and str((':' in s) and s.split(':')[1] or s)
- get_namespace_prefix = lambda s: s and str((':' in s) and s.split(':')[0] or None)
-
+ 'http://schemas.xmlsoap.org/wsdl/soap/': 'soap11',
+ 'http://schemas.xmlsoap.org/wsdl/soap12/': 'soap12',
+ }
+ wsdl_uri = 'http://schemas.xmlsoap.org/wsdl/'
+ xsd_uri = 'http://www.w3.org/2001/XMLSchema'
+ xsi_uri = 'http://www.w3.org/2001/XMLSchema-instance'
+
# always return an unicode object:
- REVERSE_TYPE_MAP[u'string'] = unicode
+ REVERSE_TYPE_MAP['string'] = str
- def fetch(url):
- "Download a document from a URL, save it locally if cache enabled"
-
- # check / append a valid schema if not given:
- url_scheme, netloc, path, query, fragment = urlsplit(url)
- if not url_scheme in ('http','https', 'file'):
- for scheme in ('http','https', 'file'):
- try:
- if not url.startswith("/") and scheme in ('http', 'https'):
- tmp_url = "%s://%s" % (scheme, url)
- else:
- tmp_url = "%s:%s" % (scheme, url)
- if debug: log.debug("Scheme not found, trying %s" % scheme)
- return fetch(tmp_url)
- except Exception, e:
- log.error(e)
- raise RuntimeError("No scheme given for url: %s" % url)
-
- # make md5 hash of the url for caching...
- filename = "%s.xml" % hashlib.md5(url).hexdigest()
- if isinstance(cache, basestring):
- filename = os.path.join(cache, filename)
- if cache and os.path.exists(filename) and not force_download:
- log.info("Reading file %s" % (filename, ))
- f = open(filename, "r")
- xml = f.read()
- f.close()
- else:
- if url_scheme == 'file':
- log.info("Fetching url %s using urllib2" % (url, ))
- f = urllib2.urlopen(url)
- xml = f.read()
- else:
- log.info("GET %s using %s" % (url, self.http._wrapper_version))
- response, xml = self.http.request(url, "GET", None, {})
- if cache:
- log.info("Writing file %s" % (filename, ))
- if not os.path.isdir(cache):
- os.makedirs(cache)
- f = open(filename, "w")
- f.write(xml)
- f.close()
- return xml
-
# Open uri and read xml:
- xml = fetch(url)
+ xml = fetch(url, self.http, cache, force_download, self.wsdl_basedir)
# Parse WSDL XML:
wsdl = SimpleXMLElement(xml, namespace=wsdl_uri)
+ # Extract useful data:
+ self.namespace = ""
+ self.documentation = unicode(wsdl('documentation', error=False)) or ''
+
+ # some wsdl are splitted down in several files, join them:
+ imported_wsdls = {}
+ for element in wsdl.children() or []:
+ if element.get_local_name() in ('import'):
+ wsdl_namespace = element['namespace']
+ wsdl_location = element['location']
+ if wsdl_location is None:
+ log.warning('WSDL location not provided for %s!' % wsdl_namespace)
+ continue
+ if wsdl_location in imported_wsdls:
+ log.warning('WSDL %s already imported!' % wsdl_location)
+ continue
+ imported_wsdls[wsdl_location] = wsdl_namespace
+ log.debug('Importing wsdl %s from %s' % (wsdl_namespace, wsdl_location))
+ # Open uri and read xml:
+ xml = fetch(wsdl_location, self.http, cache, force_download, self.wsdl_basedir)
+ # Parse imported XML schema (recursively):
+ imported_wsdl = SimpleXMLElement(xml, namespace=xsd_uri)
+ # merge the imported wsdl into the main document:
+ wsdl.import_node(imported_wsdl)
+ # warning: do not process schemas to avoid infinite recursion!
+
+
# detect soap prefix and uri (xmlns attributes of )
xsd_ns = None
soap_uris = {}
for k, v in wsdl[:]:
- if v in soap_ns and k.startswith("xmlns:"):
+ if v in soap_ns and k.startswith('xmlns:'):
soap_uris[get_local_name(k)] = v
- if v== xsd_uri and k.startswith("xmlns:"):
+ if v == xsd_uri and k.startswith('xmlns:'):
xsd_ns = get_local_name(k)
- # Extract useful data:
- self.namespace = wsdl['targetNamespace']
- self.documentation = unicode(wsdl('documentation', error=False) or '')
-
services = {}
- bindings = {} # binding_name: binding
- operations = {} # operation_name: operation
- port_type_bindings = {} # port_type_name: binding
- messages = {} # message: element
- elements = {} # element: type def
-
+ bindings = {} # binding_name: binding
+ operations = {} # operation_name: operation
+ port_type_bindings = {} # port_type_name: binding
+ messages = {} # message: element
+ elements = {} # element: type def
+
for service in wsdl.service:
- service_name=service['name']
+ service_name = service['name']
if not service_name:
- continue # empty service?
- if debug: log.debug("Processing service %s" % service_name)
+ continue # empty service?
serv = services.setdefault(service_name, {'ports': {}})
- serv['documentation']=service['documentation'] or ''
+ serv['documentation'] = service['documentation'] or ''
for port in service.port:
binding_name = get_local_name(port['binding'])
- address = port('address', ns=soap_uris.values(), error=False)
+ operations[binding_name] = {}
+ address = port('address', ns=list(soap_uris.values()), error=False)
location = address and address['location'] or None
soap_uri = address and soap_uris.get(address.get_prefix())
soap_ver = soap_uri and soap_ns.get(soap_uri)
- bindings[binding_name] = {'service_name': service_name,
- 'location': location,
- 'soap_uri': soap_uri, 'soap_ver': soap_ver,
- }
+ bindings[binding_name] = {'name': binding_name,
+ 'service_name': service_name,
+ 'location': location,
+ 'soap_uri': soap_uri,
+ 'soap_ver': soap_ver, }
serv['ports'][port['name']] = bindings[binding_name]
-
+
for binding in wsdl.binding:
binding_name = binding['name']
- if debug: log.debug("Processing binding %s" % service_name)
- soap_binding = binding('binding', ns=soap_uris.values(), error=False)
+ soap_binding = binding('binding', ns=list(soap_uris.values()), error=False)
transport = soap_binding and soap_binding['transport'] or None
port_type_name = get_local_name(binding['type'])
bindings[binding_name].update({
'port_type_name': port_type_name,
'transport': transport, 'operations': {},
- })
- port_type_bindings[port_type_name] = bindings[binding_name]
+ })
+ if port_type_name not in port_type_bindings:
+ port_type_bindings[port_type_name] = []
+ port_type_bindings[port_type_name].append(bindings[binding_name])
for operation in binding.operation:
op_name = operation['name']
- op = operation('operation',ns=soap_uris.values(), error=False)
+ op = operation('operation', ns=list(soap_uris.values()), error=False)
action = op and op['soapAction']
- d = operations.setdefault(op_name, {})
+ d = operations[binding_name].setdefault(op_name, {})
bindings[binding_name]['operations'][op_name] = d
d.update({'name': op_name})
d['parts'] = {}
# input and/or ouput can be not present!
input = operation('input', error=False)
- body = input and input('body', ns=soap_uris.values(), error=False)
+ body = input and input('body', ns=list(soap_uris.values()), error=False)
d['parts']['input_body'] = body and body['parts'] or None
output = operation('output', error=False)
- body = output and output('body', ns=soap_uris.values(), error=False)
+ body = output and output('body', ns=list(soap_uris.values()), error=False)
d['parts']['output_body'] = body and body['parts'] or None
- header = input and input('header', ns=soap_uris.values(), error=False)
+ header = input and input('header', ns=list(soap_uris.values()), error=False)
d['parts']['input_header'] = header and {'message': header['message'], 'part': header['part']} or None
- headers = output and output('header', ns=soap_uris.values(), error=False)
+ header = output and output('header', ns=list(soap_uris.values()), error=False)
d['parts']['output_header'] = header and {'message': header['message'], 'part': header['part']} or None
- #if action: #TODO: separe operation_binding from operation
if action:
- d["action"] = action
-
- def make_key(element_name, element_type):
- "return a suitable key for elements"
- # only distinguish 'element' vs other types
- if element_type in ('complexType', 'simpleType'):
- eltype = 'complexType'
- else:
- eltype = element_type
- if eltype not in ('element', 'complexType', 'simpleType'):
- raise RuntimeError("Unknown element type %s = %s" % (unicode(element_name), eltype))
- return (unicode(element_name), eltype)
-
- #TODO: cleanup element/schema/types parsing:
- def process_element(element_name, node, element_type):
- "Parse and define simple element types"
- if debug:
- log.debug("Processing element %s %s" % (element_name, element_type))
- for tag in node:
- if tag.get_local_name() in ("annotation", "documentation"):
- continue
- elif tag.get_local_name() in ('element', 'restriction'):
- if debug: log.debug("%s has not children! %s" % (element_name,tag))
- children = tag # element "alias"?
- alias = True
- elif tag.children():
- children = tag.children()
- alias = False
- else:
- if debug: log.debug("%s has not children! %s" % (element_name,tag))
- continue #TODO: abstract?
- d = OrderedDict()
- for e in children:
- t = e['type']
- if not t:
- t = e['base'] # complexContent (extension)!
- if not t:
- t = 'anyType' # no type given!
- t = t.split(":")
- if len(t)>1:
- ns, type_name = t
- else:
- ns, type_name = None, t[0]
- if element_name == type_name:
- pass ## warning with infinite recursion
- uri = ns and e.get_namespace_uri(ns) or xsd_uri
- if uri==xsd_uri:
- # look for the type, None == any
- fn = REVERSE_TYPE_MAP.get(unicode(type_name), None)
- else:
- fn = None
- if not fn:
- # simple / complex type, postprocess later
- fn = elements.setdefault(make_key(type_name, "complexType"), OrderedDict())
-
- if e['name'] is not None and not alias:
- e_name = unicode(e['name'])
- d[e_name] = fn
- else:
- if debug: log.debug("complexConent/simpleType/element %s = %s" % (element_name, type_name))
- d[None] = fn
- if e['maxOccurs']=="unbounded" or (ns == 'SOAP-ENC' and type_name == 'Array'):
- # it's an array... TODO: compound arrays?
- d.array = True
- if e is not None and e.get_local_name() == 'extension' and e.children():
- # extend base element:
- process_element(element_name, e.children(), element_type)
- elements.setdefault(make_key(element_name, element_type), OrderedDict()).update(d)
-
- # check axis2 namespace at schema types attributes
- self.namespace = dict(wsdl.types("schema", ns=xsd_uri)[:]).get('targetNamespace', self.namespace)
+ d['action'] = action
+ # check axis2 namespace at schema types attributes (europa.eu checkVat)
+ if "http://xml.apache.org/xml-soap" in dict(wsdl[:]).values():
+ # get the sub-namespace in the first schema element (see issue 8)
+ if wsdl('types', error=False):
+ schema = wsdl.types('schema', ns=xsd_uri)
+ attrs = dict(schema[:])
+ self.namespace = attrs.get('targetNamespace', self.namespace)
+ if not self.namespace or self.namespace == "urn:DefaultNamespace":
+ self.namespace = wsdl['targetNamespace'] or self.namespace
+
imported_schemas = {}
+ global_namespaces = {None: self.namespace}
- def preprocess_schema(schema):
- "Find schema elements and complex types"
- for element in schema.children() or []:
- if element.get_local_name() in ('import', ):
- schema_namespace = element['namespace']
- schema_location = element['schemaLocation']
- if schema_location is None:
- if debug: log.debug("Schema location not provided for %s!" % (schema_namespace, ))
- continue
- if schema_location in imported_schemas:
- if debug: log.debug("Schema %s already imported!" % (schema_location, ))
- continue
- imported_schemas[schema_location] = schema_namespace
- if debug: print "Importing schema %s from %s" % (schema_namespace, schema_location)
- # Open uri and read xml:
- xml = fetch(schema_location)
- # Parse imported XML schema (recursively):
- imported_schema = SimpleXMLElement(xml, namespace=xsd_uri)
- preprocess_schema(imported_schema)
+ # process current wsdl schema (if any):
+ if wsdl('types', error=False):
+ for schema in wsdl.types('schema', ns=xsd_uri):
+ preprocess_schema(schema, imported_schemas, elements, xsd_uri,
+ self.__soap_server, self.http, cache,
+ force_download, self.wsdl_basedir,
+ global_namespaces=global_namespaces)
- element_type = element.get_local_name()
- if element_type in ('element', 'complexType', "simpleType"):
- element_name = unicode(element['name'])
- if debug: log.debug("Parsing Element %s: %s" % (element_type, element_name))
- if element.get_local_name() == 'complexType':
- children = element.children()
- elif element.get_local_name() == 'simpleType':
- children = element("restriction", ns=xsd_uri)
- elif element.get_local_name() == 'element' and element['type']:
- children = element
- else:
- children = element.children()
- if children:
- children = children.children()
- elif element.get_local_name() == 'element':
- children = element
- if children:
- process_element(element_name, children, element_type)
-
- def postprocess_element(elements):
- "Fix unresolved references (elements referenced before its definition, thanks .net)"
- for k,v in elements.items():
- if isinstance(v, OrderedDict):
- if v.array:
- elements[k] = [v] # convert arrays to python lists
- if v!=elements: #TODO: fix recursive elements
- postprocess_element(v)
- if None in v and v[None]: # extension base?
- if isinstance(v[None], dict):
- for i, kk in enumerate(v[None]):
- # extend base -keep orginal order-
- if v[None] is not None:
- elements[k].insert(kk, v[None][kk], i)
- del v[None]
- else: # "alias", just replace
- if debug: log.debug("Replacing %s = %s" % (k, v[None]))
- elements[k] = v[None]
- #break
- if isinstance(v, list):
- for n in v: # recurse list
- postprocess_element(n)
-
-
- # process current wsdl schema:
- for schema in wsdl.types("schema", ns=xsd_uri):
- preprocess_schema(schema)
-
- postprocess_element(elements)
+ # 2nd phase: alias, postdefined elements, extend bases, convert lists
+ postprocess_element(elements, [])
for message in wsdl.message:
- if debug: log.debug("Processing message %s" % message['name'])
for part in message('part', error=False) or []:
element = {}
element_name = part['element']
@@ -668,112 +635,110 @@ class SoapClient(object):
type_uri = wsdl.get_namespace_uri(type_ns)
if type_uri == xsd_uri:
element_name = get_local_name(element_name)
- fn = REVERSE_TYPE_MAP.get(unicode(element_name), None)
+ fn = REVERSE_TYPE_MAP.get(element_name, None)
element = {part['name']: fn}
# emulate a true Element (complexType)
- messages.setdefault((message['name'], None), {message['name']: OrderedDict()}).values()[0].update(element)
+ list(messages.setdefault((message['name'], None), {message['name']: OrderedDict()}).values())[0].update(element)
else:
element_name = get_local_name(element_name)
- fn = elements.get(make_key(element_name, 'element'))
+ fn = elements.get(make_key(element_name, 'element', type_uri))
if not fn:
# some axis servers uses complexType for part messages
- fn = elements.get(make_key(element_name, 'complexType'))
+ fn = elements.get(make_key(element_name, 'complexType', type_uri))
element = {message['name']: {part['name']: fn}}
else:
element = {element_name: fn}
messages[(message['name'], part['name'])] = element
- def get_message(message_name, part_name):
- if part_name:
- # get the specific part of the message:
- return messages.get((message_name, part_name))
- else:
- # get the first part for the specified message:
- for (message_name_key, part_name_key), message in messages.items():
- if message_name_key == message_name:
- return message
-
for port_type in wsdl.portType:
port_type_name = port_type['name']
- if debug: log.debug("Processing port type %s" % port_type_name)
- binding = port_type_bindings[port_type_name]
- for operation in port_type.operation:
- op_name = operation['name']
- op = operations[op_name]
- op['documentation'] = unicode(operation('documentation', error=False) or '')
- if binding['soap_ver']:
- #TODO: separe operation_binding from operation (non SOAP?)
- if operation("input", error=False):
- input_msg = get_local_name(operation.input['message'])
- input_header = op['parts'].get('input_header')
- if input_header:
- header_msg = get_local_name(input_header.get('message'))
- header_part = get_local_name(input_header.get('part'))
- # warning: some implementations use a separate message!
- header = get_message(header_msg or input_msg, header_part)
+ for binding in port_type_bindings.get(port_type_name, []):
+ for operation in port_type.operation:
+ op_name = operation['name']
+ op = operations[binding['name']][op_name]
+ op['documentation'] = unicode(operation('documentation', error=False)) or ''
+ if binding['soap_ver']:
+ #TODO: separe operation_binding from operation (non SOAP?)
+ if operation('input', error=False):
+ input_msg = get_local_name(operation.input['message'])
+ input_header = op['parts'].get('input_header')
+ if input_header:
+ header_msg = get_local_name(input_header.get('message'))
+ header_part = get_local_name(input_header.get('part'))
+ # warning: some implementations use a separate message!
+ header = get_message(messages, header_msg or input_msg, header_part)
+ else:
+ header = None # not enought info to search the header message:
+ op['input'] = get_message(messages, input_msg, op['parts'].get('input_body'))
+ op['header'] = header
+ try:
+ element = list(op['input'].values())[0]
+ ns_uri = element.namespace
+ qualified = element.qualified
+ except AttributeError:
+ # TODO: fix if no parameters parsed or "variants"
+ ns = get_namespace_prefix(operation.input['message'])
+ ns_uri = operation.get_namespace_uri(ns)
+ qualified = None
+ if ns_uri:
+ op['namespace'] = ns_uri
+ op['qualified'] = qualified
else:
- header = None # not enought info to search the header message:
- op['input'] = get_message(input_msg, op['parts'].get('input_body'))
- op['header'] = header
- else:
- op['input'] = None
- op['header'] = None
- if operation("output", error=False):
- output_msg = get_local_name(operation.output['message'])
- op['output'] = get_message(output_msg, op['parts'].get('output_body'))
- else:
- op['output'] = None
+ op['input'] = None
+ op['header'] = None
+ if operation('output', error=False):
+ output_msg = get_local_name(operation.output['message'])
+ op['output'] = get_message(messages, output_msg, op['parts'].get('output_body'))
+ else:
+ op['output'] = None
+
+ # dump the full service/port/operation map
+ #log.debug(pprint.pformat(services))
- if debug:
- import pprint
- log.debug(pprint.pformat(services))
-
# Save parsed wsdl (cache)
if cache:
f = open(filename_pkl, "wb")
pkl = {
- 'version': __version__.split(" ")[0],
- 'url': url,
- 'namespace': self.namespace,
+ 'version': __version__.split(' ')[0],
+ 'url': url,
+ 'namespace': self.namespace,
'documentation': self.documentation,
'services': services,
- }
+ }
pickle.dump(pkl, f)
f.close()
-
+
return services
def __setitem__(self, item, value):
- "Set SOAP Header value - this header will be sent for every request."
+ """Set SOAP Header value - this header will be sent for every request."""
self.__headers[item] = value
def close(self):
- "Finish the connection and remove temp files"
+ """Finish the connection and remove temp files"""
self.http.close()
if self.cacert.startswith(tempfile.gettempdir()):
- if self.trace: log.info("removing %s" % self.cacert)
+ log.debug('removing %s' % self.cacert)
os.unlink(self.cacert)
-
+
def parse_proxy(proxy_str):
- "Parses proxy address user:pass@host:port into a dict suitable for httplib2"
- if isinstance(proxy_str, unicode):
- proxy_str = proxy_str.encode("utf8")
+ """Parses proxy address user:pass@host:port into a dict suitable for httplib2"""
proxy_dict = {}
if proxy_str is None:
- return
- if "@" in proxy_str:
- user_pass, host_port = proxy_str.split("@")
+ return
+ if '@' in proxy_str:
+ user_pass, host_port = proxy_str.split('@')
else:
- user_pass, host_port = "", proxy_str
- if ":" in host_port:
- host, port = host_port.split(":")
+ user_pass, host_port = '', proxy_str
+ if ':' in host_port:
+ host, port = host_port.split(':')
proxy_dict['proxy_host'], proxy_dict['proxy_port'] = host, int(port)
- if ":" in user_pass:
- proxy_dict['proxy_user'], proxy_dict['proxy_pass'] = user_pass.split(":")
+ if ':' in user_pass:
+ proxy_dict['proxy_user'], proxy_dict['proxy_pass'] = user_pass.split(':')
return proxy_dict
-
-
-if __name__ == "__main__":
+
+
+if __name__ == '__main__':
pass
diff --git a/gluon/contrib/pysimplesoap/helpers.py b/gluon/contrib/pysimplesoap/helpers.py
new file mode 100644
index 00000000..b0e1af7d
--- /dev/null
+++ b/gluon/contrib/pysimplesoap/helpers.py
@@ -0,0 +1,489 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License as published by the
+# Free Software Foundation; either version 3, or (at your option) any later
+# version.
+#
+# This program is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTIBILITY
+# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+# for more details.
+
+"""Pythonic simple SOAP Client helpers"""
+
+
+from __future__ import unicode_literals
+import sys
+if sys.version > '3':
+ basestring = unicode = str
+
+import datetime
+from decimal import Decimal
+import os
+import logging
+import hashlib
+import warnings
+
+try:
+ import urllib2
+ from urlparse import urlsplit
+except ImportError:
+ from urllib import request as urllib2
+ from urllib.parse import urlsplit
+
+from . import __author__, __copyright__, __license__, __version__
+
+
+log = logging.getLogger(__name__)
+
+
+def fetch(url, http, cache=False, force_download=False, wsdl_basedir=''):
+ """Download a document from a URL, save it locally if cache enabled"""
+
+ # check / append a valid schema if not given:
+ url_scheme, netloc, path, query, fragment = urlsplit(url)
+ if not url_scheme in ('http', 'https', 'file'):
+ for scheme in ('http', 'https', 'file'):
+ try:
+ if not url.startswith("/") and scheme in ('http', 'https'):
+ tmp_url = "%s://%s" % (scheme, os.path.join(wsdl_basedir, url))
+ else:
+ tmp_url = "%s:%s" % (scheme, os.path.join(wsdl_basedir, url))
+ log.debug('Scheme not found, trying %s' % scheme)
+ return fetch(tmp_url, http, cache, force_download, wsdl_basedir)
+ except Exception as e:
+ log.error(e)
+ raise RuntimeError('No scheme given for url: %s' % url)
+
+ # make md5 hash of the url for caching...
+ filename = '%s.xml' % hashlib.md5(url.encode('utf8')).hexdigest()
+ if isinstance(cache, basestring):
+ filename = os.path.join(cache, filename)
+ if cache and os.path.exists(filename) and not force_download:
+ log.info('Reading file %s' % filename)
+ f = open(filename, 'r')
+ xml = f.read()
+ f.close()
+ else:
+ if url_scheme == 'file':
+ log.info('Fetching url %s using urllib2' % url)
+ f = urllib2.urlopen(url)
+ xml = f.read()
+ else:
+ log.info('GET %s using %s' % (url, http._wrapper_version))
+ response, xml = http.request(url, 'GET', None, {})
+ if cache:
+ log.info('Writing file %s' % filename)
+ if not os.path.isdir(cache):
+ os.makedirs(cache)
+ f = open(filename, 'w')
+ f.write(xml)
+ f.close()
+ return xml
+
+
+def sort_dict(od, d):
+ """Sort parameters (same order as xsd:sequence)"""
+ if isinstance(od, dict):
+ ret = OrderedDict()
+ for k in od.keys():
+ v = d.get(k)
+ # don't append null tags!
+ if v is not None:
+ if isinstance(v, dict):
+ v = sort_dict(od[k], v)
+ elif isinstance(v, list):
+ v = [sort_dict(od[k][0], v1) for v1 in v]
+ ret[k] = v
+ if hasattr(od, 'namespace'):
+ ret.namespace = od.namespace
+ ret.qualified = od.qualified
+ return ret
+ else:
+ return d
+
+
+def make_key(element_name, element_type, namespace):
+ """Return a suitable key for elements"""
+ # only distinguish 'element' vs other types
+ if element_type in ('complexType', 'simpleType'):
+ eltype = 'complexType'
+ else:
+ eltype = element_type
+ if eltype not in ('element', 'complexType', 'simpleType'):
+ raise RuntimeError("Unknown element type %s = %s" % (element_name, eltype))
+ return (element_name, eltype, namespace)
+
+
+def process_element(elements, element_name, node, element_type, xsd_uri, dialect, namespace, qualified=None,
+ soapenc_uri = 'http://schemas.xmlsoap.org/soap/encoding/'):
+ """Parse and define simple element types"""
+
+ log.debug('Processing element %s %s' % (element_name, element_type))
+ for tag in node:
+ if tag.get_local_name() in ('annotation', 'documentation'):
+ continue
+ elif tag.get_local_name() in ('element', 'restriction'):
+ log.debug('%s has no children! %s' % (element_name, tag))
+ children = tag # element "alias"?
+ alias = True
+ elif tag.children():
+ children = tag.children()
+ alias = False
+ else:
+ log.debug('%s has no children! %s' % (element_name, tag))
+ continue # TODO: abstract?
+ d = OrderedDict()
+ d.namespace = namespace
+ d.qualified = qualified
+ for e in children:
+ t = e['type']
+ if not t:
+ t = e['base'] # complexContent (extension)!
+ if not t:
+ t = e['ref'] # reference to another element
+ if not t:
+ # "anonymous" elements had no type attribute but children
+ if e['name'] and e.children():
+ # create a type name to process the children
+ t = "%s_%s" % (element_name, e['name'])
+ c = e.children()
+ et = c.get_local_name()
+ c = c.children()
+ process_element(elements, t, c, et, xsd_uri, dialect, namespace, qualified)
+ else:
+ t = 'anyType' # no type given!
+ t = t.split(":")
+ if len(t) > 1:
+ ns, type_name = t
+ else:
+ ns, type_name = None, t[0]
+ if element_name == type_name and not alias and len(children) > 1:
+ continue # abort to prevent infinite recursion
+ uri = ns and e.get_namespace_uri(ns) or xsd_uri
+ if uri in (xsd_uri, soapenc_uri) and type_name != 'Array':
+ # look for the type, None == any
+ fn = REVERSE_TYPE_MAP.get(type_name, None)
+ elif uri == soapenc_uri and type_name == 'Array':
+ # arrays of simple types (look at the attribute tags):
+ fn = []
+ for a in e.children():
+ for k, v in a[:]:
+ if k.endswith(":arrayType"):
+ type_name = v
+ if ":" in type_name:
+ type_name = type_name[type_name.index(":")+1:]
+ if "[]" in type_name:
+ type_name = type_name[:type_name.index("[]")]
+ fn.append(REVERSE_TYPE_MAP.get(type_name, None))
+ else:
+ fn = None
+
+ if not fn:
+ # simple / complex type, postprocess later
+ if ns:
+ fn_namespace = uri # use the specified namespace
+ else:
+ fn_namespace = namespace # use parent namespace (default)
+ for k, v in e[:]:
+ if k.startswith("xmlns:"):
+ # get the namespace uri from the element
+ fn_namespace = v
+ fn = elements.setdefault(make_key(type_name, 'complexType', fn_namespace), OrderedDict())
+
+ if e['maxOccurs'] == 'unbounded' or (uri == soapenc_uri and type_name == 'Array'):
+ # it's an array... TODO: compound arrays? and check ns uri!
+ if isinstance(fn, OrderedDict):
+ if len(children) > 1 and dialect in ('jetty',):
+ # Jetty style support
+ # {'ClassName': [{'attr1': val1, 'attr2': val2}]
+ fn.array = True
+ else:
+ # .NET style support (backward compatibility)
+ # [{'ClassName': {'attr1': val1, 'attr2': val2}]
+ d.array = True
+ else:
+ if dialect in ('jetty',):
+ # scalar support [{'attr1': [val1]}]
+ fn = [fn]
+ else:
+ d.array = True
+
+ if (e['name'] is not None and not alias) or e['ref']:
+ e_name = e['name'] or type_name # for refs, use the type name
+ d[e_name] = fn
+ else:
+ log.debug('complexContent/simpleType/element %s = %s' % (element_name, type_name))
+ d[None] = fn
+ if e is not None and e.get_local_name() == 'extension' and e.children():
+ # extend base element:
+ process_element(elements, element_name, e.children(), element_type, xsd_uri, dialect, namespace, qualified)
+ elements.setdefault(make_key(element_name, element_type, namespace), OrderedDict()).update(d)
+
+
+def postprocess_element(elements, processed):
+ """Fix unresolved references (elements referenced before its definition, thanks .net)"""
+
+ # avoid already processed elements:
+ if elements in processed:
+ return
+ processed.append(elements)
+
+ for k, v in elements.items():
+ if isinstance(v, OrderedDict):
+ if v != elements: # TODO: fix recursive elements
+ postprocess_element(v, processed)
+ if None in v and v[None]: # extension base?
+ if isinstance(v[None], dict):
+ for i, kk in enumerate(v[None]):
+ # extend base -keep orginal order-
+ if v[None] is not None:
+ elements[k].insert(kk, v[None][kk], i)
+ del v[None]
+ else: # "alias", just replace
+ log.debug('Replacing %s = %s' % (k, v[None]))
+ elements[k] = v[None]
+ #break
+ if v.array:
+ elements[k] = [v] # convert arrays to python lists
+ if isinstance(v, list):
+ for n in v: # recurse list
+ if isinstance(n, (OrderedDict, list)):
+ #if n != elements: # TODO: fix recursive elements
+ postprocess_element(n, processed)
+
+
+def get_message(messages, message_name, part_name):
+ if part_name:
+ # get the specific part of the message:
+ return messages.get((message_name, part_name))
+ else:
+ # get the first part for the specified message:
+ for (message_name_key, part_name_key), message in messages.items():
+ if message_name_key == message_name:
+ return message
+
+
+get_local_name = lambda s: s and str((':' in s) and s.split(':')[1] or s)
+get_namespace_prefix = lambda s: s and str((':' in s) and s.split(':')[0] or None)
+
+
+def preprocess_schema(schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir, global_namespaces=None, qualified=False):
+ """Find schema elements and complex types"""
+
+ from .simplexml import SimpleXMLElement # here to avoid recursive imports
+
+ # analyze the namespaces used in this schema
+ local_namespaces = {}
+ for k, v in schema[:]:
+ if k.startswith("xmlns"):
+ local_namespaces[get_local_name(k)] = v
+ if k == 'targetNamespace':
+ # URI namespace reference for this schema
+ if v == "urn:DefaultNamespace":
+ v = global_namespaces[None]
+ local_namespaces[None] = v
+ if k == 'elementFormDefault':
+ qualified = (v == "qualified")
+ # add schema namespaces to the global namespace dict = {URI: ns prefix}
+ for ns in local_namespaces.values():
+ if ns not in global_namespaces:
+ global_namespaces[ns] = 'ns%s' % len(global_namespaces)
+
+ for element in schema.children() or []:
+ if element.get_local_name() in ('import', 'include',):
+ schema_namespace = element['namespace']
+ schema_location = element['schemaLocation']
+ if schema_location is None:
+ log.debug('Schema location not provided for %s!' % schema_namespace)
+ continue
+ if schema_location in imported_schemas:
+ log.debug('Schema %s already imported!' % schema_location)
+ continue
+ imported_schemas[schema_location] = schema_namespace
+ log.debug('Importing schema %s from %s' % (schema_namespace, schema_location))
+ # Open uri and read xml:
+ xml = fetch(schema_location, http, cache, force_download, wsdl_basedir)
+
+ # Parse imported XML schema (recursively):
+ imported_schema = SimpleXMLElement(xml, namespace=xsd_uri)
+ preprocess_schema(imported_schema, imported_schemas, elements, xsd_uri, dialect, http, cache, force_download, wsdl_basedir, global_namespaces, qualified)
+
+ element_type = element.get_local_name()
+ if element_type in ('element', 'complexType', "simpleType"):
+ namespace = local_namespaces[None] # get targetNamespace
+ element_ns = global_namespaces[ns] # get the prefix
+ element_name = element['name']
+ log.debug("Parsing Element %s: %s" % (element_type, element_name))
+ if element.get_local_name() == 'complexType':
+ children = element.children()
+ elif element.get_local_name() == 'simpleType':
+ children = element('restriction', ns=xsd_uri)
+ elif element.get_local_name() == 'element' and element['type']:
+ children = element
+ else:
+ children = element.children()
+ if children:
+ children = children.children()
+ elif element.get_local_name() == 'element':
+ children = element
+ if children:
+ process_element(elements, element_name, children, element_type, xsd_uri, dialect, namespace, qualified)
+
+
+# simplexml utilities:
+
+try:
+ _strptime = datetime.datetime.strptime
+except AttributeError: # python2.4
+ _strptime = lambda s, fmt: datetime.datetime(*(time.strptime(s, fmt)[:6]))
+
+
+# Functions to serialize/deserialize special immutable types:
+def datetime_u(s):
+ fmt = "%Y-%m-%dT%H:%M:%S"
+ try:
+ return _strptime(s, fmt)
+ except ValueError:
+ try:
+ # strip utc offset
+ if s[-3] == ":" and s[-6] in (' ', '-', '+'):
+ warnings.warn('removing unsupported UTC offset', RuntimeWarning)
+ s = s[:-6]
+ # parse microseconds
+ try:
+ return _strptime(s, fmt + ".%f")
+ except:
+ return _strptime(s, fmt)
+ except ValueError:
+ # strip microseconds (not supported in this platform)
+ if "." in s:
+ warnings.warn('removing unsuppported microseconds', RuntimeWarning)
+ s = s[:s.index(".")]
+ return _strptime(s, fmt)
+
+datetime_m = lambda dt: dt.isoformat()
+date_u = lambda s: _strptime(s[0:10], "%Y-%m-%d").date()
+date_m = lambda d: d.strftime("%Y-%m-%d")
+time_u = lambda s: _strptime(s, "%H:%M:%S").time()
+time_m = lambda d: d.strftime("%H%M%S")
+bool_u = lambda s: {'0': False, 'false': False, '1': True, 'true': True}[s]
+bool_m = lambda s: {False: 'false', True: 'true'}[s]
+
+
+# aliases:
+class Alias(object):
+ def __init__(self, py_type, xml_type):
+ self.py_type, self.xml_type = py_type, xml_type
+
+ def __call__(self, value):
+ return self.py_type(value)
+
+ def __repr__(self):
+ return "" % (self.xml_type, self.py_type)
+
+if sys.version > '3':
+ long = Alias(int, 'long')
+byte = Alias(str, 'byte')
+short = Alias(int, 'short')
+double = Alias(float, 'double')
+integer = Alias(long, 'integer')
+DateTime = datetime.datetime
+Date = datetime.date
+Time = datetime.time
+
+# Define convertion function (python type): xml schema type
+TYPE_MAP = {
+ unicode: 'string',
+ bool: 'boolean',
+ short: 'short',
+ byte: 'byte',
+ int: 'int',
+ long: 'long',
+ integer: 'integer',
+ float: 'float',
+ double: 'double',
+ Decimal: 'decimal',
+ datetime.datetime: 'dateTime',
+ datetime.date: 'date',
+}
+TYPE_MARSHAL_FN = {
+ datetime.datetime: datetime_m,
+ datetime.date: date_m,
+ bool: bool_m
+}
+TYPE_UNMARSHAL_FN = {
+ datetime.datetime: datetime_u,
+ datetime.date: date_u,
+ bool: bool_u,
+ str: unicode,
+}
+
+REVERSE_TYPE_MAP = dict([(v, k) for k, v in TYPE_MAP.items()])
+
+REVERSE_TYPE_MAP.update({
+ 'base64Binary': str,
+})
+
+# insert str here to avoid collision in REVERSE_TYPE_MAP (i.e. decoding errors)
+if str not in TYPE_MAP:
+ TYPE_MAP[str] = 'string'
+
+
+class OrderedDict(dict):
+ """Minimal ordered dictionary for xsd:sequences"""
+ def __init__(self):
+ self.__keys = []
+ self.array = False
+ self.namespace = None
+ self.qualified = None
+
+ def __setitem__(self, key, value):
+ if key not in self.__keys:
+ self.__keys.append(key)
+ dict.__setitem__(self, key, value)
+
+ def insert(self, key, value, index=0):
+ if key not in self.__keys:
+ self.__keys.insert(index, key)
+ dict.__setitem__(self, key, value)
+
+ def __delitem__(self, key):
+ if key in self.__keys:
+ self.__keys.remove(key)
+ dict.__delitem__(self, key)
+
+ def __iter__(self):
+ return iter(self.__keys)
+
+ def keys(self):
+ return self.__keys
+
+ def items(self):
+ return [(key, self[key]) for key in self.__keys]
+
+ def update(self, other):
+ for k, v in other.items():
+ self[k] = v
+ # do not change if we are an array but the other is not:
+ if isinstance(other, OrderedDict) and not self.array:
+ self.array = other.array
+ if isinstance(other, OrderedDict) and not self.namespace:
+ self.namespace = other.namespace
+ self.qualified = other.qualified
+
+ def copy(self):
+ "Make a duplicate"
+ new = OrderedDict()
+ new.update(self)
+ return new
+
+ def __str__(self):
+ return "%s" % dict.__str__(self)
+
+ def __repr__(self):
+ s = "{%s}" % ", ".join(['%s: %s' % (repr(k), repr(v)) for k, v in self.items()])
+ if self.array and False:
+ s = "[%s]" % s
+ return s
diff --git a/gluon/contrib/pysimplesoap/server.py b/gluon/contrib/pysimplesoap/server.py
index 3888b49c..fb971494 100755
--- a/gluon/contrib/pysimplesoap/server.py
+++ b/gluon/contrib/pysimplesoap/server.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: latin-1 -*-
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,30 +10,41 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
-"Simple SOAP Server implementation"
+"""Pythonic simple SOAP Server implementation"""
-__author__ = "Mariano Reingart (reingart@gmail.com)"
-__copyright__ = "Copyright (C) 2010 Mariano Reingart"
-__license__ = "LGPL 3.0"
-__version__ = "1.03c"
+from __future__ import unicode_literals
+import sys
+if sys.version > '3':
+ unicode = str
+
+
+import datetime
+import sys
import logging
+import warnings
import re
import traceback
-from simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal
+try:
+ from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
+except ImportError:
+ from http.server import BaseHTTPRequestHandler, HTTPServer
+
+from . import __author__, __copyright__, __license__, __version__
+from .simplexml import SimpleXMLElement, TYPE_MAP, Date, Decimal
log = logging.getLogger(__name__)
-# Deprecated
-DEBUG = False
-NS_RX=re.compile(r'xmlns:(\w+)="(.+?)"')
+# Deprecated?
+NS_RX = re.compile(r'xmlns:(\w+)="(.+?)"')
+
class SoapDispatcher(object):
- "Simple Dispatcher for SOAP Server"
-
- def __init__(self, name, documentation='', action='', location='',
- namespace=None, prefix=False,
- soap_uri="http://schemas.xmlsoap.org/soap/envelope/",
+ """Simple Dispatcher for SOAP Server"""
+
+ def __init__(self, name, documentation='', action='', location='',
+ namespace=None, prefix=False,
+ soap_uri="http://schemas.xmlsoap.org/soap/envelope/",
soap_ns='soap',
namespaces={},
pretty=False,
@@ -45,13 +56,13 @@ class SoapDispatcher(object):
:param namespaces: Specify additional namespaces; example: {'external': 'http://external.mt.moboperator'}
:param pretty: Prettifies generated xmls
:param debug: Use to add tracebacks in generated xmls.
-
+
Multiple namespaces
===================
-
+
It is possible to support multiple namespaces.
You need to specify additional namespaces by passing `namespace` parameter.
-
+
>>> dispatcher = SoapDispatcher(
... name = "MTClientWS",
... location = "http://localhost:8008/ws/MTClientWS",
@@ -59,13 +70,13 @@ class SoapDispatcher(object):
... namespace = "http://external.mt.moboperator", prefix="external",
... documentation = 'moboperator MTClientWS',
... namespaces = {
- ... 'external': 'http://external.mt.moboperator',
+ ... 'external': 'http://external.mt.moboperator',
... 'model': 'http://model.common.mt.moboperator'
... },
... ns = True)
-
+
Now the registered method must return node names with namespaces' prefixes.
-
+
>>> def _multi_ns_func(self, serviceMsisdn):
... ret = {
... 'external:activateSubscriptionsReturn': [
@@ -73,23 +84,22 @@ class SoapDispatcher(object):
... {'model:description': 'desc'},
... ]}
... return ret
-
+
Our prefixes will be changed to those used by the client.
"""
self.methods = {}
self.name = name
self.documentation = documentation
- self.action = action # base SoapAction
+ self.action = action # base SoapAction
self.location = location
- self.namespace = namespace # targetNamespace
+ self.namespace = namespace # targetNamespace
self.prefix = prefix
self.soap_ns = soap_ns
self.soap_uri = soap_uri
self.namespaces = namespaces
self.pretty = pretty
self.debug = debug
-
-
+
@staticmethod
def _extra_namespaces(xml, ns):
"""Extends xml with extra namespaces.
@@ -99,56 +109,57 @@ class SoapDispatcher(object):
if ns:
_tpl = 'xmlns:%s="%s"'
_ns_str = " ".join([_tpl % (prefix, uri) for uri, prefix in ns.items() if uri not in xml])
- xml = xml.replace('/>', ' '+_ns_str+'/>')
+ xml = xml.replace('/>', ' ' + _ns_str + '/>')
return xml
-
-
+
def register_function(self, name, fn, returns=None, args=None, doc=None):
self.methods[name] = fn, returns, args, doc or getattr(fn, "__doc__", "")
-
-
- def dispatch(self, xml, action=None):
- "Receive and proccess SOAP call"
+
+ def dispatch(self, xml, action=None, fault=None):
+ """Receive and process SOAP call, returns the xml"""
+ # a dict can be sent in fault to expose it to the caller
# default values:
prefix = self.prefix
- ret = fault = None
+ ret = None
+ if fault is None:
+ fault = {}
soap_ns, soap_uri = self.soap_ns, self.soap_uri
soap_fault_code = 'VersionMismatch'
name = None
-
+
# namespaces = [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')]
- _ns_reversed = dict(((v,k) for k,v in self.namespaces.iteritems())) # Switch keys-values
+ _ns_reversed = dict(((v, k) for k, v in self.namespaces.items())) # Switch keys-values
# _ns_reversed = {'http://external.mt.moboperator': 'external', 'http://model.common.mt.moboperator': 'model'}
-
+
try:
request = SimpleXMLElement(xml, namespace=self.namespace)
-
+
# detect soap prefix and uri (xmlns attributes of Envelope)
for k, v in request[:]:
if v in ("http://schemas.xmlsoap.org/soap/envelope/",
- "http://www.w3.org/2003/05/soap-env",):
+ "http://www.w3.org/2003/05/soap-env",):
soap_ns = request.attributes()[k].localName
soap_uri = request.attributes()[k].value
-
+
# If the value from attributes on Envelope is in additional namespaces
elif v in self.namespaces.values():
_ns = request.attributes()[k].localName
_uri = request.attributes()[k].value
- _ns_reversed[_uri] = _ns # update with received alias
+ _ns_reversed[_uri] = _ns # update with received alias
# Now we change 'external' and 'model' to the received forms i.e. 'ext' and 'mod'
# After that we know how the client has prefixed additional namespaces
-
+
ns = NS_RX.findall(xml)
for k, v in ns:
if v in self.namespaces.values():
_ns_reversed[v] = k
-
+
soap_fault_code = 'Client'
-
+
# parse request message and get local method
method = request('Body', ns=soap_uri).children()(0)
if action:
- # method name = action
+ # method name = action
name = action[len(self.action)+1:-1]
prefix = self.prefix
if not action or not name:
@@ -159,21 +170,21 @@ class SoapDispatcher(object):
log.debug('dispatch method: %s', name)
function, returns_types, args_types, doc = self.methods[name]
log.debug('returns_types %s', returns_types)
-
+
# de-serialize parameters (if type definitions given)
if args_types:
args = method.children().unmarshall(args_types)
elif args_types is None:
- args = {'request': method} # send raw request
+ args = {'request': method} # send raw request
else:
- args = {} # no parameters
-
+ args = {} # no parameters
+
soap_fault_code = 'Server'
# execute function
ret = function(**args)
log.debug('dispathed method returns: %s', ret)
- except Exception: # This shouldn't be one huge try/except
+ except Exception: # This shouldn't be one huge try/except
import sys
etype, evalue, etb = sys.exc_info()
log.error(traceback.format_exc())
@@ -182,43 +193,43 @@ class SoapDispatcher(object):
detail += '\n\nXML REQUEST\n\n' + xml
else:
detail = None
- fault = {'faultcode': "%s.%s" % (soap_fault_code, etype.__name__),
- 'faultstring': unicode(evalue),
- 'detail': detail}
+ fault.update({'faultcode': "%s.%s" % (soap_fault_code, etype.__name__),
+ 'faultstring': evalue,
+ 'detail': detail})
# build response message
if not prefix:
- xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>"""
+ xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"/>"""
else:
xml = """<%(soap_ns)s:Envelope xmlns:%(soap_ns)s="%(soap_uri)s"
- xmlns:%(prefix)s="%(namespace)s"/>"""
-
+ xmlns:%(prefix)s="%(namespace)s"/>"""
+
xml %= { # a %= {} is a shortcut for a = a % {}
- 'namespace': self.namespace,
+ 'namespace': self.namespace,
'prefix': prefix,
- 'soap_ns': soap_ns,
+ 'soap_ns': soap_ns,
'soap_uri': soap_uri
}
-
+
# Now we add extra namespaces
xml = SoapDispatcher._extra_namespaces(xml, _ns_reversed)
-
+
# Change our namespace alias to that given by the client.
# We put [('model', 'http://model.common.mt.moboperator'), ('external', 'http://external.mt.moboperator')]
# mix it with {'http://external.mt.moboperator': 'ext', 'http://model.common.mt.moboperator': 'mod'}
- mapping = dict(((k, _ns_reversed[v]) for k,v in self.namespaces.iteritems())) # Switch keys-values and change value
+ mapping = dict(((k, _ns_reversed[v]) for k, v in self.namespaces.items())) # Switch keys-values and change value
# and get {'model': u'mod', 'external': u'ext'}
-
- response = SimpleXMLElement(xml,
+
+ response = SimpleXMLElement(xml,
namespace=self.namespace,
- namespaces_map = mapping,
+ namespaces_map=mapping,
prefix=prefix)
-
+
response['xmlns:xsi'] = "http://www.w3.org/2001/XMLSchema-instance"
response['xmlns:xsd'] = "http://www.w3.org/2001/XMLSchema"
-
+
body = response.add_child("%s:Body" % soap_ns, ns=False)
-
+
if fault:
# generate a Soap Fault (with the python exception)
body.marshall("%s:Fault" % soap_ns, fault, ns=False)
@@ -226,14 +237,24 @@ class SoapDispatcher(object):
# return normal value
res = body.add_child("%sResponse" % name, ns=prefix)
if not prefix:
- res['xmlns'] = self.namespace # add target namespace
+ res['xmlns'] = self.namespace # add target namespace
# serialize returned values (response) if type definition available
if returns_types:
- if not isinstance(ret, dict):
+ # TODO: full sanity check of type structure (recursive)
+ complex_type = isinstance(ret, dict)
+ if complex_type:
+ # check if type mapping correlates with return value
+ types_ok = all([k in returns_types for k in ret.keys()])
+ if not types_ok:
+ warnings.warn("Return value doesn't match type structure: "
+ "%s vs %s" % (str(returns_types), str(ret)))
+ if not complex_type or not types_ok:
+ # backward compatibility for scalar and simple types
res.marshall(returns_types.keys()[0], ret, )
else:
- for k,v in ret.items():
+ # new style for complex classes
+ for k, v in ret.items():
res.marshall(k, v)
elif returns_types is None:
# merge xmlelement returned
@@ -246,16 +267,16 @@ class SoapDispatcher(object):
# Introspection functions:
def list_methods(self):
- "Return a list of aregistered operations"
- return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()]
+ """Return a list of aregistered operations"""
+ return [(method, doc) for method, (function, returns, args, doc) in self.methods.items()]
def help(self, method=None):
- "Generate sample request and response messages"
+ """Generate sample request and response messages"""
(function, returns, args, doc) = self.methods[method]
xml = """
<%(method)s xmlns="%(namespace)s"/>
-""" % {'method':method, 'namespace':self.namespace}
+""" % {'method': method, 'namespace': self.namespace}
request = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if args:
items = args.items()
@@ -263,13 +284,13 @@ class SoapDispatcher(object):
items = [('value', None)]
else:
items = []
- for k,v in items:
+ for k, v in items:
request(method).marshall(k, v, add_comments=True, ns=False)
xml = """
<%(method)sResponse xmlns="%(namespace)s"/>
-""" % {'method':method, 'namespace':self.namespace}
+""" % {'method': method, 'namespace': self.namespace}
response = SimpleXMLElement(xml, namespace=self.namespace, prefix=self.prefix)
if returns:
items = returns.items()
@@ -277,16 +298,15 @@ class SoapDispatcher(object):
items = [('value', None)]
else:
items = []
- for k,v in items:
- response('%sResponse'%method).marshall(k, v, add_comments=True, ns=False)
+ for k, v in items:
+ response('%sResponse' % method).marshall(k, v, add_comments=True, ns=False)
return request.as_xml(pretty=True), response.as_xml(pretty=True), doc
-
def wsdl(self):
- "Generate Web Service Description v1.1"
+ """Generate Web Service Description v1.1"""
xml = """
-response (generic, any type)"
+ """Copy request->response (generic, any type)"""
return request.value
- dispatcher.register_function('Adder', adder,
- returns={'AddResult': {'ab': int, 'dd': str } },
- args={'p': {'a': int,'b': int}, 'dt': Date, 'c': [{'d': Decimal}]})
+ dispatcher.register_function(
+ 'Adder', adder,
+ returns={'AddResult': {'ab': int, 'dd': unicode, 'dt': datetime.date}},
+ args={'p': {'a': int, 'b': int}, 'dt': Date, 'c': [{'d': Decimal}]}
+ )
- dispatcher.register_function('Dummy', dummy,
- returns={'out0': str},
- args={'in0': str})
+ dispatcher.register_function(
+ 'Dummy', dummy,
+ returns={'out0': str},
+ args={'in0': str}
+ )
dispatcher.register_function('Echo', echo)
if '--local' in sys.argv:
- wsdl=dispatcher.wsdl()
- print wsdl
-
- # Commented because path is platform dependent
- # Looks that it doesnt matter.
- # open("C:/test.wsdl","w").write(wsdl)
+ wsdl = dispatcher.wsdl()
for method, doc in dispatcher.list_methods():
request, response, doc = dispatcher.help(method)
- ##print request
- ##print response
-
+
if '--serve' in sys.argv:
- print "Starting server..."
+ log.info("Starting server...")
httpd = HTTPServer(("", 8008), SOAPHandler)
httpd.dispatcher = dispatcher
httpd.serve_forever()
+ if '--wsgi-serve' in sys.argv:
+ log.info("Starting wsgi server...")
+ from wsgiref.simple_server import make_server
+ application = WSGISOAPHandler(dispatcher)
+ wsgid = make_server('', 8008, application)
+ wsgid.serve_forever()
+
if '--consume' in sys.argv:
- from client import SoapClient
+ from .client import SoapClient
client = SoapClient(
- location = "http://localhost:8008/",
- action = 'http://localhost:8008/', # SOAPAction
- namespace = "http://example.com/sample.wsdl",
+ location="http://localhost:8008/",
+ action='http://localhost:8008/', # SOAPAction
+ namespace="http://example.com/sample.wsdl",
soap_ns='soap',
- trace = True,
- ns = False)
- response = client.Adder(p={'a':1,'b':2},dt='20100724',c=[{'d':'1.20'},{'d':'2.01'}])
+ trace=True,
+ ns=False
+ )
+ p = {'a': 1, 'b': 2}
+ c = [{'d': '1.20'}, {'d': '2.01'}]
+ response = client.Adder(p=p, dt='2010-07-24', c=c)
result = response.AddResult
- print int(result.ab)
- print str(result.dd)
+ log.info(int(result.ab))
+ log.info(str(result.dd))
+
+ if '--consume-wsdl' in sys.argv:
+ from .client import SoapClient
+ client = SoapClient(
+ wsdl="http://localhost:8008/",
+ )
+ p = {'a': 1, 'b': 2}
+ c = [{'d': '1.20'}, {'d': '2.01'}]
+ dt = datetime.date.today()
+ response = client.Adder(p=p, dt=dt, c=c)
+ result = response['AddResult']
+ log.info(int(result['ab']))
+ log.info(str(result['dd']))
+
diff --git a/gluon/contrib/pysimplesoap/simplexml.py b/gluon/contrib/pysimplesoap/simplexml.py
index 2a57c902..9a23497a 100755
--- a/gluon/contrib/pysimplesoap/simplexml.py
+++ b/gluon/contrib/pysimplesoap/simplexml.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: latin-1 -*-
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,164 +10,47 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
-"Simple XML manipulation"
+"""Simple XML manipulation"""
-__author__ = "Mariano Reingart (reingart@gmail.com)"
-__copyright__ = "Copyright (C) 2008/009 Mariano Reingart"
-__license__ = "LGPL 3.0"
-__version__ = "1.03a"
-import datetime
+from __future__ import unicode_literals
+import sys
+if sys.version > '3':
+ basestring = str
+ unicode = str
+
import logging
import re
import time
-import warnings
import xml.dom.minidom
-from decimal import Decimal
+
+from . import __author__, __copyright__, __license__, __version__
+
+# Utility functions used for marshalling, moved aside for readability
+from .helpers import TYPE_MAP, TYPE_MARSHAL_FN, TYPE_UNMARSHAL_FN, \
+ REVERSE_TYPE_MAP, OrderedDict, Date, Decimal
log = logging.getLogger(__name__)
-logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
-
-DEBUG = False
-
-try:
- _strptime = datetime.datetime.strptime
-except AttributeError: # python2.4
- _strptime = lambda s, fmt: datetime.datetime(*(time.strptime(s, fmt)[:6]))
-
-
-# Functions to serialize/deserialize special immutable types:
-def datetime_u(s):
- fmt = "%Y-%m-%dT%H:%M:%S"
- try:
- return _strptime(s, fmt)
- except ValueError:
- try:
- # strip utc offset
- if s[-3] == ":" and s[-6] in (' ', '-', '+'):
- warnings.warn('removing unsupported UTC offset', RuntimeWarning)
- s = s[:-6]
- # parse microseconds
- try:
- return _strptime(s, fmt + ".%f")
- except:
- return _strptime(s, fmt)
- except ValueError:
- # strip microseconds (not supported in this platform)
- if "." in s:
- warnings.warn('removing unsuppported microseconds', RuntimeWarning)
- s = s[:s.index(".")]
- return _strptime(s, fmt)
-
-datetime_m = lambda dt: dt.isoformat('T')
-date_u = lambda s: _strptime(s[0:10], "%Y-%m-%d").date()
-date_m = lambda d: d.strftime("%Y-%m-%d")
-time_u = lambda s: _strptime(s, "%H:%M:%S").time()
-time_m = lambda d: d.strftime("%H%M%S")
-bool_u = lambda s: {'0':False, 'false': False, '1': True, 'true': True}[s]
-bool_m = lambda s: {False: 'false', True: 'true'}[s]
-
-# aliases:
-class Alias(object):
- def __init__(self, py_type, xml_type):
- self.py_type, self.xml_type = py_type, xml_type
- def __call__(self, value):
- return self.py_type(value)
- def __repr__(self):
- return "" % (self.xml_type, self.py_type)
-
-byte = Alias(str,'byte')
-short = Alias(int,'short')
-double = Alias(float,'double')
-integer = Alias(long,'integer')
-DateTime = datetime.datetime
-Date = datetime.date
-Time = datetime.time
-
-# Define convertion function (python type): xml schema type
-TYPE_MAP = {
- str:'string',
- unicode:'string',
- bool:'boolean',
- short:'short',
- byte:'byte',
- int:'int',
- long:'long',
- integer:'integer',
- float:'float',
- double:'double',
- Decimal:'decimal',
- datetime.datetime:'dateTime',
- datetime.date:'date',
-}
-TYPE_MARSHAL_FN = {
- datetime.datetime:datetime_m,
- datetime.date:date_m,
- bool:bool_m
-}
-TYPE_UNMARSHAL_FN = {
- datetime.datetime:datetime_u,
- datetime.date:date_u,
- bool:bool_u,
- str:unicode,
-}
-
-REVERSE_TYPE_MAP = dict([(v,k) for k,v in TYPE_MAP.items()])
-
-class OrderedDict(dict):
- "Minimal ordered dictionary for xsd:sequences"
- def __init__(self):
- self.__keys = []
- self.array = False
- def __setitem__(self, key, value):
- if key not in self.__keys:
- self.__keys.append(key)
- dict.__setitem__(self, key, value)
- def insert(self, key, value, index=0):
- if key not in self.__keys:
- self.__keys.insert(index, key)
- dict.__setitem__(self, key, value)
- def __delitem__(self, key):
- if key in self.__keys:
- self.__keys.remove(key)
- dict.__delitem__(self, key)
- def __iter__(self):
- return iter(self.__keys)
- def keys(self):
- return self.__keys
- def items(self):
- return [(key, self[key]) for key in self.__keys]
- def update(self, other):
- for k,v in other.items():
- self[k] = v
- if isinstance(other, OrderedDict):
- self.array = other.array
- def __str__(self):
- return "*%s*" % dict.__str__(self)
- def __repr__(self):
- s= "*{%s}*" % ", ".join(['%s: %s' % (repr(k),repr(v)) for k,v in self.items()])
- if self.array and False:
- s = "[%s]" % s
- return s
class SimpleXMLElement(object):
- "Simple XML manipulation (simil PHP)"
-
- def __init__(self, text = None, elements = None, document = None,
- namespace = None, prefix=None, namespaces_map={}):
+ """Simple XML manipulation (simil PHP)"""
+
+ def __init__(self, text=None, elements=None, document=None,
+ namespace=None, prefix=None, namespaces_map={}, jetty=False):
"""
:param namespaces_map: How to map our namespace prefix to that given by the client;
{prefix: received_prefix}
"""
self.__namespaces_map = namespaces_map
- _rx = "|".join(namespaces_map.keys()) # {'external': 'ext', 'model': 'mod'} -> 'external|model'
- self.__ns_rx = re.compile(r"^(%s):.*$" % _rx) # And now we build an expression ^(external|model):.*$
- # to find prefixes in all xml nodes i.e.: 1
- # and later change that to 1
+ _rx = "|".join(namespaces_map.keys()) # {'external': 'ext', 'model': 'mod'} -> 'external|model'
+ self.__ns_rx = re.compile(r"^(%s):.*$" % _rx) # And now we build an expression ^(external|model):.*$
+ # to find prefixes in all xml nodes i.e.: 1
+ # and later change that to 1
self.__ns = namespace
self.__prefix = prefix
-
+ self.__jetty = jetty # special list support
+
if text is not None:
try:
self.__document = xml.dom.minidom.parseString(text)
@@ -178,78 +61,81 @@ class SimpleXMLElement(object):
else:
self.__elements = elements
self.__document = document
-
+
def add_child(self, name, text=None, ns=True):
- "Adding a child tag to a node"
- if not ns or not self.__ns:
- log.debug('adding %s', name)
+ """Adding a child tag to a node"""
+ if not ns or self.__ns is False:
+ ##log.debug('adding %s without namespace', name)
element = self.__document.createElement(name)
else:
- log.debug('adding %s ns "%s" %s', name, self.__ns, ns)
- if self.__prefix:
+ ##log.debug('adding %s ns "%s" %s', name, self.__ns, ns)
+ if isinstance(ns, basestring):
+ element = self.__document.createElement(name)
+ if ns:
+ element.setAttribute("xmlns", ns)
+ elif self.__prefix:
element = self.__document.createElementNS(self.__ns, "%s:%s" % (self.__prefix, name))
else:
element = self.__document.createElementNS(self.__ns, name)
# don't append null tags!
if text is not None:
- if isinstance(text, unicode):
- element.appendChild(self.__document.createTextNode(text))
- else:
- element.appendChild(self.__document.createTextNode(str(text)))
+ element.appendChild(self.__document.createTextNode(text))
self._element.appendChild(element)
return SimpleXMLElement(
- elements=[element],
- document=self.__document,
- namespace=self.__ns,
- prefix=self.__prefix,
- namespaces_map=self.__namespaces_map)
-
+ elements=[element],
+ document=self.__document,
+ namespace=self.__ns,
+ prefix=self.__prefix,
+ jetty=self.__jetty,
+ namespaces_map=self.__namespaces_map
+ )
+
def __setattr__(self, tag, text):
- "Add text child tag node (short form)"
+ """Add text child tag node (short form)"""
if tag.startswith("_"):
object.__setattr__(self, tag, text)
else:
- log.debug('__setattr__(%s, %s)', tag, text)
+ ##log.debug('__setattr__(%s, %s)', tag, text)
self.add_child(tag, text)
def __delattr__(self, tag):
- "Remove a child tag (non recursive!)"
- elements=[__element for __element in self._element.childNodes
- if __element.nodeType == __element.ELEMENT_NODE
- ]
+ """Remove a child tag (non recursive!)"""
+ elements = [__element for __element in self._element.childNodes
+ if __element.nodeType == __element.ELEMENT_NODE]
for element in elements:
self._element.removeChild(element)
def add_comment(self, data):
- "Add an xml comment to this child"
+ """Add an xml comment to this child"""
comment = self.__document.createComment(data)
self._element.appendChild(comment)
def as_xml(self, filename=None, pretty=False):
- "Return the XML representation of the document"
+ """Return the XML representation of the document"""
if not pretty:
return self.__document.toxml('UTF-8')
else:
return self.__document.toprettyxml(encoding='UTF-8')
def __repr__(self):
- "Return the XML representation of this tag"
+ """Return the XML representation of this tag"""
+ # NOTE: do not use self.as_xml('UTF-8') as it returns the whole xml doc
return self._element.toxml('UTF-8')
def get_name(self):
- "Return the tag name of this node"
+ """Return the tag name of this node"""
return self._element.tagName
def get_local_name(self):
- "Return the tag loca name (prefix:name) of this node"
+ """Return the tag local name (prefix:name) of this node"""
return self._element.localName
def get_prefix(self):
- "Return the namespace prefix of this node"
+ """Return the namespace prefix of this node"""
return self._element.prefix
def get_namespace_uri(self, ns):
- "Return the namespace uri for a prefix"
+ """Return the namespace uri for a prefix"""
element = self._element
while element is not None and element.attributes is not None:
try:
@@ -257,38 +143,39 @@ class SimpleXMLElement(object):
except KeyError:
element = element.parentNode
-
def attributes(self):
- "Return a dict of attributes for this tag"
+ """Return a dict of attributes for this tag"""
#TODO: use slice syntax [:]?
return self._element.attributes
def __getitem__(self, item):
- "Return xml tag attribute value or a slice of attributes (iter)"
- log.debug('__getitem__(%s)', item)
+ """Return xml tag attribute value or a slice of attributes (iter)"""
+ ##log.debug('__getitem__(%s)', item)
if isinstance(item, basestring):
if self._element.hasAttribute(item):
return self._element.attributes[item].value
elif isinstance(item, slice):
# return a list with name:values
- return self._element.attributes.items()[item]
+ return list(self._element.attributes.items())[item]
else:
# return element by index (position)
element = self.__elements[item]
return SimpleXMLElement(
- elements=[element],
- document=self.__document,
- namespace=self.__ns,
- prefix=self.__prefix,
- namespaces_map=self.__namespaces_map)
-
+ elements=[element],
+ document=self.__document,
+ namespace=self.__ns,
+ prefix=self.__prefix,
+ jetty=self.__jetty,
+ namespaces_map=self.__namespaces_map
+ )
+
def add_attribute(self, name, value):
- "Set an attribute value from a string"
+ """Set an attribute value from a string"""
self._element.setAttribute(name, value)
-
+
def __setitem__(self, item, value):
- "Set an attribute value"
- if isinstance(item,basestring):
+ """Set an attribute value"""
+ if isinstance(item, basestring):
self.add_attribute(item, value)
elif isinstance(item, slice):
# set multiple attributes at once
@@ -297,7 +184,7 @@ class SimpleXMLElement(object):
def __call__(self, tag=None, ns=None, children=False, root=False,
error=True, ):
- "Search (even in child nodes) and return a child tag by name"
+ """Search (even in child nodes) and return a child tag by name"""
try:
if root:
# return entire document
@@ -306,6 +193,7 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
+ jetty=self.__jetty,
namespaces_map=self.__namespaces_map
)
if tag is None:
@@ -317,23 +205,23 @@ class SimpleXMLElement(object):
elements = None
if isinstance(tag, int):
# return tag by index
- elements=[self.__elements[tag]]
+ elements = [self.__elements[tag]]
if ns and not elements:
for ns_uri in isinstance(ns, (tuple, list)) and ns or (ns, ):
- log.debug('searching %s by ns=%s', tag, ns_uri)
+ ##log.debug('searching %s by ns=%s', tag, ns_uri)
elements = self._element.getElementsByTagNameNS(ns_uri, tag)
- if elements:
+ if elements:
break
if self.__ns and not elements:
- log.debug('searching %s by ns=%s', tag, self.__ns)
+ ##log.debug('searching %s by ns=%s', tag, self.__ns)
elements = self._element.getElementsByTagNameNS(self.__ns, tag)
if not elements:
- log.debug('searching %s', tag)
+ ##log.debug('searching %s', tag)
elements = self._element.getElementsByTagName(tag)
if not elements:
- #log.debug(self._element.toxml())
+ ##log.debug(self._element.toxml())
if error:
- raise AttributeError(u"No elements found")
+ raise AttributeError("No elements found")
else:
return
return SimpleXMLElement(
@@ -341,16 +229,17 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
+ jetty=self.__jetty,
namespaces_map=self.__namespaces_map)
- except AttributeError, e:
- raise AttributeError(u"Tag not found: %s (%s)" % (tag, unicode(e)))
+ except AttributeError as e:
+ raise AttributeError("Tag not found: %s (%s)" % (tag, e))
def __getattr__(self, tag):
- "Shortcut for __call__"
+ """Shortcut for __call__"""
return self.__call__(tag)
-
+
def __iter__(self):
- "Iterate over xml tags at this level"
+ """Iterate over xml tags at this level"""
try:
for __element in self.__elements:
yield SimpleXMLElement(
@@ -358,67 +247,72 @@ class SimpleXMLElement(object):
document=self.__document,
namespace=self.__ns,
prefix=self.__prefix,
+ jetty=self.__jetty,
namespaces_map=self.__namespaces_map)
except:
raise
def __dir__(self):
- "List xml children tags names"
- return [node.tagName for node
+ """List xml children tags names"""
+ return [node.tagName for node
in self._element.childNodes
if node.nodeType != node.TEXT_NODE]
def children(self):
- "Return xml children tags element"
- elements=[__element for __element in self._element.childNodes
- if __element.nodeType == __element.ELEMENT_NODE]
+ """Return xml children tags element"""
+ elements = [__element for __element in self._element.childNodes
+ if __element.nodeType == __element.ELEMENT_NODE]
if not elements:
return None
#raise IndexError("Tag %s has no children" % self._element.tagName)
return SimpleXMLElement(
- elements=elements,
- document=self.__document,
- namespace=self.__ns,
- prefix=self.__prefix,
- namespaces_map=self.__namespaces_map)
+ elements=elements,
+ document=self.__document,
+ namespace=self.__ns,
+ prefix=self.__prefix,
+ jetty=self.__jetty,
+ namespaces_map=self.__namespaces_map
+ )
def __len__(self):
- "Return elements count"
+ """Return element count"""
return len(self.__elements)
-
- def __contains__( self, item):
- "Search for a tag name in this element or child nodes"
+
+ def __contains__(self, item):
+ """Search for a tag name in this element or child nodes"""
return self._element.getElementsByTagName(item)
-
+
def __unicode__(self):
- "Returns the unicode text nodes of the current element"
+ """Returns the unicode text nodes of the current element"""
if self._element.childNodes:
- rc = u""
+ rc = ""
for node in self._element.childNodes:
if node.nodeType == node.TEXT_NODE:
rc = rc + node.data
return rc
return ''
-
+
def __str__(self):
- "Returns the str text nodes of the current element"
- return unicode(self).encode("utf8","ignore")
+ """Returns the str text nodes of the current element"""
+ return self.__unicode__()
def __int__(self):
- "Returns the integer value of the current element"
+ """Returns the integer value of the current element"""
return int(self.__str__())
def __float__(self):
- "Returns the float value of the current element"
+ """Returns the float value of the current element"""
try:
return float(self.__str__())
except:
- raise IndexError(self._element.toxml())
-
+ raise IndexError(self._element.toxml())
+
_element = property(lambda self: self.__elements[0])
def unmarshall(self, types, strict=True):
- "Convert to python values the current serialized xml element"
+ #import pdb; pdb.set_trace()
+
+ """Convert to python values the current serialized xml element"""
# types is a dict of {tag name: convertion function}
# strict=False to use default type conversion if not specified
# example: types={'p': {'a': int,'b': int}, 'c': [{'d':str}]}
@@ -435,37 +329,62 @@ class SimpleXMLElement(object):
if ref_node['id'] == href:
node = ref_node
ref_name_type = ref_node['xsi:type'].split(":")[1]
- break
+ break
+
try:
- fn = types[name]
- except (KeyError, ), e:
- if node.get_namespace_uri("soapenc"):
- fn = None # ignore multirefs!
- elif 'xsi:type' in node.attributes().keys():
+ if isinstance(types, dict):
+ fn = types[name]
+ # custom array only in the response (not defined in the WSDL):
+ # 1):
+ # Jetty array style support [{k, v}]
+ for parent in node:
+ tmp_dict = {} # unmarshall each value & mix
+ for child in (node.children() or []):
+ tmp_dict.update(child.unmarshall(fn[0], strict))
+ value.append(tmp_dict)
+ else: # .Net / Java
+ for child in (children or []):
+ value.append(child.unmarshall(fn[0], strict))
+
elif isinstance(fn, tuple):
value = []
_d = {}
children = node.children()
as_dict = len(fn) == 1 and isinstance(fn[0], dict)
- for child in (children and children() or []): # Readability counts
+ for child in (children and children() or []): # Readability counts
if as_dict:
- _d.update(child.unmarshall(fn[0], strict)) # Merging pairs
+ _d.update(child.unmarshall(fn[0], strict)) # Merging pairs
else:
value.append(child.unmarshall(fn[0], strict))
if as_dict:
@@ -477,32 +396,32 @@ class SimpleXMLElement(object):
value = tuple(_tmp)
else:
value = tuple(value)
-
+
elif isinstance(fn, dict):
##if ref_name_type is not None:
## fn = fn[ref_name_type]
children = node.children()
value = children and children.unmarshall(fn, strict)
else:
- if fn is None: # xsd:anyType not unmarshalled
+ if fn is None: # xsd:anyType not unmarshalled
value = node
- elif str(node) or fn == str:
+ elif unicode(node) or (fn == str and unicode(node) != ''):
try:
# get special deserialization function (if any)
- fn = TYPE_UNMARSHAL_FN.get(fn,fn)
+ fn = TYPE_UNMARSHAL_FN.get(fn, fn)
if fn == str:
# always return an unicode object:
+ # (avoid encoding errors in py<3!)
value = unicode(node)
else:
value = fn(unicode(node))
- except (ValueError, TypeError), e:
- raise ValueError(u"Tag: %s: %s" % (name, unicode(e)))
+ except (ValueError, TypeError) as e:
+ raise ValueError("Tag: %s: %s" % (name, e))
else:
value = None
d[name] = value
return d
-
-
+
def _update_ns(self, name):
"""Replace the defined namespace alias with tohse used by the client."""
pref = self.__ns_rx.search(name)
@@ -513,46 +432,50 @@ class SimpleXMLElement(object):
except KeyError:
log.warning('Unknown namespace alias %s' % name)
return name
-
-
- def marshall(self, name, value, add_child=True, add_comments=False,
+
+ def marshall(self, name, value, add_child=True, add_comments=False,
ns=False, add_children_ns=True):
- "Analize python value and add the serialized XML element using tag name"
+ """Analyze python value and add the serialized XML element using tag name"""
# Change node name to that used by a client
name = self._update_ns(name)
-
+
if isinstance(value, dict): # serialize dict (value)
+ # for the first parent node, use the document target namespace
+ # (ns==True) or use the namespace string uri if passed (elements)
child = add_child and self.add_child(name, ns=ns) or self
- for k,v in value.items():
+ for k, v in value.items():
if not add_children_ns:
ns = False
+ else:
+ # for children, use the wsdl element target namespace:
+ ns = getattr(value, 'namespace', None)
child.marshall(k, v, add_comments=add_comments, ns=ns)
elif isinstance(value, tuple): # serialize tuple (value)
child = add_child and self.add_child(name, ns=ns) or self
if not add_children_ns:
ns = False
- for k,v in value:
+ for k, v in value:
getattr(self, name).marshall(k, v, add_comments=add_comments, ns=ns)
- elif isinstance(value, list): # serialize lists
- child=self.add_child(name, ns=ns)
+ elif isinstance(value, list): # serialize lists
+ child = self.add_child(name, ns=ns)
if not add_children_ns:
ns = False
if add_comments:
child.add_comment("Repetitive array of:")
for t in value:
child.marshall(name, t, False, add_comments=add_comments, ns=ns)
- elif isinstance(value, basestring): # do not convert strings or unicodes
- self.add_child(name, value,ns=ns)
- elif value is None: # sent a empty tag?
+ elif isinstance(value, basestring): # do not convert strings or unicodes
+ self.add_child(name, value, ns=ns)
+ elif value is None: # sent a empty tag?
self.add_child(name, ns=ns)
elif value in TYPE_MAP.keys():
# add commented placeholders for simple tipes (for examples/help only)
- child = self.add_child(name, ns=ns)
+ child = self.add_child(name, ns=ns)
child.add_comment(TYPE_MAP[value])
- else: # the rest of object types are converted to string
+ else: # the rest of object types are converted to string
# get special serialization function (if any)
fn = TYPE_MARSHAL_FN.get(type(value), str)
- self.add_child(name, fn(value), ns=ns)
+ self.add_child(name, fn(value), ns=ns)
def import_node(self, other):
x = self.__document.importNode(other._element, True) # deep copy
diff --git a/gluon/contrib/pysimplesoap/transport.py b/gluon/contrib/pysimplesoap/transport.py
index 5441e342..87806c06 100644
--- a/gluon/contrib/pysimplesoap/transport.py
+++ b/gluon/contrib/pysimplesoap/transport.py
@@ -1,5 +1,5 @@
-#!/usr/bin/env python
-# -*- coding: latin-1 -*-
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by the
# Free Software Foundation; either version 3, or (at your option) any later
@@ -10,30 +10,46 @@
# or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
# for more details.
-"Pythonic simple SOAP Client implementation"
+"""Pythonic simple SOAP Client transport"""
-__author__ = "Mariano Reingart (reingart@gmail.com)"
-__copyright__ = "Copyright (C) 2008 Mariano Reingart"
-__license__ = "LGPL 3.0"
-TIMEOUT = 60
-
-import os
-import cPickle as pickle
-import urllib2
-from urlparse import urlparse
-import tempfile
-from simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict
import logging
+import sys
+try:
+ import urllib2
+ from cookielib import CookieJar
+except ImportError:
+ from urllib import request as urllib2
+ from http.cookiejar import CookieJar
+
+from . import __author__, __copyright__, __license__, __version__, TIMEOUT
+from .simplexml import SimpleXMLElement, TYPE_MAP, OrderedDict
log = logging.getLogger(__name__)
-logging.basicConfig(format='%(levelname)s:%(message)s', level=logging.WARNING)
+
+#
+# Socket wrapper to enable socket.TCP_NODELAY - this greatly speeds up transactions in Linux
+# WARNING: this will modify the standard library socket module, use with care!
+# TODO: implement this as a transport faciliy
+# (to pass options directly to httplib2 or pycurl)
+# be aware of metaclasses and socks.py (SocksiPy) used by httplib2
+
+if False:
+ import socket
+ realsocket = socket.socket
+ def socketwrap(family=socket.AF_INET, type=socket.SOCK_STREAM, proto=0):
+ sockobj = realsocket(family, type, proto)
+ if type == socket.SOCK_STREAM:
+ sockobj.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+ return sockobj
+ socket.socket = socketwrap
#
# We store metadata about what available transport mechanisms we have available.
#
-_http_connectors = {} # libname: classimpl mapping
-_http_facilities = {} # functionalitylabel: [sequence of libname] mapping
+_http_connectors = {} # libname: classimpl mapping
+_http_facilities = {} # functionalitylabel: [sequence of libname] mapping
+
class TransportBase:
@classmethod
@@ -45,27 +61,41 @@ class TransportBase:
#
try:
import httplib2
+ if sys.version > '3' and httplib2.__version__ <= "0.7.7":
+ import http.client
+ # httplib2 workaround: check_hostname needs a SSL context with either
+ # CERT_OPTIONAL or CERT_REQUIRED
+ # see https://code.google.com/p/httplib2/issues/detail?id=173
+ orig__init__ = http.client.HTTPSConnection.__init__
+ def fixer(self, host, port, key_file, cert_file, timeout, context,
+ check_hostname, *args, **kwargs):
+ chk = kwargs.get('disable_ssl_certificate_validation', True) ^ True
+ orig__init__(self, host, port=port, key_file=key_file,
+ cert_file=cert_file, timeout=timeout, context=context,
+ check_hostname=chk)
+ http.client.HTTPSConnection.__init__ = fixer
except ImportError:
- TIMEOUT = None # timeout not supported by urllib2
+ TIMEOUT = None # timeout not supported by urllib2
pass
else:
class Httplib2Transport(httplib2.Http, TransportBase):
_wrapper_version = "httplib2 %s" % httplib2.__version__
_wrapper_name = 'httplib2'
+
def __init__(self, timeout, proxy=None, cacert=None, sessions=False):
##httplib2.debuglevel=4
kwargs = {}
if proxy:
import socks
kwargs['proxy_info'] = httplib2.ProxyInfo(proxy_type=socks.PROXY_TYPE_HTTP, **proxy)
- print "using proxy", proxy
+ log.info("using proxy %s" % proxy)
# set optional parameters according supported httplib2 version
if httplib2.__version__ >= '0.3.0':
kwargs['timeout'] = timeout
if httplib2.__version__ >= '0.7.0':
kwargs['disable_ssl_certificate_validation'] = cacert is None
- kwargs['ca_certs'] = cacert
+ kwargs['ca_certs'] = cacert
httplib2.Http.__init__(self, **kwargs)
_http_connectors['httplib2'] = Httplib2Transport
@@ -76,15 +106,15 @@ else:
if 'timeout' in inspect.getargspec(httplib2.Http.__init__)[0]:
_http_facilities.setdefault('timeout', []).append('httplib2')
+
#
# urllib2 support.
#
-import urllib2
class urllib2Transport(TransportBase):
_wrapper_version = "urllib2 %s" % urllib2.__version__
- _wrapper_name = 'urllib2'
+ _wrapper_name = 'urllib2'
+
def __init__(self, timeout=None, proxy=None, cacert=None, sessions=False):
- import sys
if (timeout is not None) and not self.supports_feature('timeout'):
raise RuntimeError('timeout is not supported with urllib2 transport')
if proxy:
@@ -94,26 +124,26 @@ class urllib2Transport(TransportBase):
self.request_opener = urllib2.urlopen
if sessions:
- from cookielib import CookieJar
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(CookieJar()))
self.request_opener = opener.open
-
+
self._timeout = timeout
def request(self, url, method="GET", body=None, headers={}):
req = urllib2.Request(url, body, headers)
try:
f = self.request_opener(req, timeout=self._timeout)
- except urllib2.HTTPError, f:
+ return f.info(), f.read()
+ except urllib2.HTTPError as f:
if f.code != 500:
raise
- return f.info(), f.read()
+ return f.info(), f.read()
_http_connectors['urllib2'] = urllib2Transport
_http_facilities.setdefault('sessions', []).append('urllib2')
import sys
-if sys.version_info >= (2,6):
+if sys.version_info >= (2, 6):
_http_facilities.setdefault('timeout', []).append('urllib2')
del sys
@@ -129,19 +159,23 @@ else:
try:
from cStringIO import StringIO
except ImportError:
- from StringIO import StringIO
+ try:
+ from StringIO import StringIO
+ except ImportError:
+ from io import StringIO
class pycurlTransport(TransportBase):
_wrapper_version = pycurl.version
_wrapper_name = 'pycurl'
+
def __init__(self, timeout, proxy=None, cacert=None, sessions=False):
- self.timeout = timeout
+ self.timeout = timeout
self.proxy = proxy or {}
self.cacert = cacert
-
+
def request(self, url, method, body, headers):
c = pycurl.Curl()
- c.setopt(pycurl.URL, str(url))
+ c.setopt(pycurl.URL, url)
if 'proxy_host' in self.proxy:
c.setopt(pycurl.PROXY, self.proxy['proxy_host'])
if 'proxy_port' in self.proxy:
@@ -154,20 +188,19 @@ else:
#self.body = StringIO(body)
#c.setopt(pycurl.HEADERFUNCTION, self.header)
if self.cacert:
- c.setopt(c.CAINFO, str(self.cacert))
+ c.setopt(c.CAINFO, self.cacert)
c.setopt(pycurl.SSL_VERIFYPEER, self.cacert and 1 or 0)
c.setopt(pycurl.SSL_VERIFYHOST, self.cacert and 2 or 0)
- c.setopt(pycurl.CONNECTTIMEOUT, self.timeout/6)
+ c.setopt(pycurl.CONNECTTIMEOUT, self.timeout / 6)
c.setopt(pycurl.TIMEOUT, self.timeout)
- if method=='POST':
+ if method == 'POST':
c.setopt(pycurl.POST, 1)
- c.setopt(pycurl.POSTFIELDS, body)
+ c.setopt(pycurl.POSTFIELDS, body)
if headers:
- hdrs = ['%s: %s' % (str(k), str(v)) for k, v in headers.items()]
- ##print hdrs
+ hdrs = ['%s: %s' % (k, v) for k, v in headers.items()]
+ log.debug(hdrs)
c.setopt(pycurl.HTTPHEADER, hdrs)
c.perform()
- ##print "pycurl perform..."
c.close()
return {}, self.buf.getvalue()
@@ -178,15 +211,15 @@ else:
class DummyTransport:
- "Testing class to load a xml response"
-
+ """Testing class to load a xml response"""
+
def __init__(self, xml_response):
self.xml_response = xml_response
-
+
def request(self, location, method, body, headers):
- print method, location
- print headers
- print body
+ log.debug("%s %s", method, location)
+ log.debug(headers)
+ log.debug(body)
return {}, self.xml_response
@@ -222,20 +255,19 @@ def get_http_wrapper(library=None, features=[]):
else:
return _http_connectors[candidate_name]
+
def set_http_wrapper(library=None, features=[]):
- "Set a suitable HTTP connection wrapper."
+ """Set a suitable HTTP connection wrapper."""
global Http
Http = get_http_wrapper(library, features)
return Http
def get_Http():
- "Return current transport class"
+ """Return current transport class"""
global Http
return Http
-
+
# define the default HTTP connection class (it can be changed at runtime!):
set_http_wrapper()
-
-
diff --git a/gluon/contrib/webclient.py b/gluon/contrib/webclient.py
index c2f18fb5..602c79e6 100644
--- a/gluon/contrib/webclient.py
+++ b/gluon/contrib/webclient.py
@@ -1,3 +1,4 @@
+
"""
Developed by Massimo Di Pierro
Released under the web2py license (LGPL)
@@ -105,9 +106,9 @@ class WebClient(object):
# assume everything is ok and make http request
error = None
try:
- if isinstance(data,str):
+ if isinstance(data, str):
self.method = 'POST' if method=='auto' else method
- if isinstance(data, dict):
+ elif isinstance(data, dict):
self.method = 'POST' if method=='auto' else method
# if there is only one form, set _formname automatically
if not '_formname' in data and len(self.forms) == 1:
diff --git a/gluon/tests/test_web.py b/gluon/tests/test_web.py
index a241e5ae..54b4059b 100644
--- a/gluon/tests/test_web.py
+++ b/gluon/tests/test_web.py
@@ -40,6 +40,7 @@ def fix_sys_path():
fix_sys_path()
from contrib.webclient import WebClient
+from urllib2 import HTTPError
webserverprocess = None
@@ -140,6 +141,37 @@ class TestWeb(LiveTest):
assert('expires' in s.headers)
assert(s.headers['cache-control'].startswith('max-age'))
+ def testSoap(self):
+ # test soap server implementation
+ from gluon.contrib.pysimplesoap.client import SoapClient, SoapFault
+ url = 'http://127.0.0.1:8000/examples/soap_examples/call/soap?WSDL'
+ client = SoapClient(wsdl=url)
+ ret = client.SubIntegers(a=3, b=2)
+ # check that the value returned is ok
+ assert('SubResult' in ret)
+ assert(ret['SubResult'] == 1)
+
+ try:
+ ret = client.Division(a=3, b=0)
+ except SoapFault, sf:
+ # verify the exception value is ok
+ assert(sf.faultstring == "float division by zero")
+ assert(sf.faultcode == "Server.ZeroDivisionError")
+
+ # store sent and received xml for low level test
+ xml_request = client.xml_request
+ xml_response = client.xml_response
+
+ # do a low level raw soap request (using
+ s = WebClient('http://127.0.0.1:8000/')
+ try:
+ s.post('examples/soap_examples/call/soap', data=xml_request, method="POST")
+ except HTTPError, e:
+ assert(e.msg=='INTERNAL SERVER ERROR')
+ # check internal server error returned (issue 153)
+ assert(s.status == 500)
+ assert(s.text == xml_response)
+
if __name__ == '__main__':
unittest.main()
diff --git a/gluon/tools.py b/gluon/tools.py
index 9e893be4..b5b9aa1d 100644
--- a/gluon/tools.py
+++ b/gluon/tools.py
@@ -4830,9 +4830,15 @@ class Service(object):
for method, (function, returns, args, doc) in procedures.iteritems():
dispatcher.register_function(method, function, returns, args, doc)
if request.env.request_method == 'POST':
+ fault = {}
# Process normal Soap Operation
response.headers['Content-Type'] = 'text/xml'
- return dispatcher.dispatch(request.body.read())
+ xml = dispatcher.dispatch(request.body.read(), fault=fault)
+ if fault:
+ # May want to consider populating a ticket here...
+ response.status = 500
+ # return the soap response
+ return xml
elif 'WSDL' in request.vars:
# Return Web Service Description
response.headers['Content-Type'] = 'text/xml'