Commits

Anonymous committed 668f438

1.Move to cElementTree, this gets memory consumption down by a factor of more than 2. 2. Clean up. 3. Rename

Comments (0)

Files changed (20)

PKG-INFO

-Metadata-Version: 1.0
-Name: Scio
-Version: 0.9.1
-Summary: Scio is a humane SOAP client
-Home-page: http://bitbucket.org/leapfrogdevelopment/scio/overview
-Author: UNKNOWN
-Author-email: oss@leapfrogdevelopment.com
-License: UNKNOWN
-Description: UNKNOWN
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Natural Language :: English
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.4
-Classifier: Programming Language :: Python :: 2.5
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Topic :: Software Development :: Object Brokering
+A small python library to consume SOAP services. It can process
+a WSDL documents and then use types and methods defined in that
+document.
+
+This library is based on some ideas from:
+    Scio - http://pypi.python.org/pypi/Scio/0.9.1
+    suds - https://fedorahosted.org/suds/
+    soaplib - https://github.com/soaplib/soaplib
+With many thanks to the contributors of those libraries.

Scio.egg-info/PKG-INFO

-Metadata-Version: 1.0
-Name: Scio
-Version: 0.9.1
-Summary: Scio is a humane SOAP client
-Home-page: http://bitbucket.org/leapfrogdevelopment/scio/overview
-Author: UNKNOWN
-Author-email: oss@leapfrogdevelopment.com
-License: UNKNOWN
-Description: UNKNOWN
-Platform: UNKNOWN
-Classifier: Development Status :: 5 - Production/Stable
-Classifier: Intended Audience :: Developers
-Classifier: License :: OSI Approved :: BSD License
-Classifier: Natural Language :: English
-Classifier: Programming Language :: Python
-Classifier: Programming Language :: Python :: 2.4
-Classifier: Programming Language :: Python :: 2.5
-Classifier: Programming Language :: Python :: 2.6
-Classifier: Programming Language :: Python :: 2.7
-Classifier: Topic :: Software Development :: Object Brokering

Scio.egg-info/SOURCES.txt

-setup.cfg
-setup.py
-Scio.egg-info/PKG-INFO
-Scio.egg-info/SOURCES.txt
-Scio.egg-info/dependency_links.txt
-Scio.egg-info/requires.txt
-Scio.egg-info/top_level.txt
-scio/__init__.py
-scio/autowsdl.py
-scio/client.py

Scio.egg-info/dependency_links.txt

-

Scio.egg-info/requires.txt

-lxml>=2.2
-python-dateutil>=1.4,<2.0

Scio.egg-info/top_level.txt

