Commits

Anonymous committed c512e54

Add all the functionality I require. It still takes a lot of memory. Measurements show that the memory is eaten up by xml tree. cElementTree instead of lxml consumes twice less, but it has slightly different API. Is it worth adapting it?

  • Participants
  • Parent commits f4692f2

Comments (0)

Files changed (9)

 # OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
 # IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-from scio.client import Client, Fault, Method, NotSOAP
-
-__all__ = ['Client', 'Fault', 'Method', 'NotSOAP']
-
-
+from client import Client

scio/alltypes.py

-from exceptions import *
-from soap import *
-from lxml import etree
-from decimal import Decimal
-from datetime import date, datetime, time
-from urllib2 import urlopen, Request, HTTPError
-
-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 max_occurs != 'unbounded' and 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 : lxml.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
-        ns = "{" + self._namespace + "}"
-        #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 : lxml.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)
-            #check we have such an attribute
-            if name not in all_children_names:
-                raise ValueErro('does not have a "%s" member' % name)
-
-            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)
-
-        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)
-
-        #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):
-    def to_xml(self, parent, name):
-        value = etree.fromstring(self)
-        element = etree.SubElement(parent, name)
-        element.append(value)
-
-    def from_xml(self, element):
-        children = element.getchildren()
-        if children:
-            return children[0]
-        return None
-
-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')
-
-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.
-
-        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).
-        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 to_xml(self, *arg, **kw):
-        """
-            Convert from Python into xml message.
-        """
-        if self.style != "document" or not(self.literal):
-            raise RuntimeError(
-                "Only document/literal are supported. Improve Message class!")
-
-        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)
-
-        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.")
-
-        p = self.parts[0][1]() #decoding instance
-
-        res = p.from_xml(body)
-
-        #for wrapped doc style (the only one implemented) we now, 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 the location, action, name
-        and input and output classes.
-
-        TODO: add a useful repr
-
-        This is a copy from Scio.
-
-        self.input - input message - to convert from Python to xml
-        self.output - output message - to convert from xml to Python
-    """
-    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
-        self.__doc__ = doc
-
-    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()
-        except HTTPError, e:
-            if e.code in (202, 204):#empty returns
-                pass
-                #return self.client.handle_response(self.method, None)
-            else:
-                pass
-                #return self.client.handle_error(self.method, e)
-            raise
-
-        #string to xml
-        xml = etree.fromstring(response)
-        del response
-
-        #find soap body
-        body = xml.find(SOAP_BODY)
-        if body is None:
-            raise NotSOAP("No SOAP body found in response", response)
-        fault = body.find(SOAP_FAULT)
-        if fault is not None:
-            code = fault.find('faultcode')
-            if code is not None:
-                code = code.text
-            string = fault.find('faultstring')
-            if string is not None:
-                string = string.text
-            detail = fault.find('detail')
-            if detail is not None:
-                detail = detail.text
-            raise RuntimeError("SOAP Fault %s:%s <%s> %s%s"\
-                    %(method.location, method.name, code, string, detail))
-        body = body[0] # hacky? get the first real element
-
-        return self.output.from_xml(body)
-
-def get_local_name(full_name):
-    """
-        Removes namespace part of the name.
-    """
-    full_name = full_name[full_name.find('}')+1:]
-    full_name = full_name[full_name.find(':')+1:]
-    return full_name

scio/autowsdl.py

-# scio/autowsdl.py -- 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.
-
-import os
-
-from docutils import nodes
-from docutils.parsers.rst import directives
-from docutils.statemachine import ViewList
-from sphinx.util.compat import Directive
-
-import scio, scio.client
-
-class AutoWsdl(Directive):
-    required_arguments = 1
-    optional_arguments = 0
-    final_argument_whitespace = False
-    option_spec = {'namespace': directives.unchanged}
-    has_content = False
-    ns = 'unknown'
-    client = None
-
-    def run(self):
-        wsdl_file = self.arguments[0]
-        self.ns = self.options.get('namespace', self.ns_from_file(wsdl_file))
-        self.client = scio.Client(open(wsdl_file, 'r')) # FIXME accept urls too?
-        rst = self.doc()
-        state = self.state
-        node = nodes.section()
-        surrounding_title_styles = state.memo.title_styles
-        surrounding_section_level = state.memo.section_level
-        state.memo.title_styles = []
-        state.memo.section_level = 0
-        state.nested_parse(rst, 0, node, match_titles=1)
-        state.memo.title_styles = surrounding_title_styles
-        state.memo.section_level = surrounding_section_level
-        return node.children
-
-    def ns_from_file(self, filename):
-        bn = os.path.basename(filename)
-        bn, _ext = os.path.splitext(bn)
-        return bn
-
-    def doc(self):
-        client = self.client
-        buf = ViewList()
-        name = client.wsdl.wsdl.get(
-            'name', client.wsdl.wsdl.get('targetNamespace', '(unnamed)'))
-        title = 'Web Service: %s' % name
-        buf.append(title, '<autowsdl>')
-        buf.append('=' * len(title), '<autowsdl>')
-        buf.append('', '<autowsdl>')
-
-        buf.append('Methods', '<autowsdl>')
-        buf.append('-------', '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        buf.append("Methods are accessible under the ``service`` attribute "
-                      "of a client instance.", '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        for entry in dir(client.service):
-            # print 'method', entry
-            if entry.startswith('_') or entry == 'method_class':
-                continue
-            item = getattr(client.service, entry)
-            try:
-                doc = self.doc_method(item.method)
-                if doc is not None:
-                    buf.extend(doc)
-            except AttributeError, e:
-                print "failed method", entry, e
-                pass
-        buf.append('', '<autowsdl>')
-        buf.append('Types', '<autowsdl>')
-        buf.append('-----', '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        buf.append("Types are accessible under the ``type`` attribute "
-                      "of a client instance.", '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        for entry in dir(client.type):
-            if entry.startswith('_'):
-                continue
-            item = getattr(client.type, entry)
-            try:
-                doc = self.doc_type(item)
-                if doc is not None:
-                    buf.extend(doc)
-            except AttributeError, e:
-                print "failed type", entry, e
-                pass
-
-        return buf
-
-    def doc_type(self, cls):
-        buf = ViewList()
-        buf.append('.. class :: %s.%s' % (self.ns, cls.__name__), '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        if issubclass(cls, scio.client.EnumType):
-            buf.append('   Values: ', '<autowsdl>')
-            buf.append('', '<autowsdl>')
-            for val in cls._values:
-                buf.append('   * %s' % val, '<autowsdl>')
-                buf.append('', '<autowsdl>')
-        else:
-            if getattr(cls, '_content_type', None):
-                buf.append('   .. attribute :: _content', '<autowsdl>')
-                buf.append('', '<autowsdl>')
-                buf.append(
-                    '      type: :class:`%s.%s`' % (self.ns,
-                                                    cls._content_type.__name__),
-                    '<autowsdl>')
-                buf.append('', '<autowsdl>')
-            for a in getattr(cls, '_attributes', []):
-                buf.append('   .. attribute :: %s' % a.name, '<autowsdl>')
-                buf.append('', '<autowsdl>')
-            for c in getattr(cls, '_children', []):
-                buf.append('   .. attribute :: %s' % c.name, '<autowsdl>')
-                buf.append('', '<autowsdl>')
-                if issubclass(c.type, (scio.client.ComplexType,
-                                       scio.client.EnumType)):
-                    buf.append('      type: :class:`%s.%s`' % (self.ns,
-                                                               c.type.__name__),
-                                  '<autowsdl>')
-                else:
-                    buf.append('      type: %s.%s' % (self.ns, c.type.__name__),
-                               '<autowsdl>')
-
-                descr = getattr(cls, c.name, None)
-                if (descr and
-                    descr.max and
-                    (descr.max == 'unbounded' or descr.max > 1)):
-                    msg = '      This is a list with'
-                    if descr.max == 'unbounded' :
-                        msg += " an unlimited number of items."
-                    else:
-                        msg += "at most %s items." % descr.max
-                    buf.append('      ', '<autowsdl>')
-                    buf.append(msg, '<autowsdl>')
-
-                subs = getattr(c.type, '_substitutions', {})
-                if subs:
-                    buf.append('      ', '<autowsdl>')
-                    buf.append('      This attribute may contain any of the '
-                               'following types: ', '<autowsdl>')
-                    for name in sorted(subs.keys()):
-                        buf.append('       - :class:`%s.%s`' % (self.ns, name),
-                                   '<autowsdl>')
-                buf.append('', '<autowsdl>')
-        return buf
-
-    def doc_method(self, meth):
-        buf = ViewList()
-        details = []
-        req = []
-        opt = []
-        # print 'meth', meth, meth.input, meth.input.parts
-        if meth.input.parts:
-            for part_name, cls in meth.input.parts:
-                param_details, param_req, param_opt = self.doc_param(part_name, cls)
-                details.extend(param_details)
-                req.extend(param_req)
-                opt.extend(param_opt)
-        else:
-            # FIXME is this correct?
-            c = meth.input
-            name = c.name or c.__name__
-            req.append(name)
-            details.append('   :param %s: :class:`%s.%s`' %
-                           (name, self.ns, c.__name__))
-        if meth.input.headers:
-            for part_name, cls in meth.input.headers:
-                param_details, param_req, param_opt = self.doc_param(part_name, cls)
-                details.extend(param_details)
-                req.extend(param_req)
-                opt.extend(param_opt)
-
-        rtypes = []
-        # print meth.name, meth.output.parts
-        if meth.output.parts:
-            rtypes = [':class:`%s.%s`' % (self.ns, cls.__name__)
-                      for _, cls in meth.output.parts]
-        #print meth.name, meth.output.headers
-        if meth.output.headers:
-            rtypes.append(
-                '{%s}' % ', '. join('%s: :class:`%s.%s`' % (name,
-                                                            self.ns,
-                                                            cls.__name__)
-                                    for name, cls in meth.output.headers))
-        if len(rtypes) == 1:
-            details.append('   :rtype: %s' % rtypes[0])
-        elif len(rtypes) > 1:
-            details.append('   :rtype: (%s)' %
-                           ', '.join(rtypes))
-        signature = ', '.join(req)
-        if opt:
-            signature += '[' + ', '.join(opt) + ']'
-        buf.append('.. method :: %s.%s(%s)' % (self.ns, meth.name, signature),
-                      '<autowsdl>')
-        buf.append('', '<autowsdl>')
-        for d in details:
-            buf.append(d, '<autowsdl')
-        return buf
-
-    def doc_param(self, part_name, cls):
-        details = []
-        req = []
-        opt = []
-        kids = getattr(cls, '_children', ())
-        kids = kids + getattr(cls, '_attributes', ())
-        if kids:
-            for c in kids:
-                if c.min:
-                    min = int(c.min)
-                else:
-                    min = 0
-                if min > 0 or c.required:
-                    req.append(c.name)
-                    desc = ''
-                else:
-                    opt.append(c.name)
-                    desc = ' (optional)'
-                details.append('   :param %s: :class:`%s.%s`%s' %
-                               (c.name, self.ns, c.type.__name__, desc))
-        else:
-            name = part_name or cls._tag
-            # FIXME is this correct?
-            if name:
-                # print "Arg?", name, cls
-                req.append(name)
-                details.append('   :param %s: :class:`%s.%s`' %
-                               (name, self.ns, cls.__name__))
-        return details, req, opt
-
-
-def setup(app):
-    app.add_directive('autowsdl', AutoWsdl)
-
-
-# client.py -- soap classes for input and output
-#
-# Copyright (c) 2011, Leapfrog Online, 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 Online, LLC 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 <COPYRIGHT HOLDER> 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.
+"""
+    Top level access to SOAP service.
+"""
 
-# TODO
-# - schema types can extend enums. How the heck can that work?
+import wsdl
+import xmltypes
 
-from decimal import Decimal
-from lxml import etree
-from urllib2 import urlopen, Request, HTTPError
-from threading import RLock
-from time import strptime
-from datetime import date, datetime, time
-from dateutil.parser import parse as parse_date
-import logging
+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
 
-log = logging.getLogger(__name__)
-
-# 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
-
-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 }
-
-# singleton used by AttributeDescriptor
-notset = object()
-
-#
-# SOAP client class and helpers
-#
 class Client(object):
     """