-scio
+# scio -- soap classes for input and output
+#
+# Copyright (c) 2011, Leapfrog Direct Response, LLC
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#     * Redistributions of source code must retain the above copyright
+#       notice, this list of conditions and the following disclaimer.
+#     * Redistributions in binary form must reproduce the above copyright
+#       notice, this list of conditions and the following disclaimer in the
+#       documentation and/or other materials provided with the distribution.
+#     * Neither the name of the Leapfrog Direct Response, LLC, including
+#       its subsidiaries and affiliates nor the names of its
+#       contributors, may be used to endorse or promote products derived
+#       from this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LEAPFROG DIRECT
+# RESPONSE, LLC, INCLUDING ITS SUBSIDIARIES AND AFFILIATES, BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+from client import Client
+"""
+    Top level access to SOAP service.
+"""
+
+import wsdl
+import xmltypes
+
+def str_for_containers(self):
+    """
+        Nice printing for types and method containers.
+
+        Containers must have _container attribute containing all
+        elements to be printed.
+    """
+    cont = getattr(self, '_container', None)
+    if cont is None:
+        return ''
+    res = ''
+    for child in cont:
+        descr = str(getattr(getattr(self, child, None), '__doc__', None))
+        if len(descr)>100:
+            descr = descr[:100] + "..."
+        descr = descr.replace("\n", "\n\t")
+        res = res + '\n%s\n\t%s' %(child, descr)
+    res = res[1:]
+    return res
+
+class Client(object):
+    """
+        Top level class to talk to soap services.
+
+        This is an access point to service functionality. The client accepts
+        WSDL address and uses WSDLParser to get all defined types and
+        operations. The types are set to client.types and operations
+        are set to self.service.
+
+        To examine present types or operations simply print (or touch repr):
+            client.types or client.service, correspondingly.
+
+        To create type simply call:
+            client.types.MyTypeName().
+        Class constructor will also create all obligatory (non-nillable) children.
+        To call an operation:
+            client.service.MyOperationName(arg1, arg2, arg3, ...),
+        where arguments are of required types. Arguments can also
+        be passed as keywords or a ready wrapped message.
+
+        If any help is available in the WSDL document it is propagated to the
+        types and operations, see e.g. help client.types.MyTypeName. In addition
+        the help page on an operation displays its call signature.
+
+        Nice printing is also available for all types defined in client.types:
+            print(client.types.MyTypeName())
+
+        .. warning::
+            Only document/literal wrapped convention is implemented at the moment.
+
+        Details
+        -------
+        In reality client.types and client.service are simply containers.
+        The content of these containers is set from results of parsing
+        the wsdl document by WSDLParser.get_types and WSDLParser.get_methods
+        correspondingly.
+
+        The client.types container consists of auto generated (by WSDLParser)
+        class definitions. So that a call to a member returns and instance
+        of the new type. New types are auto-generated according to a special
+        convention by metaclass xmltypes.ComplexTypeMeta.
+
+        The client.service container consists of methods wrapers
+        methods.Method. The method wrapper is callable with free number of
+        parameters. The input and output requirements of a method are
+        contained in methods.Message instances Method.input and
+        Method.output correspondingly. On a call a method converts
+        the input to XML by using Method.input, sends request to the
+        service and finally decodes the response from XML by
+        Method.output.
+
+        Parameters
+        ----------
+        wsdl_url : str
+            Address of wsdl document to consume.
+    """
+    def __init__(self, wsdl_url):
+        #create parser and download the WSDL document
+        self.wsdl_url = wsdl_url
+        parser = wsdl.WSDLParser(wsdl_url)
+        #before getting types we handle anyType
+        #anyType is somewhat tricky, because it must
+        #know all the other types to work, therefore
+        #we recreate it here. In such a way all other
+        #service do not conflict with this instance
+        primmap = wsdl._primmap.copy()
+        primmap['anyType'] = type('XMLAny', (xmltypes.XMLAny,), {})
+        #get all types - a dictionary
+        types = parser.get_types(primmap)
+        primmap['anyType']._types = types.copy()
+        del primmap
+        #get all methods - a dictionary
+        methods = parser.get_methods(types)
+        #create dispatchers for types and methods
+        #first provide nice printing
+        types["_container"] = types.keys()
+        methods["_container"] = methods.keys()
+        types["__str__"] = str_for_containers
+        types["__repr__"] = str_for_containers
+        methods["__str__"] = str_for_containers
+        methods["__repr__"] = str_for_containers
+        self.types = type('TypesDispatcher', (), types)()
+        del types
+        self.service = type('ServiceDispatcher', (), methods)()
+        del methods
+        #get service names for printing
+        self.names = parser.get_service_names()
+
+    def __str__(self):
+        res = ''
+        for name in self.names:
+            res = res + ', %s' %name
+        res = res[2:] + " at:\n\t%s" %(self.wsdl_url)
+        return res
+
+    def __repr__(self):
+        return self.__str__()
+
+
+"""
+    Classes required for remote method calls: messages and method wrappers.
+"""
+from soap import *
+from exceptions import ValueError, RuntimeError
+from urllib2 import urlopen, Request, HTTPError
+import xml.etree.cElementTree as etree
+
+class Message(object):
+    """
+        Message for input and output of service operations.
+
+        Messages perform conversion of Python to xml and backwards
+        of the calls and returns.
+
+        A message instance knows about used style/literal convention and can
+        use it to perform transformations. At the moment only
+        document/literal wrapped is implemented. You can improve this class
+        to have the others.
+
+        .. warning::
+            Only document/literal wrapped convention is implemented at the moment.
+
+        Parameters
+        ----------
+        tag : str
+            Name of the message.
+        namespafe : str
+            Namespace of the message.
+        parts : list
+            List of message parts in the form
+            (part name, part type class).
+            This description is usually found in message part of a WSDL document.
+        style : str
+            Operation style document/rpc.
+        literal : bool
+            True = literal, False = encoded.
+    """
+    def __init__(self, tag, namespace, parts, style, literal):
+        self.tag = tag
+        self.namespace = namespace
+        self.parts = parts
+        self.style = style
+        self.literal = literal
+
+
+    def __str__(self, switch = "wrap"):
+        """
+            String representation of the message in three forms:
+                - wrapped message
+                - positional sub-arguments
+                - keyword sub-arguments.
+                - out - the only child of wrapped message. This applicable
+                        to output message extraction.
+
+            Parameters
+            ----------
+            switch : str, optional
+                Specifies which form to return: wrap, positional, keyword, out.
+        """
+        if self.style != "document" or not(self.literal):
+            raise RuntimeError(
+                "Only document/literal are supported. Improve Message class!")
+        #assumed wrapped convention
+        p = self.parts[0][1] #message type
+        res = ''
+        if switch == "positional":
+            for child in p._children:
+                opt = ''
+                array = ''
+                if child['max']>1:
+                     # 'unbounded'>1
+                     array = '[]'
+                if child['min']==0:
+                    opt = '| None'
+                type = get_local_type(child['type'].__name__)
+                res = res + ', %s%s %s %s'\
+                        %(type, array, child["name"], opt)
+        elif switch == "keyword":
+            for child in p._children:
+                opt = ''
+                array = ''
+                if child['max']>1:
+                     # 'unbounded'>1
+                     array = '[]'
+                if child['min']==0:
+                    opt = '| None'
+                type = get_local_type(child['type'].__name__)
+                res = res + ', %s=%s%s %s'\
+                        %(child['name'], type, array, opt)
+        elif switch == 'out' and len(p._children) == 1:
+            child = p._children[0]
+            opt = ''
+            array = ''
+            if child['max']>1:
+                 # 'unbounded'>1
+                 array = '[]'
+            if child['min']==0:
+                opt = '| None'
+            type = get_local_type(child['type'].__name__)
+            res = '%s%s %s %s'  %(type, array, 'result', opt)
+        else:
+            res = '%s %s' %(p.__name__, 'msg')
+
+        if len(res)>2 and res[0] == ',':
+            res = res[2:]
+
+        return res
+
+    def to_xml(self, *arg, **kw):
+        """
+            Convert from Python into xml message.
+
+            This function accepts parameters as they are supplied
+            to the method call and tries to convert it to a message.
+            Arguments can be in one of  four forms:
+                - 1 argument of proper message type for this operation
+                - positional arguments - members of the proper message type
+                - keyword arguments - members of the message type.
+                - a mixture of positional and keyword arguments.
+
+            Keyword arguments must have at least one member: _body which
+            contains etree.Element to append the conversion result to.
+        """
+        if self.style != "document" or not(self.literal):
+            raise RuntimeError(
+                "Only document/literal are supported. Improve Message class!")
+
+        #assumed wrapped convention
+        p = self.parts[0][1]() #encoding instance
+
+        #wrapped message is supplied
+        if len(arg) == 1 and isinstance(arg[0], self.parts[0][1]):
+            for child in p._children:
+                setattr(p, child['name'], getattr(arg[0], child['name'], None))
+        else:
+            #reconstruct wrapper from expanded input
+            counter = 0
+            for child in p._children:
+                name = child["name"]
+                #first try keyword
+                val = kw.get(name, None)
+                if val is None: #not keyword
+                    if counter < len(arg):
+                        #assume this is positional argument
+                        val = arg[counter]
+                        counter = counter + 1
+                if val is None: #check if nillable
+                    if child["min"] == 0:
+                        continue
+                    else:
+                        raise ValueError(\
+                                "Non-nillable parameter %s is not present"\
+                                                                    %name)
+                setattr(p, name, val)
+
+        #the real conversion is done by ComplexType
+        p.to_xml(kw["_body"], "{%s}%s" %(self.namespace, self.tag))
+
+    def from_xml(self, body, header = None):
+        """
+            Convert from xml message to Python.
+        """
+        if self.style != "document" or not(self.literal):
+            raise RuntimeError(
+                "Only document/literal are supported. Improve Message class.")
+
+        #assumed wrapped convention
+        p = self.parts[0][1]() #decoding instance
+
+        res = p.from_xml(body)
+
+        #for wrapped doc style (the only one implemented) we know, that
+        #wrapper has only one child, get it
+        if len(p._children) == 1:
+            return getattr(res, p._children[0]["name"], None)
+        else:
+            return res
+
+class Method(object):
+    """
+        Definition of a single SOAP method, including location, action, name
+        and input and output classes.
+
+        Parameters
+        ----------
+        location : str
+            Location as found in service part of WSDL.
+        name : str
+            Name of operation
+        action : str
+            Action (?) as found in binding part of WSDL.
+        input : Message instance
+            Input message description.
+        output : Message instance
+            Output message description.
+        doc : str, optional - default to None
+            Documentation of the method as found in portType section of WSDL.
+    """
+    def __init__(self, location, name, action, input, output, doc=None):
+        self.location = location
+        self.name = name
+        self.action = action
+        self.input = input
+        self.output = output
+        #add call signatures to doc
+        sign = '%s\n%s\n%s' %(self.__str__(),
+                                            self.__str__(switch="positional"),
+                                            self.__str__(switch="keyword"))
+        self.__doc__ = '%s\n%s' %(sign, doc)
+
+    def __str__(self, switch = 'wrap'):
+        """
+            String representation of the call in three forms:
+                - wrapped message
+                - positional sub-arguments
+                - keyword sub-arguments.
+
+            Parameters
+            ----------
+            switch : str, optional
+                Specifies which form to return: wrap, positional, keyword.
+        """
+        input_msg = self.input.__str__(switch = switch)
+        output_msg = self.output.__str__(switch = 'out')
+
+        return '%s = %s(%s)' %(output_msg, self.name, input_msg)
+
+
+    def __call__(self, *arg, **kw):
+        """
+            Process rpc-call.
+        """
+        #create soap-wrap around our message
+        env = etree.Element('{%s}Envelope' %NS_SOAP_ENV)
+        header = etree.SubElement(env, '{%s}Header' %NS_SOAP_ENV)
+        body = etree.SubElement(env, '{%s}Body' %NS_SOAP_ENV)
+
+        #compose call message - convert all parameters and encode the call
+        kw["_body"] = body
+        self.input.to_xml(*arg, **kw)
+
+        text_msg = etree.tostring(env) #message to send
+        del env
+
+        #http stuff
+        request = Request(self.location, text_msg,
+                                {'Content-Type': 'text/xml',
+                                'SOAPAction': self.action})
+        del text_msg
+
+        #real rpc
+        try:
+            response = urlopen(request).read()
+            del request
+            #string to xml
+            xml = etree.fromstring(response)
+            del response
+            #find soap body
+            body = xml.find(SOAP_BODY)
+            if body is None:
+                raise RuntimeError("No SOAP body found in response")
+            body = body[0] # hacky? get the first real element
+        except HTTPError, e:
+            if e.code in (202,204):
+                return None
+            elif e.code == 500:
+                #read http error body and make xml from it
+                xml = etree.fromstring(e.fp.read())
+                body = xml.find(SOAP_BODY)
+                if body is None:
+                    raise
+                #process service fault
+                fault = body.find(SOAP_FAULT)
+                if fault is not None:
+                    code = fault.find('faultcode')
+                    if code is not None:
+                        code = code.text or ''
+                    string = fault.find('faultstring')
+                    if string is not None:
+                        string = string.text or ''
+                    detail = fault.find('detail')
+                    if detail is not None:
+                        detail = detail.text or ''
+                    raise RuntimeError("SOAP Fault %s:%s <%s> %s%s"\
+                            %(self.location, self.name, code, string, detail))
+                else:
+                    raise
+            else:
+                raise
+
+        return self.output.from_xml(body)
+
+"""
+    Some common soap stuff.
+"""
+
+# soap contstants
+NS_SOAP_ENV = "http://schemas.xmlsoap.org/soap/envelope/"
+NS_SOAP_ENC = "http://schemas.xmlsoap.org/soap/encoding/"
+NS_SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/'
+NS_SOAP12 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
+NS_XSI = "http://www.w3.org/2001/XMLSchema-instance"
+NS_XSD = "http://www.w3.org/2001/XMLSchema"
+NS_WSDL = 'http://schemas.xmlsoap.org/wsdl/'
+SOAP_BODY = '{%s}Body' % NS_SOAP_ENV
+SOAP_FAULT = '{%s}Fault' % NS_SOAP_ENV
+SOAP_HEADER = '{%s}Header' % NS_SOAP_ENV
+
+def get_local_name(full_name):
+    """
+        Removes namespace part of the name.
+
+        In lxml namespacec can appear in 2 forms:
+            {full.namespace.com}name, and
+            prefix:name.
+        Both cases are handled correctly here.
+    """
+    full_name = full_name[full_name.find('}')+1:]
+    full_name = full_name[full_name.find(':')+1:]
+    return full_name
+
+def get_local_type(xmltype):
+    """
+        Simplifies types names, e.g. XMLInteger is
+        presented as int.
+
+        This is used for nice printing only.
+    """
+    if xmltype == "XMLBoolean":
+        return 'bool'
+    elif xmltype == "XMLDecimal":
+        return 'decimal'
+    elif xmltype == "XMLInteger":
+        return 'int'
+    elif xmltype == "XMLDouble":
+        return 'float'
+    elif xmltype == "XMLString":
+        return 'str'
+    elif xmltype == "XMLDate":
+        return 'date'
+    elif xmltype == "XMLDateTime":
+        return 'datetime'
+    else:
+        return xmltype
+
+def get_ns(tag):
+    """
+        Extract namespace.
+
+        This function is opposite to get_local_name, in that
+        it returns the first part of the tag: the namespace.
+
+        Parameters
+        ----------
+        tag : str
+            Tag to process.
+    """
+    p_open = tag.find('{')
+    p_close = tag.find('}')
+    if p_open != -1 and p_close != -1:
+        return tag[p_open+1:p_close]
+    else:
+        return ''
+
+"""
+    Conversion of WSDL documents into Python.
+"""
+from xmltypes import *
+from methods import *
+from soap import *
+import urllib2
+import xml.etree.cElementTree as etree
+
+#primitive types mapping xml -> python
+_primmap = { 'anyType'          : XMLAny,
+             'boolean'          : XMLBoolean,
+             'decimal'          : XMLDecimal,
+             'int'              : XMLInteger,
+             'integer'          : XMLInteger,
+             'positiveInteger'  : XMLInteger,
+             'unsignedInt'      : XMLInteger,
+             'short'            : XMLInteger,
+             'byte'             : XMLInteger,
+             'long'             : XMLInteger,
+             'float'            : XMLDouble,
+             'double'           : XMLDouble,
+             'string'           : XMLString,
+             'base64Binary'     : XMLString,
+             'anyURI'           : XMLString,
+             'language'         : XMLString,
+             'token'            : XMLString,
+             'date'             : XMLDate,
+             'dateTime'         : XMLDateTime,
+             # FIXME: probably timedelta, but needs parsing.
+             # It looks like P29DT23H54M58S
+             'duration'         : XMLString}
+
+class WSDLParser(object):
+    """
+        Parser to get types and methods defined in the document.
+    """
+    def __init__(self, wsdl_url):
+        """
+            Initialize parser.
+
+            The WSDL document is loaded and is converted into xml.
+
+            Initialized members:
+            self.wsdl_url  - url of wsdl document
+            self.wsdl - xml document read from wsdl_url (etree.Element)
+            self.tns - target namespace
+
+            Parameters
+            ----------
+            wsdl_url : str
+                Address of the WSDL document.
+        """
+        #open wsdl page - get a file like object and
+        # parse it into xml
+        page_handler = urllib2.urlopen(wsdl_url)
+        self.wsdl = etree.parse(page_handler).getroot()
+        page_handler.close()
+        del page_handler
+        self.wsdl_url = wsdl_url
+        #get target namespace
+        self.tns = self.wsdl.get('targetNamespace', None)
+
+    def get_service_names(self):
+        """
+            Returns names of services found in WSDL.
+
+            This is from wsdl:service section.
+
+            Returns
+            -------
+            out : list of str
+                Names.
+        """
+        services = self.wsdl.findall('.//{%s}service' % NS_WSDL)
+        res = []
+        for service in services:
+            name = service.get("name", None)
+            if name is not None:
+                res.append(name)
+        return res
+
+    def get_type_name(self, element):
+        """
+            Get type name from XML element.
+
+            Parameters
+            ----------
+            element : etree.Element
+                XML description of the type.
+        """
+        name = element.get('name', None)
+        if name is None:
+            #try to find parent. ElementTree does not keep track
+            #of parent, so we search for element and see if current
+            #element is in children, a bit weird :).
+            elements = self.wsdl.findall('.//{%s}element' % NS_XSD)
+            parent = None
+            for e in elements:
+                if element in e:
+                    parent = e
+                    break
+            if parent is None:
+                return None
+            name = parent.get('name', None)
+        return name
+
+    def create_named_class(self, name, types, allelements):
+        """
+            Creates a single named type.
+
+            Function searches through all available elements to find one
+            suitable. This is useful if a type is present as a child before
+            it is present in the list.
+
+            Parameters
+            ----------
+            name : str
+                Name of the type.
+            types : dict
+                Map of known types.
+            allelements : list of etree.Element instance
+                List of all types found in WSDL. It is used to create
+                related classes in place.
+        """
+        for element in allelements:
+            el_name = self.get_type_name(element)
+            if el_name == name:
+                self.create_class(element, el_name, types, allelements)
+                break
+
+    def collect_children(self, element, children, types, allelements):
+        """
+            Collect information about children (xml sequence, etc.)
+
+            Parameters
+            ----------
+            element : etree.Element
+                XML sequence container.
+            children : list
+                Information is appended to this list.
+            types : dict
+                Known types map.
+            allelements : list of etree.Element instance
+                List of all types found in WSDL. It is used to create
+                related classes in place.
+        """
+        for subel in element:
+            #iterate over sequence, do not consider in place defs
+            type = get_local_name(subel.get('type', None))
+            if type is None:
+                raise ValueError(
+                        "Do not support this type of complex type: %s"
+                                                         %subsub.tag)
+            ch = types.get(type, None)
+            if ch is None:
+                self.create_named_class(type, types, allelements)
+            ch = types.get(type, None)
+            if ch is None:
+                raise ValueError("Child %s class is not found " %type)
+            child_name = subel.get('name', 'unknown')
+            minOccurs = int(subel.get('minOccurs', 1))
+            maxOccurs = subel.get('maxOccurs', 1)
+            if maxOccurs != 'unbounded':
+                maxOccurs = int(maxOccurs)
+            children.append({ "name":child_name,
+                             'type' : ch,
+                             'min' : minOccurs,
+                             'max' : maxOccurs})
+
+    def create_class(self, element, name, types, allelements):
+        """
+            Create new type from xml description.
+
+            Parameters
+            ----------
+            element : etree.Element instance
+                XML description of a complex type.
+            name : str
+                Name of the new class.
+            types : dict
+                Map of already known types.
+            allelements : list of etree.Element instance
+                List of all types found in WSDL. It is used to create
+                related classes in place.
+        """
+        doc = None
+        children = []
+        base = []
+        #iterate over children
+        #handle only children, bases non-primitive classes and docs
+        for subel in element:
+            if subel.tag in ("{%s}sequence" %NS_XSD,
+                             "{%s}all" %NS_XSD,
+                             "{%s}choice" %NS_XSD):
+                #add children - arguments of new class
+                self.collect_children(subel, children, types, allelements)
+
+            elif subel.tag == "{%s}complexContent" %NS_XSD:
+                #base class
+                subel = subel[0]
+                if subel.tag == "{%s}extension" %NS_XSD:
+                    base_name = get_local_name(subel.get("base", None))
+                    b = types.get(base_name, None)
+                    if b is None:
+                        self.create_named_class(base_name, types, allelements)
+                    b = types.get(base_name, None)
+                    if b is None:
+                        raise ValueError("Base %s class is not found" %base_name)
+                    base.append(b)
+                for subsub in subel:
+                    if subsub.tag in ("{%s}sequence" %NS_XSD,
+                                      "{%s}all" %NS_XSD,
+                                      "{%s}choice" %NS_XSD):
+                        self.collect_children(subsub, children, types, allelements)
+            elif subel.tag == "{%s}annotation" %NS_XSD:
+                if len(subel) and\
+                   subel[0].tag == "{%s}documentation" %NS_XSD:
+                    doc = subel[0].text
+
+        if name not in types:
+            #create new class
+            cls = ComplexTypeMeta(name, base,
+                                      {"_children":children, "__doc__":doc})
+            types[name] = cls
+
+
+    def get_types(self, initialmap):
+        """
+            Constructs a map of all types defined in the document.
+
+            At the moment simple types are not processed at all!
+            Only complex types are considered. If attribute
+            or what so ever are encountered an exception if fired.
+
+            Parameters
+            ----------
+            initialmap : dict
+                Initial map of types. Usually it will be _primmap.
+                This is present here so that different services
+                can create own types of XMLAny.
+
+            Returns
+            -------
+            out : dict
+                A map of found types {type_name : complex class}
+        """
+        #find all types defined here
+        types = self.wsdl.findall('.//{%s}complexType' %NS_XSD)
+        types.extend(self.wsdl.findall('.//{%s}simpleType' %NS_XSD))
+
+        res = initialmap.copy() #types container
+        #iterate over the found types and fill in the container
+        # If an element used types placed later in the document, it will
+        #created in place when calling create_class.
+        for t in types:
+
+            #get name of the type
+            name = self.get_type_name(t)
+            if name is None:
+                continue
+
+            #if type is primitive or was already processed, skip it
+            if (res.get(name, None) is not None):
+                continue
+
+            #if unknown simple type - raise error
+            if t.tag == "{%s}simpleType" %NS_XSD:
+                raise ValueError("Uknown simple type %s" %name)
+
+            #handle complex type, this also registers new class to result
+            self.create_class(t, name, res, types)
+
+        return res
+
+    def get_methods(self, types):
+        """
+            Construct a map of all operations defined in the document.
+
+            Parameters
+            ----------
+            types : dict
+                Map of known types as returned by get_types.
+
+            Returns
+            -------
+            out : dict
+                A map of operations: {operation name : method object}
+        """
+        res = {} # future result
+
+        #find all service definitions
+        services = self.wsdl.findall('.//{%s}service' %NS_WSDL)
+        bindings = self.wsdl.findall(".//{%s}binding" %NS_WSDL)
+        port_types = self.wsdl.findall(".//{%s}portType" %NS_WSDL)
+        messages = self.wsdl.findall('.//{%s}message' %NS_WSDL)
+        for service in services:
+            #all ports defined in this service. Port contains
+            #operations
+            ports = service.findall('.//{%s}port' %NS_WSDL)
+            for port in ports:
+                subel = port[0]
+
+                #check that this is a soap port, since wsdl can
+                #also define other ports
+                if get_ns(subel.tag) not in (NS_SOAP, NS_SOAP12):
+                    continue
+
+                #port location
+                location = subel.get('location')
+
+                #find binding for this port
+                binding_name = get_local_name(port.get('binding'))
+                for binding in bindings:
+                    if binding.get("name") == binding_name:
+                        break
+
+                #find binding style
+                soap_binding = binding.find('{%s}binding' % NS_SOAP)
+                if soap_binding is None:
+                    soap_binding = binding.find('{%s}binding' % NS_SOAP12)
+                if soap_binding is None:
+                    soap_binding = binding.find('binding')
+                if soap_binding is None:
+                    raise SyntaxError("No SOAP binding found in %s" %
+                                                    etree.tostring(binding))
+                style =  soap_binding.get('style')
+
+                #get port type - operation + message links
+                port_type_name = get_local_name(binding.get('type'))
+                for port_type in port_types:
+                    if port_type.get("name") == port_type_name:
+                        break
+                port_operations = port_type.findall("{%s}operation" %NS_WSDL)
+
+                #get operations
+                operations = binding.findall('{%s}operation' % NS_WSDL)
+                for operation in operations:
+                    #get operation name
+                    name = get_local_name(operation.get("name"))
+
+                    #check we have soap operation
+                    soap_op = operation.find('{%s}operation' % NS_SOAP)
+                    if soap_op is None:
+                        soap_op = operation.find('{%s}operation' % NS_SOAP12)
+                    if soap_op is None:
+                        soap_op = operation.find('operation')
+                    if soap_op is None:
+                        raise SyntaxError("No SOAP operation found in %s" %
+                                                  etree.tostring(operation))
+
+                    #operation action(?), style
+                    action = soap_op.attrib['soapAction']
+                    operation_style = soap_op.get('style', style)
+
+                    # FIXME is it reasonable to assume that input and output
+                    # are the same? Is it ok to ignore the encoding style?
+                    input = operation.find('{%s}input' % NS_WSDL)[0]
+                    literal = input.get('use') == 'literal'
+
+                    #do not support in/out headers, otherwise must be found here
+
+                    #go to port part and find messages.
+                    for port_operation in port_operations:
+                        if port_operation.get("name") == name:
+                            break
+                    in_msg_name = get_local_name(
+                            port_operation.find('{%s}input' %NS_WSDL).get("message"))
+                    out_msg_name = get_local_name(
+                            port_operation.find('{%s}output' %NS_WSDL).get("message"))
+                    #documentation
+                    doc = port_operation.find('{%s}documentation' %NS_WSDL)
+                    if doc is not None:
+                        doc = doc.text
+
+                    #finally go to message section
+                    for in_msg_xml in messages:
+                        if in_msg_xml.get("name") == in_msg_name:
+                            break
+                    for out_msg_xml in messages:
+                        if out_msg_xml.get("name") == out_msg_name:
+                            break
+                    in_types = in_msg_xml.findall('{%s}part' %NS_WSDL)
+                    out_types = out_msg_xml.findall('{%s}part' %NS_WSDL)
+                    #create input and output messages
+                    in_msg = self.create_msg(name, in_types, operation_style,
+                                                                 literal, types)
+                    out_msg = self.create_msg(name, out_types, operation_style,
+                                                                 literal, types)
+                    method = Method(location, name, action, in_msg, out_msg, doc=doc)
+                    res[name] = method
+        return res
+
+    def create_msg(self, name, part_elements, style, literal, types):
+        """
+            Create input or output message.
+
+            Parameters
+            ----------
+            name : str
+                Name of this message.
+            part_elements : list instance
+                List of parts as found in message section.
+            style : str
+                Style of operation: 'document', 'rpc'.
+            literal : bool
+                True = literal, False = encoded.
+            types : dict
+                Map of known types as returned by get_types.
+
+            Returns
+            -------
+            out : Message instance
+                Message for handling calls in/out.
+        """
+        #get all parameters - parts of the message
+        parts = []
+        for t in part_elements:
+            part_name = t.get("name", None)
+            if part_name is None:
+                continue
+            type_name = t.get('element', None)
+            if type_name is None:
+                type_name = t.get('type', None)
+            type_name = get_local_name(type_name)
+            parts.append((part_name, types[type_name]))
+
+        #create message
+        return Message(name, self.tns, parts, style, literal)
+
+
+
+"""
+    Python classes corresponding to XML schema.
+"""
+from exceptions import ValueError, RuntimeError
+import xml.etree.cElementTree as etree
+from decimal import Decimal
+from datetime import date, datetime, time
+from soap import *
+
+def toinit(self, deep = False):
+    """
+        Nice init for complex types.
+
+        All obligatory (nonnillable) children can also be created.
+
+        Parameters
+        ----------
+        deep : bool, optional, defaule False
+            If True all non-nillable children are created, otherwise
+            they are simplty None. The latter is used when
+            converting response from XML to Python.
+    """
+    if not(deep):
+        return
+    for child in self._children:
+        if child['min'] == 0:
+            continue
+        val_type = child['type']
+        val = None
+        if getattr(val_type, "_children", None) is not None:
+            val = val_type(deep=deep)
+        else:
+            val = val_type()
+        if child['max'] > 1:
+            #'unbounded' > 1
+            val = [val,]
+        setattr(self, child['name'], val)
+
+def tostr(self):
+    """
+        Nice printing facility for complex types.
+    """
+    children = ''
+    for child in self._children:
+        child_name = child['name']
+        array = ''
+        if child['max']>1:
+             # 'unbounded'>1
+             array = '[]'
+        child_value = getattr(self, child_name, None)
+        many = False
+        if len(array) and isinstance(child_value, (list, tuple)):
+            many = True
+        shift = len(child_name) + len(array) + 7 # 4 comes from tab
+        if many:
+            shift = shift + 1
+            tmp = child_value
+            stop = len(child_value)
+            after = '\n]'
+            if stop > 10:
+                stop = 10
+                after = '\n...' + after
+            child_value = ''
+            for val in tmp:
+                child_value = child_value + ',\n%s' %str(val)
+            child_value = '[\n' + child_value[2:] + after
+        elif child_value is not None:
+            child_value = str(child_value)
+        else:
+            child_value = "%s (%s)" %(str(None),
+                                    get_local_type(child['type'].__name__))
+        child_value = child_value.replace('\n', '\n%s' %(' '*shift))
+        descr = '    %s%s = %s' %(child_name, array, child_value)
+        children = children + '\n%s' %descr
+    res = '(%s){%s\n}' %(self.__class__.__name__, children)
+
+    return res
+
+class XMLType(object):
+    """
+        Base xml schema type.
+
+        It defines basic functions to_xml and from_xml.
+    """
+    _namespace = ""
+
+
+    def check_constraints(self, n, min_occurs, max_occurs):
+        """
+            Performs constraints checking.
+
+            Parameters
+            ----------
+            n : int
+                Actual number of occurrences.
+            min_occurs : int
+                Minimal allowed number of occurrences.
+            max_occurs : int or 'unbounded'
+                Maximal allowed number of occurrences.
+
+           Raises
+           ------
+            ValueError
+                If constraints are not satisfied.
+        """
+        if n<min_occurs:
+            raise ValueError("Number of values is less than min_occurs")
+        if n > max_occurs:
+            raise ValueError("Number of values is more than max_occurs")
+
+    def to_xml(self, parent, name):
+        """
+            Function to convert to xml from python representation.
+
+            This is basic function and it is suitable for complex types.
+            Primitive types must overload it.
+
+            Parameters
+            ----------
+            parent : etree.Element
+                Parent xml element to append this child to.
+            name : str
+                Full qualified (with namespace) name of this element.
+        """
+        #this level element
+        element = etree.SubElement(parent, name)
+
+        #namespace for future naming
+        if self._namespace:
+            ns = "{" + self._namespace + "}"
+        else:
+            ns = ''
+        #add all children to the current level
+        #note that children include also base classes, as they are propagated by
+        #the metaclass below
+        for child in self._children:
+            child_name = child["name"]
+            #get the value of the argument
+            val = getattr(self, child_name, None)
+
+            #do constraints checking
+            n = 0 #number of values for constraints checking
+            if isinstance(val, (list, tuple)):
+                n = len(val)
+            elif val is not None:
+                n = 1
+                val = [val, ]
+            self.check_constraints(n, child['min'], child['max'])
+            if n == 0:
+                continue #only nillables can get so far
+
+            #conversion
+            full_name = ns + child_name #name with namespace
+            for single in val:
+                if not(isinstance(single, child['type'])):
+                    #useful for primitive types:  python int, e.g.,
+                    #can be passed directly. If str is used instead
+                    #an exception is fired up.
+                    single = child['type'](single)
+                single.to_xml(element, full_name)
+
+    def from_xml(self, element):
+        """
+            Function to convert from xml to python representation.
+
+            This is basic function and it is suitable for complex types.
+            Primitive types must overload it.
+
+            Parameters
+            ----------
+            element : etree.Element
+                Element to recover from.
+        """
+        #element is nill
+        if bool(element.get('nil')):
+            return
+
+        all_children_names = []
+        for child in self._children:
+            all_children_names.append(child["name"])
+
+        for subel in element:
+            name = get_local_name(subel.tag)
+            ind = all_children_names.index(name)
+
+            #used for conversion. for primitive types we receive back built-ins
+            inst = self._children[ind]['type']()
+            subvalue = inst.from_xml(subel)
+
+            #check conversion
+            if subvalue is None:
+                if self._children[ind]['min'] != 0:
+                    raise ValueError("Non-nillable %s element is nil." %name)
+            else:
+                #unbounded is larger than 1
+                if self._children[ind]['max'] > 1:
+                    current_value = getattr(self, name, None)
+                    if current_value is None:
+                        current_value = []
+                        setattr(self, name, current_value)
+                    current_value.append(subvalue)
+                else:
+                    setattr(self, name, subvalue)
+            del name, ind, inst
+
+        #now all children were processed, so remove them to save memory
+        element.clear()
+
+        return self
+
+class ComplexTypeMeta(type):
+    """
+        Metaclass to create complex types on the fly.
+    """
+    def __new__(cls, name, bases, attributes):
+        """
+            Method to create new types.
+
+            _children attribute must be present in attributes. It describes
+            the arguments to be present in the new type. The he
+            _children argument must be a list of the form:
+            [{'name':'arg1', 'min':1, 'max':1, 'type':ClassType}, ...]
+
+            Parameters
+            ----------
+            cls : this class
+            name : str
+                Name of the new type.
+            bases : tuple
+                List of bases classes.
+            attributes : dict
+                Attributes of the new type.
+        """
+        #list of children, even if empty, must be always present
+        if "_children" not in attributes:
+            raise ValueError("_children attribute must be present")
+
+        #create dictionary for initializing class arguments
+        clsDict = {}
+        #iterate over children and add arguments to the dictionary
+        #all arguments are initially have None value
+        for attr in attributes["_children"]:
+            #set the argument
+            clsDict[attr['name']] = None
+        #propagate documentation
+        clsDict["__doc__"] = attributes.get("__doc__", None)
+        #add nice printing
+        clsDict["__str__"] = tostr
+        clsDict["__repr__"] = tostr
+        #add complex init
+        clsDict["__init__"] = toinit
+
+        #extend children list with that of base classes
+        new = []
+        for b in bases:
+            base_children = getattr(b, "_children", None)
+            if base_children is not None:
+                #append
+                new.extend(base_children)
+        new.extend(attributes["_children"])
+        attributes["_children"] = new
+
+        #children property is passed through
+        clsDict["_children"] = attributes["_children"]
+
+        #add ComplexType to base list
+        if XMLType not in bases:
+            newBases = list(bases)
+            newBases.append(XMLType)
+            bases = tuple(newBases)
+
+        #create new type
+        return type.__new__(cls, name, bases, clsDict)
+
+#the following is a modified copy from soaplib library
+
+class XMLString(XMLType, str):
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = unicode(self)
+
+    def from_xml(self, element):
+        if element.text:
+            return element.text.encode('utf-8')
+        else:
+            return None
+
+class XMLInteger(XMLType, int):
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = repr(self)
+
+    def from_xml(self, element):
+        if element.text:
+            try:
+                return int(element.text)
+            except:
+                return long(element.text)
+        return None
+
+class XMLDouble(XMLType, float):
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = repr(self)
+
+    def from_xml(self, element):
+        if element.text:
+            return float(element.text)
+        return None
+
+class XMLBoolean(XMLType, str):
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        if self in ('True', 'true', '1'):
+            element.text = repr(True).lower()
+        else:
+            element.text = repr(False).lower()
+
+    def from_xml(cls, element):
+        if element.text:
+            return (element.text.lower() in ['true', '1'])
+        return None
+
+class XMLAny(XMLType, str):
+    _types = {} #dict of known types
+    def to_xml(self, parent, name):
+        value = etree.fromstring(self)
+        element = etree.SubElement(parent, name)
+        element.append(value)
+
+    def from_xml(self, element):
+        #try to find types
+        type = element.get('{%s}type' %NS_XSI, None)
+        if type is None:
+            return element
+        type = get_local_name(type)
+        type_class = self._types.get(type, None)
+        if type_class is not None:
+            res = type_class()
+            return res.from_xml(element)
+        else:
+            return element
+
+class XMLDecimal(XMLType, Decimal):
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = str(self)
+
+    def from_xml(self, element):
+        if element.text:
+            return Decimal(element.text)
+        return None
+
+class XMLDate(XMLType):
+    def __init__(self, *arg):
+        if len(arg) == 1 and isinstance(arg[0], date):
+            self.value = arg[0]
+        else:
+            self.value = date(2008, 11, 11)
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = self.value.isoformat()
+
+    def from_xml(self, element):
+        """expect ISO formatted dates"""
+        if not(element.text):
+            return None
+        text = element.text
+
+        full = datetime.strptime(text, '%Y-%m-%d')
+
+        return full.date()
+
+
+class XMLDateTime(XMLType):
+    def __init__(self, *arg):
+        if len(arg) == 1 and isinstance(arg[0], datetime):
+            self.value = arg[0]
+        else:
+            self.value = datetime(2008, 11, 11)
+    def to_xml(self, parent, name):
+        element = etree.SubElement(parent, name)
+        element.text = self.value.isoformat('T')
+
+    def from_xml(self, element):
+        return datetime.strptime('2011-08-02T17:00:01.000122',
+                                        '%Y-%m-%dT%H:%M:%S.%f')

scio/__init__.py

-# scio -- soap classes for input and output
-#
-# Copyright (c) 2011, Leapfrog Direct Response, LLC
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions are met:
-#     * Redistributions of source code must retain the above copyright
-#       notice, this list of conditions and the following disclaimer.
-#     * Redistributions in binary form must reproduce the above copyright
-#       notice, this list of conditions and the following disclaimer in the
-#       documentation and/or other materials provided with the distribution.
-#     * Neither the name of the Leapfrog Direct Response, LLC, including
-#       its subsidiaries and affiliates nor the names of its
-#       contributors, may be used to endorse or promote products derived
-#       from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LEAPFROG DIRECT
-# RESPONSE, LLC, INCLUDING ITS SUBSIDIARIES AND AFFILIATES, BE LIABLE
-# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
-# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
-# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
-# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
-# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
-# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
-# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-from client import Client

scio/client.py

-"""
-    Top level access to SOAP service.
-"""
-
-import wsdl
-import xmltypes
-
-def str_for_containers(self):
-    """
-        Nice printing for types and method containers.
-
-        Containers must have _container attribute containing all
-        elements to be printed.
-    """
-    cont = getattr(self, '_container', None)
-    if cont is None:
-        return ''
-    res = ''
-    for child in cont:
-        descr = str(getattr(getattr(self, child, None), '__doc__', None))
-        if len(descr)>100:
-            descr = descr[:100] + "..."
-        descr = descr.replace("\n", "\n\t")
-        res = res + '\n%s\n\t%s' %(child, descr)
-    res = res[1:]
-    return res
-
-class Client(object):
-    """
-        Top level class to talk to soap services.
-
-        This is an access point to service functionality. The client accepts
-        WSDL address and uses WSDLParser to get all defined types and
-        operations. The types are set to client.types and operations
-        are set to self.service.
-
-        To examine present types or operations simply print (or touch repr):
-            client.types or client.service, correspondingly.
-
-        To create type simply call:
-            client.types.MyTypeName().
-        Class constructor will also create all obligatory (non-nillable) children.
-        To call an operation:
-            client.service.MyOperationName(arg1, arg2, arg3, ...),
-        where arguments are of required types. Arguments can also
-        be passed as keywords or a ready wrapped message.
-
-        If any help is available in the WSDL document it is propagated to the
-        types and operations, see e.g. help client.types.MyTypeName. In addition
-        the help page on an operation displays its call signature.
-
-        Nice printing is also available for all types defined in client.types:
-            print(client.types.MyTypeName())
-
-        .. warning::
-            Only document/literal wrapped convention is implemented at the moment.
-
-        Details
-        -------
-        In reality client.types and client.service are simply containers.
-        The content of these containers is set from results of parsing
-        the wsdl document by WSDLParser.get_types and WSDLParser.get_methods
-        correspondingly.
-
-        The client.types container consists of auto generated (by WSDLParser)
-        class definitions. So that a call to a member returns and instance
-        of the new type. New types are auto-generated according to a special
-        convention by metaclass xmltypes.ComplexTypeMeta.
-
-        The client.service container consists of methods wrapers
-        methods.Method. The method wrapper is callable with free number of
-        parameters. The input and output requirements of a method are
-        contained in methods.Message instances Method.input and
-        Method.output correspondingly. On a call a method converts
-        the input to XML by using Method.input, sends request to the
-        service and finally decodes the response from XML by
-        Method.output.
-
-        Parameters
-        ----------
-        wsdl_url : str
-            Address of wsdl document to consume.
-    """
-    def __init__(self, wsdl_url):
-        #create parser and download the WSDL document
-        self.wsdl_url = wsdl_url
-        parser = wsdl.WSDLParser(wsdl_url)
-        #before getting types we handle anyType
-        #anyType is somewhat tricky, because it must
-        #know all the other types to work, therefore
-        #we recreate it here. In such a way all other
-        #service do not conflict with this instance
-        primmap = wsdl._primmap.copy()
-        primmap['anyType'] = type('XMLAny', (xmltypes.XMLAny,), {})
-        #get all types - a dictionary
-        types = parser.get_types(primmap)
-        primmap['anyType']._types = types.copy()
-        #get all methods - a dictionary
-        methods = parser.get_methods(types)
-        #create dispatchers for types and methods
-        #first provide nice printing
-        types["_container"] = types.keys()
-        methods["_container"] = methods.keys()
-        types["__str__"] = str_for_containers
-        types["__repr__"] = str_for_containers
-        methods["__str__"] = str_for_containers
-        methods["__repr__"] = str_for_containers
-        self.types = type('TypesDispatcher', (), types)()
-        self.service = type('ServiceDispatcher', (), methods)()
-        #get service names for printing
-        self.names = parser.get_service_names()
-
-    def __str__(self):
-        res = ''
-        for name in self.names:
-            res = res + ', %s' %name
-        res = res[2:] + " at:\n\t%s" %(self.wsdl_url)
-        return res
-
-    def __repr__(self):
-        return self.__str__()
-
-