-    WSDL client container class. Given an open wsdl file-like object,
-    instantiating a Client builds SOAP types and service calls using a
-    Factory, and provides access to them via the instance's ``type``
-    and ``service`` attributes.
+        Top level class to talk to soap services.
 
-    :param wsdl_fp: A file-like object containing the wsdl to use to
-                    construct the client types and methods.
-    :param transport: The transport mechanism for communicating with
-                      target services. Default: :func:`urlopen`.
-    :param service_class: A class that will contain services. An
-                          instance of this class will be available as
-                          client.service.
-    :param type_class: A class that will contain types. An instance of
-                       this class will be available as client.type.
-    :param reduce_callback: REQUIRED TO SUPPORT PICKLING! If you want to
-                            be able to pickle and unpickle scio types,
-                            you must supply a function that can, given
-                            a class name, return a bare instance of the
-                            type. This function *must* be defined at the
-                            top level of a module and must be marked as
-                            __safe_for_unpickle__. The reduce callback must
-                            have a signature compatible with the typical
-                            definition::
+        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.
 
-                              def reviver(classname, proto=object, args=()):
-                                  return proto.__new__(getattr(
-                                      some_client.type, classname), *args)
+        To examine present types or operations simply print (or touch repr):
+            client.types or client.service, correspondingly.
 