scio/methods.py

-"""
-    Classes required for remote method calls: messages and method wrappers.
-"""
-from soap import *
-from lxml import etree
-from urllib2 import urlopen, Request, HTTPError
-#import xml.etree.cElementTree as etree
-
-class Message(object):
-    """
-        Message for input and output of service operations.
-
-        Messages perform conversion of Python to xml and backwards
-        of the calls and returns.
-
-        A message instance knows about used style/literal convention and can
-        use it to perform transformations. At the moment only
-        document/literal wrapped is implemented. You can improve this class
-        to have the others.
-
-        .. warning::
-            Only document/literal wrapped convention is implemented at the moment.
-
-        Parameters
-        ----------
-        tag : str
-            Name of the message.
-        namespafe : str
-            Namespace of the message.
-        nsmap : dict
-            Map of namespace prefixes.
-        parts : list
-            List of message parts in the form
-            (part name, part type class).
-            This description is usually found in message part of a WSDL document.
-        style : str
-            Operation style document/rpc.
-        literal : bool
-            True = literal, False = encoded.
-    """
-    def __init__(self, tag, namespace, nsmap, parts, style, literal):
-        self.tag = tag
-        self.namespace = namespace
-        self.nsmap = nsmap
-        self.parts = parts
-        self.style = style
-        self.literal = literal
-
-
-    def __str__(self, switch = "wrap"):
-        """
-            String representation of the message in three forms:
-                - wrapped message
-                - positional sub-arguments
-                - keyword sub-arguments.
-                - out - the only child of wrapped message. This applicable
-                        to output message extraction.
-
-            Parameters
-            ----------
-            switch : str, optional
-                Specifies which form to return: wrap, positional, keyword, out.
-        """
-        if self.style != "document" or not(self.literal):
-            raise RuntimeError(
-                "Only document/literal are supported. Improve Message class!")
-        #assumed wrapped convention
-        p = self.parts[0][1] #message type
-        res = ''
-        if switch == "positional":
-            for child in p._children:
-                opt = ''
-                array = ''
-                if child['max']>1:
-                     # 'unbounded'>1
-                     array = '[]'
-                if child['min']==0:
-                    opt = '| None'
-                type = get_local_type(child['type'].__name__)
-                res = res + ', %s%s %s %s'\
-                        %(type, array, child["name"], opt)
-        elif switch == "keyword":
-            for child in p._children:
-                opt = ''
-                array = ''
-                if child['max']>1:
-                     # 'unbounded'>1
-                     array = '[]'
-                if child['min']==0:
-                    opt = '| None'
-                type = get_local_type(child['type'].__name__)
-                res = res + ', %s=%s%s %s'\
-                        %(child['name'], type, array, opt)
-        elif switch == 'out' and len(p._children) == 1:
-            child = p._children[0]
-            opt = ''
-            array = ''
-            if child['max']>1:
-                 # 'unbounded'>1
-                 array = '[]'
-            if child['min']==0:
-                opt = '| None'
-            type = get_local_type(child['type'].__name__)
-            res = '%s%s %s %s'  %(type, array, 'result', opt)
-        else:
-            res = '%s %s' %(p.__name__, 'msg')
-
-        if len(res)>2 and res[0] == ',':
-            res = res[2:]
-
-        return res
-
-    def to_xml(self, *arg, **kw):
-        """
-            Convert from Python into xml message.
-
-            This function accepts parameters as they are supplied
-            to the method call and tries to convert it to a message.
-            Arguments can be in one of  four forms:
-                - 1 argument of proper message type for this operation
-                - positional arguments - members of the proper message type
-                - keyword arguments - members of the message type.
-                - a mixture of positional and keyword arguments.
-
-            Keyword arguments must have at least one member: _body which
-            contains lxml.etree.Element to append the conversion result to.
-        """
-        if self.style != "document" or not(self.literal):
-            raise RuntimeError(
-                "Only document/literal are supported. Improve Message class!")
-
-        #assumed wrapped convention
-        p = self.parts[0][1]() #encoding instance
-
-        #wrapped message is supplied
-        if len(arg) == 1 and isinstance(arg[0], self.parts[0][1]):
-            for child in p._children:
-                setattr(p, child['name'], getattr(arg[0], child['name'], None))
-        else:
-            #reconstruct wrapper from expanded input
-            counter = 0
-            for child in p._children:
-                name = child["name"]
-                #first try keyword
-                val = kw.get(name, None)
-                if val is None: #not keyword
-                    if counter < len(arg):
-                        #assume this is positional argument
-                        val = arg[counter]
-                        counter = counter + 1
-                if val is None: #check if nillable
-                    if child["min"] == 0:
-                        continue
-                    else:
-                        raise ValueError(\
-                                "Non-nillable parameter %s is not present"\
-                                                                    %name)
-                setattr(p, name, val)
-
-        #the real conversion is done by ComplexType
-        p.to_xml(kw["_body"], "{%s}%s" %(self.namespace, self.tag))
-
-    def from_xml(self, body, header = None):
-        """
-            Convert from xml message to Python.
-        """
-        if self.style != "document" or not(self.literal):
-            raise RuntimeError(
-                "Only document/literal are supported. Improve Message class.")
-
-        #assumed wrapped convention
-        p = self.parts[0][1]() #decoding instance
-
-        res = p.from_xml(body)
-
-        #for wrapped doc style (the only one implemented) we know, that
-        #wrapper has only one child, get it
-        if len(p._children) == 1:
-            return getattr(res, p._children[0]["name"], None)
-        else:
-            return res
-
-class Method(object):
-    """
-        Definition of a single SOAP method, including location, action, name
-        and input and output classes.
-
-        Parameters
-        ----------
-        location : str
-            Location as found in service part of WSDL.
-        name : str
-            Name of operation
-        action : str
-            Action (?) as found in binding part of WSDL.
-        input : Message instance
-            Input message description.
-        output : Message instance
-            Output message description.
-        doc : str, optional - default to None
-            Documentation of the method as found in portType section of WSDL.
-    """
-    def __init__(self, location, name, action, input, output, doc=None):
-        self.location = location
-        self.name = name
-        self.action = action
-        self.input = input
-        self.output = output
-        #add call signatures to doc
-        sign = '%s\n%s\n%s' %(self.__str__(),
-                                            self.__str__(switch="positional"),
-                                            self.__str__(switch="keyword"))
-        self.__doc__ = '%s\n%s' %(sign, doc)
-
-    def __str__(self, switch = 'wrap'):
-        """
-            String representation of the call in three forms:
-                - wrapped message
-                - positional sub-arguments
-                - keyword sub-arguments.
-
-            Parameters
-            ----------
-            switch : str, optional
-                Specifies which form to return: wrap, positional, keyword.
-        """
-        input_msg = self.input.__str__(switch = switch)
-        output_msg = self.output.__str__(switch = 'out')
-
-        return '%s = %s(%s)' %(output_msg, self.name, input_msg)
-
-
-    def __call__(self, *arg, **kw):
-        """
-            Process rpc-call.
-        """
-        #create soap-wrap around our message
-        env = etree.Element('{%s}Envelope' % SOAPNS['soap-env'], nsmap=SOAPNS)
-        header = etree.SubElement(env, '{%s}Header' % SOAPNS['soap-env'],
-                                                                 nsmap=SOAPNS)
-        body = etree.SubElement(env, '{%s}Body' % SOAPNS['soap-env'],
-                                                                 nsmap=SOAPNS)
-
-        #compose call message - convert all parameters and encode the call
-        kw["_body"] = body
-        self.input.to_xml(*arg, **kw)
-
-        text_msg = etree.tostring(env) #message to send
-        del env
-
-        #http stuff
-        request = Request(self.location, text_msg,
-                                {'Content-Type': 'text/xml',
-                                'SOAPAction': self.action})
-
-        #real rpc
-        try:
-            response = urlopen(request).read()
-            del request
-            #string to xml
-            xml = etree.fromstring(response)
-            del response
-            #find soap body
-            body = xml.find(SOAP_BODY)
-            if body is None:
-                raise RuntimeError("No SOAP body found in response")
-        except HTTPError, e:
-            if e.code in (202,204):
-                return None
-            elif e.code == 500:
-                #read http error body and make xml from it
-                xml = etree.fromstring(e.fp.read())
-                body = xml.find(SOAP_BODY)
-                if body is None:
-                    raise
-                #process service fault
-                fault = body.find(SOAP_FAULT)
-                if fault is not None:
-                    code = fault.find('faultcode')
-                    if code is not None:
-                        code = code.text or ''
-                    string = fault.find('faultstring')
-                    if string is not None:
-                        string = string.text or ''
-                    detail = fault.find('detail')
-                    if detail is not None:
-                        detail = detail.text or ''
-                    raise RuntimeError("SOAP Fault %s:%s <%s> %s%s"\
-                            %(self.location, self.name, code, string, detail))
-                else:
-                    raise
-            else:
-                raise
-
-        body = body[0] # hacky? get the first real element
-
-        return self.output.from_xml(body)
-

scio/soap.py

-"""
-    Some common soap stuff.
-"""
-
-# soap contstants
-NS_SOAP_ENV = "http://schemas.xmlsoap.org/soap/envelope/"
-NS_SOAP_ENC = "http://schemas.xmlsoap.org/soap/encoding/"
-NS_SOAP = 'http://schemas.xmlsoap.org/wsdl/soap/'
-NS_SOAP12 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
-NS_XSI = "http://www.w3.org/1999/XMLSchema-instance"
-NS_XSD = "http://www.w3.org/1999/XMLSchema"
-NS_WSDL = 'http://schemas.xmlsoap.org/wsdl/'
-SOAP_BODY = '{%s}Body' % NS_SOAP_ENV
-SOAP_FAULT = '{%s}Fault' % NS_SOAP_ENV
-SOAP_HEADER = '{%s}Header' % NS_SOAP_ENV
-
-#namespace mapping
-SOAPNS = {
-             'soap-env'         : NS_SOAP_ENV,
-             'soap-enc'         : NS_SOAP_ENC,
-             'soap'             : NS_SOAP,
-             'soap12'           : NS_SOAP12,
-             'wsdl'             : NS_WSDL,
-             'xsi'              : NS_XSI,
-             'xsd'              : NS_XSD }
-
-def get_local_name(full_name):
-    """
-        Removes namespace part of the name.
-
-        In lxml namespacec can appear in 2 forms:
-            {full.namespace.com}name, and
-            prefix:name.
-        Both cases are handled correctly here.
-    """
-    full_name = full_name[full_name.find('}')+1:]
-    full_name = full_name[full_name.find(':')+1:]
-    return full_name
-
-def get_local_type(xmltype):
-    """
-        Simplifies types names, e.g. XMLInteger is
-        presented as int.
-
-        This is used for nice printing only.
-    """
-    if xmltype == "XMLBoolean":
-        return 'bool'
-    elif xmltype == "XMLDecimal":
-        return 'decimal'
-    elif xmltype == "XMLInteger":
-        return 'int'
-    elif xmltype == "XMLDouble":
-        return 'float'
-    elif xmltype == "XMLString":
-        return 'str'
-    elif xmltype == "XMLDate":
-        return 'date'
-    elif xmltype == "XMLDateTime":
-        return 'datetime'
-    else:
-        return xmltype