-                            The ``proto`` parameter will only be used for
-                            a number of basic types, including int
-                            and arrays (list).
+        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_fp, transport=None,
-                 service_class=None, type_class=None,
-                 reduce_callback=None):
-        self.wsdl = Factory(wsdl_fp)
-        if transport is None:
-            transport = urlopen
-        if service_class is None:
-            service_class = ServiceContainer
-        if type_class is None:
-            type_class = TypeContainer
-        self.transport = transport
-        self.service = service_class(self)
-        self.type = type_class(self)
-        self.reduce_callback = reduce_callback
-        self.wsdl.build(self)
-
-    def envelope(self, request):
-        """
-        Given an InputMessage, wrap in in a SOAP envelope and produce
-        an Element.
-        """
-        env = etree.Element('{%s}Envelope' % SOAPNS['soap-env'], nsmap=SOAPNS)
-        if request.headers:
-            header = etree.SubElement(
-                env, '{%s}Header' % SOAPNS['soap-env'], nsmap=SOAPNS)
-            for element in request.headerxml():
-                if element is not None:
-                    header.append(element)
-        body = etree.SubElement(
-            env, '{%s}Body' % SOAPNS['soap-env'], nsmap=SOAPNS)
-        for element in request.toxml():
-            if element is not None:
-                body.append(element)
-        return env
-
-    def send(self, method, request):
-        """
-        Send the SOAP request for the given method. Don't call this directly
-        (use the methods attached to a client's `service` attribute instead),
-        but do override it in a subclass to mock a service or change how
-        a request is sent.
-        """
-        response = self.transport(request).read()
-        return self.handle_response(method, response)
-
-    def handle_response(self, method, response):
-        """
-        Handle a seemingly successful response.
-        """
-        body, header = self.parse_response(method, response)
-        return method.output(body, header)
-
-    def parse_response(self, method, response):
-        """
-        Parse the response xml and return the soap body and header.
-        """
-        parsed = etree.fromstring(response)
-        body = parsed.find(SOAP_BODY)
-        if body is None:
-            raise NotSOAP("No SOAP body found in response", response)
-        self.raise_if_fault(method, body)
-        body = body[0] # hacky? get the first real element
-        header = parsed.find(SOAP_HEADER)
-        return body, header
-
-    def handle_error(self, method, err):
-        """
-        Handle an exception raised by a method call that may be a
-        SOAP Fault.
-        """
-        try:
-            response = err.fp.read()
-        except AttributeError:
-            response = None
-        if response:
-            try:
-                parsed = etree.fromstring(response)
-                body = parsed.find(SOAP_BODY)
-                self.raise_if_fault(method, body)
-            except SyntaxError:
-                pass
-        raise err # FIXME loses traceback
-
-    def raise_if_fault(self, method, body):
-        """
-        Raise a SOAP Fault if one is found in the response body.
-        """
-        if body is None:
-            return
-        fault = body.find(SOAP_FAULT)
-        if fault is not None:
-            code = fault.find('faultcode')
-            if code is not None:
-                code = code.text
-            string = fault.find('faultstring')
-            if string is not None:
-                string = string.text
-            detail = fault.find('detail')
-            if detail is not None:
-                detail = detail.text
-            raise Fault(method.location, method.name, code, string, detail)
-
-
-class Fault(Exception):
-    """
-    SOAP Fault exception. The method that raised the fault, the faultcode,
-    and faultstring are available as attributes of the exception.
-    """
-    def __init__(self, method_location, method_name, faultcode, faultstring, detail):
-        self.method_location = method_location
-        self.method_name = method_name
-        self.faultcode = faultcode
-        self.faultstring = faultstring
-        self.detail = detail
-        # for < 2.5 compatibility, can't call super()
-        Exception.__init__(
-            self, method_location, method_name, faultcode, faultstring, detail)
+    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):
-        return "SOAP Fault %s:%s <%s> %s%s" % (
-            self.method_location, self.method_name, self.faultcode,
-            self.faultstring, self.detail and ": %s" % self.detail or '')
-
-    def __unicode__(self):
-        return unicode(str(self))
-
-
-class NotSOAP(ValueError):
-    """
-    Exception thrown when a response is received that does not
-    contain SOAP xml. The full response is available as the
-    response attribute of the exception.
-    """
-    def __init__(self, msg, response, *arg, **kw):
-        self.msg = msg
-        self.response = response
-        Exception.__init__(self, msg, *arg, **kw)
-
-    def __str__(self):
-        return "%s:\n%s" % (self.msg, self.response)
-
-
-class TypeContainer(object):
-    """
-    Bucket that holds types defined in WSDL. Attached to client as `type`
-    attribute.
-    """
-    def __init__(self, client):
-        self._client = client
-
-
-class MethodCall(object):
-    """
-    Pseudo-closure for a single SOAP method call.
-    """
-    def __init__(self, client, method):
-        self.client = client
-        self.method = method
-
-    def __call__(self, *arg, **kw):
-        request = self.format_request(*arg, **kw)
-        try:
-            return self.send_request(request)
-            #boz: remove logging of potentially large pieces
-            #log.debug("Response: %s", response)
-            #return response
-        except HTTPError, e:
-            if e.code in (202, 204):
-                return self.client.handle_response(self.method, None)
-            else:
-                return self.client.handle_error(self.method, e)
-
-    def format_request(self, *arg, **kw):
-        request = self.client.envelope(self.method.input(*arg, **kw))
-        req_xml = etree.tostring(request)
-        del request #boz ??
-        #boz: remove logging of potentially large pieces
-        #log.debug("Request: %s", req_xml)
-        return Request(self.method.location, req_xml, self.headers())
-
-    def send_request(self, request):
-        return self.client.send(self.method, request)
-
-    def headers(self):
-        return {'Content-Type': 'text/xml',
-                'SOAPAction': self.method.action}
-
-
-class Method(object):
-    """
-    Definition of a single SOAP method, including the location, action, name
-    and input and output classes.
-
-    TODO: add a useful repr
-    """
-    def __init__(self, location, name, action, input, output):
-        self.location = location
-        self.name = name
-        self.action = action
-        self.input = input
-        self.output = output
-
-
-class ServiceContainer(object):
-    """
-    Bucket that holds service methods. Attached to client as `service`
-    attribute.
-    """
-    method_class = MethodCall
-    def __init__(self, client):
-        self._methods = []
-        self._client = client
-
-    def __setattr__(self, attr, val):
-        if attr.startswith('_'):
-            self.__dict__[attr] = val
-        else:
-            meth = self.method_class(self._client, val)
-            self._methods.append(meth)
-            self.__dict__[attr] = meth
-
-#
-# Pickling support
-#
-class Pickleable(object):
-    _client = None
-
-    def __reduce__(self):
-        try:
-            base = self._base_type()
-        except AttributeError:
-            base = None
-        try:
-            args = self.__getnewargs__()
-        except AttributeError:
-            args = ()
-        if self._client and self._client.reduce_callback:
-            return (self._client.reduce_callback,
-                    (self.__class__.__name__, base, args),
-                    self.__getstate__())
-        return object.__reduce__(self)
-
-    def __getstate__(self):
-        d = self.__dict__.copy()
-        if hasattr(self, '__slots__'):
-            d.update(dict((k, getattr(self, k)) for k in self.__slots__))
-        if '_client' in d:
-            del d['_client']
-        return d
-
-    def __setstate__(self, dct):
-        if hasattr(self, '__slots__'):
-            for k in self.__slots__:
-                setattr(self, k, dct.pop(k))
-        self.__dict__.update(dct)
-
-#
-# Types, elements and accessors
-#
-class Element(object):
-    """
-    Base class for xml elements and attributes
-    """
-    _tag = None
-    _prefix = None
-    _nsmap = None
-    _namespace = None
-    _typemap = {} # intentional
-    _position = 0
-
-    @classmethod
-    def fromxml(cls, element):
-        """
-        Convert value from xml. Override in typed subclasses
-        to do type conversion.
-        """
-        return element.text
-
-    @classmethod
-    def empty(cls):
-        """Return an empty instance of this class."""
-        return cls()
-
-    def toxml(self, tag=None, namespace=None, nsmap=None, empty=False):
-        if tag is None:
-            tag = self._tag
-        if namespace is None:
-            namespace = self._namespace
-        if nsmap is None:
-            nsmap = self._nsmap
-        if tag is None or namespace is None or nsmap is None:
-            raise ValueError("%s has no associated xml context" % self)
-        try:
-            value = unicode(self)
-        except TypeError:
-            value = None
-        tag = '{%s}%s' % (namespace, tag)
-        e = etree.Element(tag, nsmap=nsmap)
-        if value is not None and value != u'':
-            e.text = value
-        return e
-
-
-class SimpleTypeMeta(type):
-    """
-    Metaclass that registers each simple type in the Element typemap.
-    """
-    def __init__(cls, name, bases, dct):
-        if dct.get('xsi_type', None):
-            Element._typemap[dct['xsi_type'][1]] = cls
-        super(SimpleTypeMeta, cls).__init__(name, bases, dct)
-
-
-class SimpleType(Element):
-    """
-    Base class mixin for simple types. Allows simple types to be set by
-    passing xml elements to their constructors.
-    """
-    __metaclass__ = SimpleTypeMeta
-    xsi_type = None
-    def __new__(cls, *arg, **kw):
-        newarg, newkw = cls.adapt_args(arg, kw)
-        base = cls._base_type()
-        inst = base.__new__(cls, *newarg, **newkw)
-        inst.__init__(*newarg, **newkw)
-        return inst
-
-    @classmethod
-    def adapt_args(cls, arg, kw):
-        """
-        Adapt incoming arguments -- which may include xml Elements -- to
-        the argument list expected by the native class of this type.
-        """
-        newarg = []
-        for a in arg:
-            if isinstance(a, etree._Element):
-                val = cls.fromxml(a)
-                if val is not None:
-                    newarg.append(val)
-            else:
-                newarg.append(a)
-        return newarg, kw
-
-    @classmethod
-    def empty(cls):
-        # unset simple types are None, to distinguish from
-        # eg those set to empty string or 0
-        """Return an empty instance of this class. Empty SimpleTypes are
-        always None."""
-        return None
-
-    @classmethod
-    def _base_type(cls):
-        # FIXME this is pretty hacky
-        for t in cls.__mro__:
-            if t.__module__ != cls.__module__:
-                return t
-
-class IntType(SimpleType, int):
-    xsi_type = (NS_XSD, 'int')
-
-
-class LongType(SimpleType, long):
-    xsi_type = (NS_XSD, 'long')
-
-
-class StringType(SimpleType, unicode):
-    xsi_type = (NS_XSD, 'string')
-
-
-class DecimalType(SimpleType, Decimal):
-    xsi_type = (NS_XSD, 'decimal')
-
-
-class FloatType(SimpleType, float):
-    xsi_type = (NS_XSD, 'float')
-    #boz: to eliminate bug with repr/str conversion for float numbers
-    def toxml(self, tag=None, namespace=None, nsmap=None, empty=False):
-        if tag is None:
-            tag = self._tag
-        if namespace is None:
-            namespace = self._namespace
-        if nsmap is None:
-            nsmap = self._nsmap
-        if tag is None or namespace is None or nsmap is None:
-            raise ValueError("%s has no associated xml context" % self)
-        try:
-            value = unicode(repr(self))
-        except TypeError:
-            value = None
-        tag = '{%s}%s' % (namespace, tag)
-        e = etree.Element(tag, nsmap=nsmap)
-        if value is not None and value != u'':
-            e.text = value
-        return e
-
-
-class DateTimeType(SimpleType, datetime):
-    xsi_type = (NS_XSD, 'dateTime')
-
-    @classmethod
-    def adapt_args(cls, arg, kw):
-        newarg, newkw = SimpleType.adapt_args(arg, kw)
-        if len(newarg) == 1:
-            try:
-                dt = parse_date(newarg[0])
-                newarg = (dt.year, dt.month, dt.day,
-                          # microsecond always 0
-                          dt.hour, dt.minute, dt.second, 0,
-                          dt.tzinfo)
-            except ValueError:
-                # may be binary date 'string' from a pickle, let it through
-                pass
-        return newarg, newkw
-
-    def __str__(self):
-        return str(unicode(self))
-
-    def __unicode__(self):
-        return unicode(self.isoformat('T'))
-
-
-class DateType(SimpleType, date):
-    xsi_type = (NS_XSD, 'date')
-
-    @classmethod
-    def adapt_args(cls, arg, kw):
-        newarg, newkw = SimpleType.adapt_args(arg, kw)
-        if len(newarg) == 1:
-            try:
-                dt = parse_date(newarg[0])
-                newarg = (dt.year, dt.month, dt.day)
-            except ValueError:
-                # may be binary date 'string' from a pickle, let it through
-                pass
-        return newarg, newkw
-
-    def __str__(self):
-        return str(unicode(self))
-
-    def __unicode__(self):
-        return unicode(self.isoformat())
-
-
-class TimeType(SimpleType, time):
-    xsi_type = (NS_XSD, 'time')
-
-    @classmethod
-    def adapt_args(cls, arg, kw):
-        newarg, newkw = SimpleType.adapt_args(arg, kw)
-        if len(newarg) == 1:
-            dt = parse_date(newarg[0])
-            # microsecond always 0
-            newarg = (dt.hour, dt.minute, dt.second, 0, dt.tzinfo)
-        return newarg, newkw
-
-    def __str__(self):
-        return str(unicode(self))
-
-    def __unicode__(self):
-        return unicode(self.isoformat())
-
-
-# bool is notsubclassable, so this type just normalizes to the
-# string values 'true' or 'false'
-class BooleanType(SimpleType, unicode):
-    xsi_type = (NS_XSD, 'boolean')
-    true_vals = ('true', '1', 1)
-
-    @classmethod
-    def adapt_args(cls, arg, kw):
-        newarg, newkw = SimpleType.adapt_args(arg, kw)
-        if len(newarg) == 1:
-            if str(newarg[0]).lower() in cls.true_vals:
-                newarg = ('true',)
-            else:
-                newarg = ('false',)
-        return newarg, newkw
-
-    def __nonzero__(self):
-        return self == u'true'
-
-
-class EnumType(Element, Pickleable):
-    """
-    Element representing a SOAP Enum. Subclasses have a defined set
-    of allowed values.
-    """
-    _values = ()
-    _attributes = ()
-    _children = ()
-    _content_type = None # FIXME this should be setable
-
-    def __init__(self, val=None, **kw):
-        err = False
-        self.value = None
-        if val is not None:
-            try:
-                val = val.text
-            except AttributeError:
-                pass
-            if val in self._values:
-                self.value = val
-            elif ' ' in val:
-                # enums may be a space-separated list of flags
-                # still only one real value though -- not a true
-                # list. So we just check to make sure all values
-                # are legal, but store the original space-separated
-                # string
-                parts = val.split(' ')
-                for part in parts:
-                     if not part.strip() in self._values:
-                         err = True
-                         break
-                self.value = val
-            else:
-                err = True
-            if err:
-                raise ValueError("Illegal enum value %s for %s (allowed: %s)" %
-                                 (val, self.__class__.__name__, self._values))
-        super(EnumType, self).__init__(**kw)
-
-    def __unicode__(self):
-        return self.value
-    __str__ = __unicode__
+        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 "%s.%s" % (self.__class__.__name__, self.value)
+        return self.__str__()
 