scio/wsdl.py

-"""
-    Conversion of WSDL documents into Python.
-"""
-from xmltypes import *
-from methods import *
-from soap import *
-import urllib2
-from lxml import etree
-#import xml.etree.cElementTree as etree
-
-#primitive types mapping xml -> python
-_primmap = { 'anyType'          : XMLAny,
-             'boolean'          : XMLBoolean,
-             'decimal'          : XMLDecimal,
-             'int'              : XMLInteger,
-             'integer'          : XMLInteger,
-             'positiveInteger'  : XMLInteger,
-             'unsignedInt'      : XMLInteger,
-             'short'            : XMLInteger,
-             'byte'             : XMLInteger,
-             'long'             : XMLInteger,
-             'float'            : XMLDouble,
-             'double'           : XMLDouble,
-             'string'           : XMLString,
-             'base64Binary'     : XMLString,
-             'anyURI'           : XMLString,
-             'language'         : XMLString,
-             'token'            : XMLString,
-             'date'             : XMLDate,
-             'dateTime'         : XMLDateTime,
-             # FIXME: probably timedelta, but needs parsing.
-             # It looks like P29DT23H54M58S
-             'duration'         : XMLString}
-
-class WSDLParser(object):
-    """
-        Parser to get types and methods defined in the document.
-    """
-    def __init__(self, wsdl_url):
-        """
-            Initialize parser.
-
-            The WSDL document is loaded and is converted into xml.
-            In addition namespace parsing is done.
-
-            Initialized members:
-            self.wsdl_url  - url of wsdl document
-            self.wsdl - xml document read from wsdl_url (etree.Element)
-            self.nsmap - map of namespaces
-            self.tns - target namespace
-            self.xsd - schema namespace prefix used in the current document
-            self.wsdl_ns - wsdl namespace prefix used here
-
-            Parameters
-            ----------
-            wsdl_url : str
-                Address of the WSDL document.
-        """
-        #open wsdl page - get a file like object and
-        # parse it into xml
-        page_handler = urllib2.urlopen(wsdl_url)
-        self.wsdl = etree.parse(page_handler).getroot()
-        page_handler.close()
-        self.wsdl_url = wsdl_url
-
-        #process namespaces
-        self.nsmap = SOAPNS.copy() # copy predifined global map
-        self.nsmap.update(self.wsdl.nsmap) # mapping from the current document
-        #correct default wsdl namespace prefix
-        if None in self.nsmap:
-            del self.nsmap[None]
-        #get target namespace
-        self.tns = self.wsdl.get('targetNamespace', None)
-        #reverse dictionary
-        backmap = dict(zip(self.nsmap.values(), self.nsmap.keys()))
-        self.xsd = backmap['http://www.w3.org/2001/XMLSchema']
-        self.wsdl_ns = backmap['http://schemas.xmlsoap.org/wsdl/']
-
-    def get_service_names(self):
-        """
-            Returns names of services found in WSDL.
-
-            This is from wsdl:service section.
-
-            Returns
-            -------
-            out : list of str
-                Names.
-        """
-        services = self.wsdl.xpath('//%s:service' % self.wsdl_ns,
-                                                      namespaces=self.nsmap)
-        res = []
-        for service in services:
-            name = service.get("name", None)
-            if name is not None:
-                res.append(name)
-        return res
-
-    def get_type_name(self, element):
-        """
-            Get type name from XML element.
-
-            Parameters
-            ----------
-            element : etree.Element
-                XML description of the type.
-        """
-        name = element.get('name', None)
-        if name is None:
-            # find name in parent 'element'
-            parent = element.getparent()
-            if parent.tag == "{%s}element" %self.nsmap[self.xsd]:
-                name = parent.get('name', None)
-        return name
-
-    def create_named_class(self, name, types, allelements):
-        """
-            Creates a single named type.
-
-            Function searches through all available elements to find one
-            suitable. This is useful if a type is present as a child before
-            it is present in the list.
-
-            Parameters
-            ----------
-            name : str
-                Name of the type.
-            types : dict
-                Map of known types.
-            allelements : list of etree.Element instance
-                List of all types found in WSDL. It is used to create
-                related classes in place.
-        """
-        for element in allelements:
-            el_name = self.get_type_name(element)
-            if el_name == name:
-                self.create_class(element, el_name, types, allelements)
-                break
-
-    def collect_children(self, element, children, types, allelements):
-        """
-            Collect information about children (xml sequence, etc.)
-
-            Parameters
-            ----------
-            element : etree.Element
-                XML sequence container.
-            children : list
-                Information is appended to this list.
-            types : dict
-                Known types map.
-            allelements : list of etree.Element instance
-                List of all types found in WSDL. It is used to create
-                related classes in place.
-        """
-        for subel in element:
-            #iterate over sequence, do not consider in place defs
-            type = get_local_name(subel.get('type', None))
-            if type is None:
-                raise ValueError(
-                        "Do not support this type of complex type: %s"
-                                                         %subsub.tag)
-            ch = types.get(type, None)
-            if ch is None:
-                self.create_named_class(type, types, allelements)
-            ch = types.get(type, None)
-            if ch is None:
-                raise ValueError("Child %s class is not found " %type)
-            child_name = subel.get('name', 'unknown')
-            minOccurs = int(subel.get('minOccurs', 1))
-            maxOccurs = subel.get('maxOccurs', 1)
-            if maxOccurs != 'unbounded':
-                maxOccurs = int(maxOccurs)
-            children.append({ "name":child_name,
-                             'type' : ch,
-                             'min' : minOccurs,
-                             'max' : maxOccurs})
-
-    def create_class(self, element, name, types, allelements):
-        """
-            Create new type from xml description.
-
-            Parameters
-            ----------
-            element : etree.Element instance
-                XML description of a complex type.
-            name : str
-                Name of the new class.
-            types : dict
-                Map of already known types.
-            allelements : list of etree.Element instance
-                List of all types found in WSDL. It is used to create
-                related classes in place.
-        """
-        doc = None
-        children = []
-        base = []
-        #iterate over children
-        #handle only children, bases non-primitive classes and docs
-        for subel in element:
-            if subel.tag in ("{%s}sequence" %self.nsmap[self.xsd],
-                             "{%s}all" %self.nsmap[self.xsd],
-                             "{%s}choice" %self.nsmap[self.xsd]):
-                #add children - arguments of new class
-                self.collect_children(subel, children, types, allelements)
-
-            elif subel.tag == "{%s}complexContent" %self.nsmap[self.xsd]:
-                #base class
-                subel = subel[0]
-                if subel.tag == "{%s}extension" %self.nsmap[self.xsd]:
-                    base_name = get_local_name(subel.get("base", None))
-                    b = types.get(base_name, None)
-                    if b is None:
-                        self.create_named_class(base_name, types, allelements)
-                    b = types.get(base_name, None)
-                    if b is None:
-                        raise ValueError("Base %s class is not found" %base_name)
-                    base.append(b)
-                for subsub in subel:
-                    if subsub.tag in ("{%s}sequence" %self.nsmap[self.xsd],
-                                      "{%s}all" %self.nsmap[self.xsd],
-                                      "{%s}choice" %self.nsmap[self.xsd]):
-                        self.collect_children(subsub, children, types, allelements)
-            elif subel.tag == "{%s}annotation" %self.nsmap[self.xsd]:
-                if len(subel) and\
-                   subel[0].tag == "{%s}documentation" %self.nsmap[self.xsd]:
-                    doc = subel[0].text
-
-        if name not in types:
-            #create new class
-            cls = ComplexTypeMeta(name, base,
-                                      {"_children":children, "__doc__":doc})
-            types[name] = cls
-
-
-    def get_types(self, initialmap):
-        """
-            Constructs a map of all types defined in the document.
-
-            At the moment simple types are not processed at all!
-            Only complex types are considered. If attribute
-            or what so ever are encountered an exception if fired.
-
-            Parameters
-            ----------
-            initialmap : dict
-                Initial map of types. Usually it will be _primmap.
-                This is present here so that different services
-                can create own types of XMLAny.
-
-            Returns
-            -------
-            out : dict
-                A map of found types {type_name : complex class}
-        """
-        #find all types defined here
-        types = self.wsdl.xpath('//%s:complexType|//%s:simpleType' %
-                                (self.xsd, self.xsd), namespaces=self.nsmap)
-
-        res = initialmap.copy() #types container
-        #iterate over the found types and fill in the container
-        # If an element used types placed later in the document, it will
-        #created in place when calling create_class.
-        for t in types:
-
-            #get name of the type
-            name = self.get_type_name(t)
-            if name is None:
-                continue
-
-            #if type is primitive or was already processed, skip it
-            if (res.get(name, None) is not None):
-                continue
-
-            #if unknown simple type - raise error
-            if t.tag == "{%s}simpleType" %self.nsmap[self.xsd]:
-                raise ValueError("Uknown simple type %s" %name)
-
-            #handle complex type, this also registers new class to result
-            self.create_class(t, name, res, types)
-
-        return res
-
-    def get_methods(self, types):
-        """
-            Construct a map of all operations defined in the document.
-
-            Parameters
-            ----------
-            types : dict
-                Map of known types as returned by get_types.
-
-            Returns
-            -------