-    @classmethod
-    def empty(cls):
-        # Like simple types, an empty enum is just None, to avoid collisions
-        # with any blank-looking enum values
-        return None
 
-
-class UnionType(Element, Pickleable):
-    _values = ()
-    _attributes = ()
-    _children = ()
-    _content_type = None # FIXME this should be setable
-
-    def __init__(self, val=None, **kw):
-        self.value = None
-        if val is not None:
-            try:
-                self.value = val.text
-            except AttributeError:
-                self.value = val
-        super(UnionType, self).__init__(**kw)
-
-    def __unicode__(self):
-        if self.value is None:
-            return u''
-        return unicode(self.value)
-    __str__ = __unicode__
-
-    def __repr__(self):
-        return "%s.%s" % (self.__class__.__name__, self.value)
-
-    @classmethod
-    def empty(cls):
-        return None
-
-
-class AnyType(object):
-    """
-    Factory for runtime type lookups. When given an 'anyType' element, the
-    client must look up the type to deserialize to from the type given
-    by the element, in some attribute with a local name of 'type'.
-    """
-    client = None
-    # needed because I get used as an Element, though I'm not really one
-    _tag = _namespace = _nsmap = _prefix = None
-
-    def __init__(self, client):
-        self.client = client
-        self.__name__ = '<AnyType>'
-
-    def __call__(self, value=None, **kw):
-        if value is None:
-            return
-        valtype = xsi_type(value)
-        if not valtype:
-            return
-        # Can't create types at runtime, only find them
-        valcls = self.client.wsdl.resolve(valtype, allow_ref=False)
-        return valcls(value, **kw)
-
-    @classmethod
-    def empty(cls):
-        """Return an empty instance. Like simple types, an empty anyType
-        is just None."""
-        return None
-
-
-class AnyAttribute(object):
-    """Minimal wrapper for untyped extra attributes.
-    """
-    def __init__(self, name):
-        self.name = name
-
-
-class ArrayType(list, Pickleable):
-    _arrayType = None
-    _client = None
-    _tag = _namespace = _nsmap = _prefix = None
-
-    def __init__(self, iterable=()):
-        for item in iterable:
-            self.append(self._arrayType(item))
-
-    def __reduce__(self):
-        if self._client and self._client.reduce_callback:
-            return (self._client.reduce_callback,
-                    (self.__class__.__name__, list),
-                    self.__getstate__(),
-                    iter(i for i in self))
-        return object.__reduce__(self)
-
-
-class ComplexType(Element, Pickleable):
-    """
-    Base class for SOAP complexTypes, at least the ones that look like
-    classes. For each complexType in a WSDL document, the Factory creates
-    a ComplexType subclass with the appropriate children and attributes.
-    """
-    _content = None
-    _content_type = None
-    _attributes = ()
-    _children = ()
-    _substitutions = None
-    _arg = () # TODO
-    _abstract = False
-    _client = None
-    _type_attr = None
-    _type_value = None
-    _child_count = 0
-    any_attribute = False
-
-    def __new__(cls, element=None, **kw):
-        # Similar to AnyType()(element), but just finds the
-        # class. AnyType() calls cls(), which if done here,
-        # results in obj.__init__() getting called twice.
-        if cls._abstract and cls._client and element is not None:
-            valtype = xsi_type(element)
-            if valtype:
-                cls = cls._client.wsdl.resolve(valtype, allow_ref=False)
-        return object.__new__(cls)
-
-    def __init__(self, element=None, **kw):
-        self.qns = '{%s}' % self._namespace
-        # FIXME allow element to be a dict, eg:
-        # Foo.Address = {'Zip': '12345', 'StreetAddress': '101 street lane'}
-        # FIXME support positional args, including assignment
-        # of scio classes to children via positional args
-        if element is not None:
-            content = None
-            if isinstance(element, etree._Element):
-                attrs = set(attr.name for attr in self._attributes)
-                kids = set(child.name for child in self._children)
-                kids_subs = self._child_substitutions()
-                content = element.text
-                for attr, aval in element.attrib.items():
-                    if attr != local(attr):
-                        continue
-                    if self.any_attribute and attr not in attrs:
-                        self._attributes.append(AnyAttribute(attr))
-                    setattr(self, attr, aval)
-                for el in element:
-                    if el.text is not None or el.attrib or len(el):
-                        name = local(el.tag)
-                        if name in kids:
-                            setattr(self, name, el)
-                        else:
-                            # substitutionGroups
-                            real_cls, real_name = kids_subs.get(name, (None,None))
-                            if real_cls:
-                                setattr(self, real_name, real_cls(el))
-                                # FIXME Or for unnamed subelements:
-                                # setattr(self, name, real_cls(el))
-                        # TODO handle any tag, ref any_attribute above
-            else:
-                content = element
-            if content is not None:
-                # support type conversion
-                if self._content_type:
-                    self._content = self._content_type(content)
-                else:
-                    self._content = element.text
-        for k, v in kw.items():
-            # This assures that subelements have correct element name
-            # when they are passed in as ComplexType() instances
-            if not getattr(v, '_tag', None):
-                try:
-                    v._name = k
-                except AttributeError:
-                    # builtins and such
-                    pass
-            setattr(self, k, v)
-
-    def _child_substitutions(self):
-        # FIXME this is a hack, feels like I'm poking through
-        # things I shouldn't be from here.
-        key = '_child_substitutions_'
-        if not hasattr(self.__class__, key):
-            subs = {}
-            for child in self._children:
-                if not hasattr(child.type, '_substitutions'):
-                    continue
-                for sub_name, sub in child.type._substitutions.items():
-                    subs[sub_name] = (sub, child.name)
-            setattr(self.__class__, key, subs)
-        return getattr(self, key)
-
-    def _findall(self, element, child):
-        # Sometimes you need to be qualified, sometimes not
-        check_unqual = True
-        for n in element.findall('./%s%s' % (self.qns, child.name)):
-            check_unqual = False
-            yield n
-        if check_unqual:
-            for n in element.findall("./%s" % child.name):
-                yield n
-
-    def _items(self):
-        items = []
-        for a in self._attributes + self._children:
-            # access the internal key not the real attribute to
-            # avoid autovivification
-            val = getattr(self, "_%s_" % a.name, None)
-            if val is not None:
-                items.append((a.name, val))
-        return items
-
-    def __repr__(self):
-        props = []
-        if self._content is not None:
-            props.append('_content=%r' % self._content)
-        for name, val in self._items():
-            props.append('%s=%r' % (name, val))
-        return '%s(%s)' % (self.__class__.__name__,
-                           ', '.join(props))
-
-    def __unicode__(self):
-        if self._content is not None:
-            return unicode(self._content)
-        return u''
-
-    __str__ = __unicode__
-
-    def __nonzero__(self):
-        return bool(self._content or self._items())
-
-    def __iter__(self):
-        if self:
-            return iter([self])
-        return iter([])
-
-    def toxml(self, tag=None, namespace=None, nsmap=None, empty=False):
-        if tag is None:
-            tag = self._tag
-        if namespace is None:
-            namespace = self._namespace
-        if nsmap is None:
-            nsmap = self._nsmap
-        if tag is None or namespace is None or nsmap is None:
-            raise ValueError("%s has no associated xml context" % self)
-        if self._content is not None:
-            value = unicode(self._content)
-        else:
-            value = None
-        tag = '{%s}%s' % (namespace, tag)
-        e = etree.Element(tag, nsmap=nsmap)
-        if self._type_attr and self._type_value:
-            e.attrib[self._type_attr] = self._type_value
-        if isinstance(value, basestring):
-            e.text = value
-        elif isinstance(value, etree._Element):
-            e.append(value)
-        for attr in self._attributes:
-            at_val = getattr(self, attr.name, None)
-            if at_val is not None:
-                e.attrib[attr.name] = unicode(at_val)
-        for child in self._children:
-            # use private accessors to avoid autovivification
-            # since we're potentially passing empty=True to children
-            # and we don't want to render all possible children, only
-            # those with actual values.
-            key = "_%s_" % child.name
-            ch_val = getattr(self, key, None)
-            if ch_val is not None:
-                if isinstance(ch_val, list):
-                    for ch in ch_val:
-                        ch_el = ch.toxml(child.name, namespace, nsmap, empty)
-                        if ch_el is not None:
-                            e.append(ch_el)
-                else:
-                    ch_el = ch_val.toxml(child.name, namespace, nsmap, empty)
-                    if ch_el is not None:
-                        e.append(ch_el)
-        if not empty and e.text is None and not e.attrib and not len(e):
-            return None
-        return e
-
-
-class AttributeDescriptor(object):
-    """
-    AttributeDescriptors are used as properties of complex types each one models
-    an attribute or element that is part of the complex type. The descriptor
-    mediates access to the type instance, which holds the value.
-    """
-    required = False
-    name = None
-    min = max = None
-
-    def __init__(self, name, type_=None, required=False, min=None, max=None,
-                 doc=None, **kw):
-        self.name = name
-        if type_ is None:
-            type_ = StringType
-        self.type = type_
-        self.required = required
-        self.min = min
-        self.max = max
-        if doc is not None:
-            self.__doc__ = doc
-
-    def __str__(self):
-        return "%s(%s:%s)" % (
-            self.__class__.__name__, self.name, self.type.__class__.__name__)
-
-    def __get__(self, obj, cls):
-        if obj is None:
-            return self
-        key = '_%s_' % self.name
-        val = getattr(obj, key, notset)
-        if val is notset:
-            # ComplexTypes should not return None, but fresh
-            # empty versions of themselves if not set -- of course
-            # for this to work they have to *become set* when
-            # accessed for the first time. This enables you to say:
-            # Foo.Bar.Baz = 1 even if Foo.Bar has not yet been set.
-            val = self.type.empty()
-            setattr(obj, key, val)
-        return val
-
-    def __set__(self, obj, value):
-        # convert from node or other xml value into simple value
-        # # print self.name, obj, value, self.type
-        key = '_%s_' % self.name
-
-        if isinstance(value, (list, tuple)):
-            stop
-            new = []
-            for item in value:
-                if not isinstance(item, self.type):
-                    item = self._new(item)
-                item._position = obj._child_count
-                obj._child_count += 1
-                new.append(item)
-            setattr(obj, key, new)
-            return
-
-        if self.isanytype() or not isinstance(value, self.type):
-            value = self._new(value)
-
-        # remember the order in which we saw assignments
-        value._position = obj._child_count
-        obj._child_count += 1
-
-        # sort of hacky set/append combo
-        # this is needed to handle parsing multiple values out of xml
-        curval = getattr(obj, key, None)
-        if curval is not None and (self.max == 'unbounded' or self.max > 1):
-            if not isinstance(curval, list):
-                curval = [curval]
-            curval.append(value)
-            value = curval
-        setattr(obj, key, value)
-
-    def __delete__(self, obj):
-        delattr(obj, '_%s_' % self.name)
-
-    def _new(self, value):
-        return self.type(value)
-
-    def isanytype(self):
-        return (isinstance(self.type, AnyType)
-                or getattr(self.type, '_abstract', False))
-
-
-class InputMessage(object):
-    """
-    Base of the marshalling chain for input messages. Call this with
-    positional or keyword arguments appropriate to the message, and
-    get back a formatter whose toxml() method will yield xml Elements
-    for inclusion in a SOAP message.
-    """
-    def __init__(self, tag, namespace, nsmap, parts, style, literal, headers):
-        self.tag = tag
-        self.namespace = namespace
-        self.nsmap = nsmap
-        self.parts = parts
-        self.style = style
-        self.literal = literal
-        self.headers = headers
-        self.formatter = self._pick_formatter()
-
-    def __call__(self, *arg, **kw):
-        tag = self.tag
-        namespace = self.namespace
-        nsmap = self.nsmap
-        header_fmt = []
-        for name, cls in self.headers:
-            val = kw.pop(name, None)
-            if val is not None:
-                header_fmt.append((name, cls(val)))
-        if len(self.parts) == 1:
-            return self.formatter(
-                tag, namespace, nsmap, [
-                    (part_tag, cls(*arg, **kw))
-                    for part_tag, cls in self.parts],
-                header_fmt
-                )
-        else:
-            # map each arg in args to one part
-            parts_fmt = []
-            alist = list(arg)
-            for part_tag, cls in self.parts:
-                fmt = cls(alist.pop(0), **kw)
-                parts_fmt.append((part_tag, fmt))
-            return self.formatter(tag, namespace, nsmap, parts_fmt, header_fmt)
-
-    def _pick_formatter(self):
-        if self.style == 'document':
-            # decide whether wrapped or not
-            wrapper = self._is_wrapper()
-            if self.literal:
-                if wrapper:
-                    return DocumentLiteralWrapperInputFormatter
-                else:
-                    return DocumentLiteralInputFormatter
-            elif wrapper:
-                return DocumentEncodedWrapperInputFormatter # FIXME
-            else:
-                return DocumentEncodedInputFormatter # FIXME
-        elif self.style == 'rpc':
-            if self.literal:
-                return RpcLiteralInputFormatter
-            else:
-                return RpcEncodedInputFormatter
-
-    def _is_wrapper(self):
-        if len(self.parts) != 1:
-            return False
-        part = self.parts[0][1]
-        if part._tag != self.tag:
-            return False
-        return True
-
-
-class InputFormatter(object):
-    """
-    Base class for input message formatters
-    """
-    def __init__(self, tag, namespace, nsmap, parts, headers):
-        self.tag = tag
-        self.namespace = namespace
-        self.nsmap = nsmap
-        self.parts = parts
-        self.headers = headers
-
-    def toxml(self):
-        raise NotImplemented
-
-    def headerxml(self):
-        for name, hdr in self.headers:
-            yield hdr.toxml(name, self.namespace, self.nsmap)
-
-
-class DocumentLiteralWrapperInputFormatter(InputFormatter):
-    """
-    Input message formatter that formats a document literal message
-    with wrapper. This means that the body of the message is simply
-    the serialized xml of the single message part, which must be
-    a complexType element with the same name as the operation.
-    """
-    def toxml(self):
-        _, part = self.parts[0]
-        yield part.toxml(part._tag, self.namespace, self.nsmap, empty=True)
-
-
-class DocumentLiteralInputFormatter(InputFormatter):
-    """
-    Input message formatter for non-wrapped document literal messages.
-    The main difference between this type of message and wrapped types
-    is that there may be > 1 input part, and they are not all necessarily
-    contained in an element with the same name as the operation.
-    """
-    def toxml(self):
-        for part_tag, part in self.parts:
-            yield part.toxml(part_tag, self.namespace, self.nsmap, empty=True)
-
-
-class RpcLiteralInputFormatter(InputFormatter):
-    """
-    Input message formatting in the RPC literal style. A top-level element
-    named after the operation wraps each input part, which may be a
-    complexType or simple type.
-    """
-    def toxml(self):
-        tag = '{%s}%s' % (self.namespace, self.tag)
-        e = etree.Element(tag, nsmap=self.nsmap)
-        for part_tag, type_ in self.parts:
-            if part_tag:
-                # print "appending", part_tag, type_
-                part_xml = type_.toxml(part_tag, self.namespace, self.nsmap)
-                if part_xml is not None:
-                    e.append(part_xml)
-        yield e
-
-
-class RpcEncodedInputFormatter(InputFormatter):
-    """
-    Input message formatter for rpc/enc style. A top-level element
-    named after the operation wraps each input part, which may be a complex
-    or simple type. Each part's xml includes an xsi:type attribute with
-    the value being the namespaced type of the element.
-    """
-    def toxml(self):
-        tag = '{%s}%s' % (self.namespace, self.tag)
-        nsmap = SOAPNS.copy()
-        nsmap.update(self.nsmap)
-        backmap = dict(zip(nsmap.values(), nsmap.keys()))
-        e = etree.Element(tag, nsmap=nsmap)
-        for part_tag, type_ in self.parts:
-            if part_tag:
-                part_xml = type_.toxml(part_tag, self.namespace, self.nsmap)
-                if part_xml is not None:
-                    if type_.xsi_type:
-                        ns, local = type_.xsi_type
-                        prefix = backmap[ns]
-                    part_xml.attrib['{%s}type' % NS_XSI] = (
-                        '%s:%s' % (prefix, local))
-                    e.append(part_xml)
-        yield e
-
-
-class OutputMessage(object):
-    """
-    Unmarshaller for SOAP responses. This class probably needs subclasses
-    for all of the different binding styles.
-    """
-    def __init__(self, tag, namespace, nsmap, parts, headers):
-        self.tag = tag
-        self.namespace = namespace
-        self.nsmap = nsmap
-        self.parts = parts
-        self.headers = headers
-
-    def __call__(self, body, header=None):
-        result = []
-        local_tag = local(body.tag)
-        # print [(name, part) for name, part in self.parts]
-        for part_tag, part in self.parts:
-            # print name, part.name, tname
-            if part_tag is None:
-                part_tag = part._tag
-            if part_tag is None:
-                raise ValueError("No part tag for part %s" % part)
-            if local_tag in (part_tag, part._tag):
-                result.append(part(body))
-            else:
-                # ns = body.nsmap[body.prefix]
-                part_el = None
-                # print "{%s}%s {%s}%s" % (ns, part.name, ns, name)
-                if part._tag:
-                    part_el = body.find(part._tag)
-                if part_el is None:
-                    part_el = body.find(part_tag)
-                if part_el is not None:
-                    result.append(part(part_el))
-                else:
-                    log.debug(
-                        "No element found in %s  for part %s/%s",
-                        body, part_tag, part._tag)
-        if header is not None:
-            headers = {}
-            for header_tag, part in self.headers:
-                header_el = header.find('{%s}%s' % (self.namespace, header_tag))
-                if header_el is not None:
-                    headers[header_tag] = part(header_el)
-            if headers:
-                result.append(headers)
-        if len(result) == 1:
-            return result[0]
-        return tuple(result)
-
-
-#
-# The wsdl type factory.
-#
-class Factory(object):
-    """
-    WSDL type factory.
-    """
-    _typemap = Element._typemap.copy()
-    _typemap.update({
-        'integer': IntType,
-        'positiveInteger': IntType,
-        'short': IntType,
-        'unsignedInt': IntType,
-        'long': LongType,
-        'byte': StringType,
-        'double': FloatType,
-        'base64Binary': StringType,
-        'anyURI': StringType,
-        'language': StringType,
-        'token': StringType,
-        # FIXME: probably timedelta, but needs parsing.
-        # It looks like P29DT23H54M58S
-        'duration': StringType
-        })
-
-    _simple_tag = '{http://www.w3.org/2001/XMLSchema}simpleType'
-    _enum_tag =  '{http://www.w3.org/2001/XMLSchema}enumeration'
-    _attr_tag = '{http://www.w3.org/2001/XMLSchema}attribute'
-    _seq_tag = '{http://www.w3.org/2001/XMLSchema}sequence'
-    _all_tag = '{http://www.w3.org/2001/XMLSchema}all'
-    _cplx_tag = '{http://www.w3.org/2001/XMLSchema}complexContent'
-    _ext_tag = '{http://www.w3.org/2001/XMLSchema}extension'
-    _spl_tag = '{http://www.w3.org/2001/XMLSchema}simpleContent'
-    _element_tag = '{http://www.w3.org/2001/XMLSchema}element'
-    _choice_tag = '{http://www.w3.org/2001/XMLSchema}choice'
-    _restr_tag = '{http://www.w3.org/2001/XMLSchema}restriction'
-    _any_attr_tag = '{http://www.w3.org/2001/XMLSchema}anyAttribute'
-    _list_tag = '{http://www.w3.org/2001/XMLSchema}list'
-
-    def __init__(self, wsdl_file):
-        self.wsdl = etree.parse(wsdl_file).getroot()
-        self._lock = RLock()
-        self._lock.acquire()
-        try:
-            self._typemap = self._typemap.copy()
-            self._process_namespaces()
-        finally:
-            self._lock.release()
-
-    def build(self, client):
-        """
-        Generate classes and methods for types and bindings defined in
-        the wsdl file. Classes and methods become attributes of the type
-        and service containers of the given client instance, respectively.
-        """
-        self._lock.acquire()
-        try:
-            self._process_types(client)
-            self._process_methods(client)
-            return client
-        finally:
-            self._lock.release()
-
-    def resolve(self, name, allow_ref=True):
-        """
-        Resolve a class name to a class.
-        """
-        try:
-            return self._typemap[name]
-        except KeyError:
-            if allow_ref:
-                return TypeRef(name, self)
-            raise
-
-    def _process_namespaces(self):
-        self.nsmap = SOAPNS.copy()
-        self.nsmap.update(self.wsdl.nsmap)
-        if None in self.nsmap:
-            # FIXME obviously don't just use "t"
-            self.nsmap['t'] = self.nsmap.pop(None)
-        tns = self.wsdl.get('targetNamespace', None)
-        backmap = dict(zip(self.nsmap.values(), self.nsmap.keys()))
-        self._tns = backmap[tns]
-        self._xsd = backmap['http://www.w3.org/2001/XMLSchema']
-        self._soap_ns = backmap['http://schemas.xmlsoap.org/wsdl/soap/']
-        self._soap12_ns = backmap['http://schemas.xmlsoap.org/wsdl/soap12/']
-        self._wsdl_ns = backmap['http://schemas.xmlsoap.org/wsdl/']
-
-    def _process_types(self, client):
-        self._refs = []
-        types = self.wsdl.xpath('//%s:complexType|//%s:simpleType' %
-                                (self._xsd, self._xsd),
-                                namespaces=self.nsmap)
-        for t in types:
-            name = t.get('name', None)
-            force_name = False
-            if name is None:
-                # find name in parent 'element'
-                p = t.getparent()
-                if p.tag == self._element_tag:
-                    name = p.get('name', None)
-                    force_name = True
-            if name is None:
-                continue
-            self._make_class(client, t, name=name, force_name=force_name)
-        self._resolve_refs()
-
-    def _resolve_refs(self):
-        # print "refs to resolve", self._refs
-        for client_type, name in self._refs:
-            ref = getattr(client_type, name)
-            if isinstance(ref, TypeRef):
-                setattr(client_type, name, ref())
-        self._refs = []
-
-    def _process_methods(self, client):
-        services = self.wsdl.xpath('//%s:service' % self._wsdl_ns,
-                                   namespaces=self.nsmap)
-        for service in services:
-            for port in service.xpath('//%s:port' % self._wsdl_ns,
-                                      namespaces=self.nsmap):
-                if not self._is_soap_port(port):
-                    continue
-                self._process_port(client, port)
-
-    def _is_soap_port(self, port):
-        soap_ns = (self.nsmap[self._soap_ns], self.nsmap[self._soap12_ns])
-        for el in port:
-            if el.nsmap[el.prefix] in soap_ns:
-                return True
-        return False
-