[tryton-debian-vcs] suds branch upstream created. 86b0d57fe6b866708e174569278e32f9947e6bbb

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Wed Nov 27 16:49:09 UTC 2013


The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/suds.git;a=commitdiff;h=86b0d57fe6b866708e174569278e32f9947e6bbb
commit 86b0d57fe6b866708e174569278e32f9947e6bbb
Author: Daniel Baumann <daniel at debian.org>
Date:   Sun Oct 31 16:09:02 2010 +0100

    Adding upstream version 0.4.1.

diff --git a/python-suds.spec b/python-suds.spec
index a9b5a88..8d40ac0 100644
--- a/python-suds.spec
+++ b/python-suds.spec
@@ -2,9 +2,9 @@
 
 Summary: A python SOAP client
 Name:  python-suds
-Version: 0.4
+Version: 0.4.1
 Release: 1%{?dist}
-Source0: https://fedorahosted.org/releases/s/u/%{name}/%{name}-%{version}.tar.gz
+Source0: https://fedorahosted.org/releases/s/u/suds/%{name}-%{version}.tar.gz
 License: LGPLv3+
 Group: Development/Libraries
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
@@ -15,11 +15,10 @@ Url: https://fedorahosted.org/suds
 
 %description
 The suds project is a python soap web services client lib.  Suds leverages
-python meta programming to provide an intuative API for consuming web
+python meta programming to provide an intuitive API for consuming web
 services.  Objectification of types defined in the WSDL is provided
 without class generation.  Programmers rarely need to read the WSDL since
-services and WSDL based objects can be easily inspected.  Supports
-pluggable soap bindings.
+services and WSDL based objects can be easily inspected.
 
 %prep
 %setup -q
@@ -55,8 +54,24 @@ rm -rf $RPM_BUILD_ROOT
 %doc README LICENSE
 
 %changelog
-* Thu Dec 17 2009 jortel <jortel at redhat.com> - 0.4-1
-- 0.4
+* Thu Oct 15 2010 jortel <jortel at redhat.com> - 0.4.1-1
+- 0.4.1
+
+* Thu Sep 8 2010 jortel <jortel at redhat.com> - 0.4-1
+- Fix spelling errors in spec description.
+- Fix source0 URL warning.
+- Updated caching to not cache intermediate wsdls.
+- Added DocumentCache which caches verified XML documents as text. User can choose.
+- Added cachingpolicy option to allow user to specify whether to cache XML documents or the WSDL object.
+- Provided for repeating values in reply for message parts consistent with way handled in nested objects.
+- Added charset=utf-8 to stock content-type http header.
+- Added <?xml version="1.0" encoding="UTF-8"?> to outgoing SOAP messages.
+- Detection of faults in successful (http=200) replies and raise WebFault. Search for <soapenv:Fault/>.
+- Add plugins facility. 
+- Fixed Tickets: #251, #313, #314, #334
+
+* Thu Jul 22 2010 David Malcolm <dmalcolm at redhat.com> - 0.3.9-2
+- Rebuilt for https://fedoraproject.org/wiki/Features/Python_2.7/MassRebuild
 
 * Thu Dec 17 2009 jortel <jortel at redhat.com> - 0.3.9-1
 - Bumped python requires to 2.4
diff --git a/suds/__init__.py b/suds/__init__.py
index 4f4cdcd..2ffb50d 100644
--- a/suds/__init__.py
+++ b/suds/__init__.py
@@ -26,8 +26,8 @@ import sys
 # Project properties
 #
 
-__version__ = '0.4'
-__build__="(beta) R685-20100513"
+__version__ = '0.4.1'
+__build__="(beta) R703-20101015"
 
 #
 # Exceptions
diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py
index ef857df..4a7a996 100644
--- a/suds/bindings/binding.py
+++ b/suds/bindings/binding.py
@@ -33,6 +33,7 @@ from suds.bindings.multiref import MultiRef
 from suds.xsd.query import TypeQuery, ElementQuery
 from suds.xsd.sxbasic import Element as SchemaElement
 from suds.options import Options
+from suds.plugin import PluginContainer
 from copy import deepcopy 
 
 log = getLogger(__name__)
@@ -142,6 +143,8 @@ class Binding:
         reply = self.replyfilter(reply)
         sax = Parser()
         replyroot = sax.parse(string=reply)
+        plugins = PluginContainer(self.options().plugins)
+        plugins.message.parsed(reply=replyroot)
         soapenv = replyroot.getChild('Envelope')
         soapenv.promotePrefixes()
         soapbody = soapenv.getChild('Body')
diff --git a/suds/builder.py b/suds/builder.py
index 95f81c4..c2aad98 100644
--- a/suds/builder.py
+++ b/suds/builder.py
@@ -44,10 +44,10 @@ class Builder:
         else:
             type = name
         cls = type.name
-        if len(type):
-            data = Factory.object(cls)
-        else:
+        if type.mixed():
             data = Factory.property(cls)
+        else:
+            data = Factory.object(cls)
         resolved = type.resolve()
         md = data.__metadata__
         md.sxtype = resolved
@@ -73,10 +73,15 @@ class Builder:
             value = []
         else:
             if len(resolved) > 0:
-                value = Factory.object(resolved.name)
-                md = value.__metadata__
-                md.sxtype = resolved
-                md.ordering = self.ordering(resolved)
+                if resolved.mixed():
+                    value = Factory.property(resolved.name)
+                    md = value.__metadata__
+                    md.sxtype = resolved
+                else:
+                    value = Factory.object(resolved.name)
+                    md = value.__metadata__
+                    md.sxtype = resolved
+                    md.ordering = self.ordering(resolved)
         setattr(data, type.name, value)
         if value is not None:
             data = value
diff --git a/suds/client.py b/suds/client.py
index 0bdde6f..1692b26 100644
--- a/suds/client.py
+++ b/suds/client.py
@@ -111,7 +111,7 @@ class Client(object):
         reader = DefinitionsReader(options, Definitions)
         self.wsdl = reader.open(url)
         plugins = PluginContainer(options.plugins)
-        plugins.initialized(wsdl=self.wsdl)
+        plugins.init.initialized(wsdl=self.wsdl)
         self.factory = Factory(self.wsdl)
         self.service = ServiceSelector(self, self.wsdl.services)
         self.sd = []
@@ -592,14 +592,14 @@ class SoapClient:
         timer.start()
         result = None
         binding = self.method.binding.input
-        msg = binding.get_message(self.method, args, kwargs)
+        soapenv = binding.get_message(self.method, args, kwargs)
         timer.stop()
         metrics.log.debug(
                 "message for '%s' created: %s",
                 self.method.name,
                 timer)
         timer.start()
-        result = self.send(msg)
+        result = self.send(soapenv)
         timer.stop()
         metrics.log.debug(
                 "method '%s' invoked: %s",
@@ -607,11 +607,11 @@ class SoapClient:
                 timer)
         return result
     
-    def send(self, msg):
+    def send(self, soapenv):
         """
         Send soap message.
-        @param msg: A soap message to send.
-        @type msg: basestring
+        @param soapenv: A soap envelope to send.
+        @type soapenv: L{Document}
         @return: The reply to the sent message.
         @rtype: I{builtin} or I{subclass of} L{Object}
         """
@@ -620,14 +620,27 @@ class SoapClient:
         binding = self.method.binding.input
         transport = self.options.transport
         retxml = self.options.retxml
-        log.debug('sending to (%s)\nmessage:\n%s', location, msg)
+        nosend = self.options.nosend
+        prettyxml = self.options.prettyxml
+        log.debug('sending to (%s)\nmessage:\n%s', location, soapenv)
         try:
-            self.last_sent(Document(msg))
+            self.last_sent(soapenv)
             plugins = PluginContainer(self.options.plugins)
-            plugins.sending(envelope=msg.root())
-            request = Request(location, str(msg))
+            plugins.message.marshalled(envelope=soapenv.root())
+            if prettyxml:
+                soapenv = soapenv.str()
+            else:
+                soapenv = soapenv.plain()
+            soapenv = soapenv.encode('utf-8')
+            ctx = plugins.message.sending(envelope=soapenv)
+            soapenv = ctx.envelope
+            if nosend:
+                return RequestContext(self, binding, soapenv)
+            request = Request(location, soapenv)
             request.headers = self.headers()
             reply = transport.send(request)
+            ctx = plugins.message.received(reply=reply.message)
+            reply.message = ctx.reply
             if retxml:
                 result = reply.message
             else:
@@ -647,6 +660,8 @@ class SoapClient:
         @rtype: dict
         """
         action = self.method.soap.action
+        if isinstance(action, unicode):
+            action = action.encode('utf-8')
         stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action }
         result = dict(stock, **self.options.headers)
         log.debug('headers = %s', result)
@@ -657,26 +672,25 @@ class SoapClient:
         Request succeeded, process the reply
         @param binding: The binding to be used to process the reply.
         @type binding: L{bindings.binding.Binding}
+        @param reply: The raw reply text.
+        @type reply: str
         @return: The method result.
         @rtype: I{builtin}, L{Object}
         @raise WebFault: On server.
         """
         log.debug('http succeeded:\n%s', reply)
         plugins = PluginContainer(self.options.plugins)
-        ctx = plugins.received(reply=reply)
-        reply = ctx.reply
         if len(reply) > 0:
-            r, p = binding.get_reply(self.method, reply)
-            self.last_received(r)
-            if self.options.faults:
-                return p
-            else:
-                return (200, p)
+            reply, result = binding.get_reply(self.method, reply)
+            self.last_received(reply)
         else:
-            if self.options.faults:
-                return None
-            else:
-                return (200, None)
+            result = None
+        ctx = plugins.message.unmarshalled(reply=result)
+        result = ctx.reply
+        if self.options.faults:
+            return result
+        else:
+            return (200, result)
         
     def failed(self, binding, error):
         """
@@ -775,3 +789,52 @@ class SimClient(SoapClient):
             return (500, p)
         else:
             return (500, None)
+        
+
+class RequestContext:
+    """
+    A request context.
+    Returned when the ''nosend'' options is specified.
+    @ivar client: The suds client.
+    @type client: L{Client}
+    @ivar binding: The binding for this request.
+    @type binding: I{Binding}
+    @ivar envelope: The request soap envelope.
+    @type envelope: str
+    """
+    
+    def __init__(self, client, binding, envelope):
+        """
+        @param client: The suds client.
+        @type client: L{Client}
+        @param binding: The binding for this request.
+        @type binding: I{Binding}
+        @param envelope: The request soap envelope.
+        @type envelope: str
+        """
+        self.client = client
+        self.binding = binding
+        self.envelope = envelope
+        
+    def succeeded(self, reply):
+        """
+        Re-entry for processing a successful reply.
+        @param reply: The reply soap envelope.
+        @type reply: str
+        @return: The returned value for the invoked method.
+        @rtype: object 
+        """
+        options = self.client.options
+        plugins = PluginContainer(options.plugins)
+        ctx = plugins.message.received(reply=reply)
+        reply = ctx.reply
+        return self.client.succeeded(self.binding, reply)
+    
+    def failed(self, error):
+        """
+        Re-entry for processing a failure reply.
+        @param error: The error returned by the transport.
+        @type error: A suds I{TransportError}.
+        """
+        return self.client.failed(self.binding, error)
+        
\ No newline at end of file
diff --git a/suds/options.py b/suds/options.py
index 36f1732..dabd770 100644
--- a/suds/options.py
+++ b/suds/options.py
@@ -84,6 +84,10 @@ class Options(Skin):
             of the python object graph.
                 - type: I{bool}
                 - default: False
+        - B{prettyxml} - Flag that causes I{pretty} xml to be rendered when generating
+            the outbound soap envelope.
+                - type: I{bool}
+                - default: False
         - B{autoblend} - Flag that ensures that the schema(s) defined within the
             WSDL import each other.
                 - type: I{bool}
@@ -95,6 +99,11 @@ class Options(Skin):
                 - default: 0
         - B{plugins} - A plugin container.
                 - type: I{list}
+        - B{nosend} - Create the soap envelope but don't send.
+            When specified, method invocation returns a I{RequestContext}
+            instead of sending it.
+                - type: I{bool}
+                - default: False
     """    
     def __init__(self, **kwargs):
         domain = __name__
@@ -111,8 +120,10 @@ class Options(Skin):
             Definition('xstq', bool, True),
             Definition('prefixes', bool, True),
             Definition('retxml', bool, False),
+            Definition('prettyxml', bool, False),
             Definition('autoblend', bool, False),
             Definition('cachingpolicy', int, 0),
-            Definition('plugins', [], (list, tuple)),
+            Definition('plugins', (list, tuple), []),
+            Definition('nosend', bool, False),
         ]
         Skin.__init__(self, domain, definitions, kwargs)
diff --git a/suds/plugin.py b/suds/plugin.py
index ca0826f..061c564 100644
--- a/suds/plugin.py
+++ b/suds/plugin.py
@@ -41,58 +41,90 @@ class InitContext(Context):
     pass
 
 
-class LoadContext(Context):
+class DocumentContext(Context):
     """
-    The XSD load context.
-    @ivar root: The loaded xsd document root.
-    @type root: L{sax.Element}
+    The XML document load context.
+    @ivar url: The URL.
+    @type url: str
+    @ivar document: Either the XML text or the B{parsed} document root.
+    @type document: (str|L{sax.element.Element})
     """
     pass
+
         
-        
-class SendContext(Context):
+class MessageContext(Context):
     """
     The context for sending the soap envelope.
-    @ivar envelope: The soap envelope I{root} element to be sent.
-    @type envelope: L{sax.Element}
+    @ivar envelope: The soap envelope to be sent.
+    @type envelope: (str|L{sax.element.Element})
+    @ivar reply: The reply.
+    @type reply: (str|L{sax.element.Element}|object)
     """
     pass
-        
-        
-class ReplyContext(Context):
+
+
+class Plugin:
     """
-    The context for the text received as a reply
-    to method invocation.
-    @ivar reply: The received text.
-    @type reply: unicode
+    Plugin base.
     """
     pass
-    
 
 
-class Plugin:
+class InitPlugin(Plugin):
     """
-    The base class for suds plugins.
-    All plugins should implement this interface.
+    The base class for suds I{init} plugins.
     """
     
     def initialized(self, context):
         """
-        Suds initialization.
+        Suds client initialization.
         Called after wsdl the has been loaded.  Provides the plugin
         with the opportunity to inspect/modify the WSDL.
         @param context: The init context.
         @type context: L{InitContext}
         """
         pass
+
+
+class DocumentPlugin(Plugin):
+    """
+    The base class for suds I{document} plugins.
+    """
     
-    def loaded(self, context):
+    def loaded(self, context): 
+        """
+        Suds has loaded a WSDL/XSD document.  Provides the plugin 
+        with an opportunity to inspect/modify the unparsed document. 
+        Called after each WSDL/XSD document is loaded. 
+        @param context: The document context. 
+        @type context: L{DocumentContext} 
         """
-        Suds has loaded an XSD document.  Provides the plugin
-        with an opportunity to inspect/modify the loaded XSD.
-        Called after each XSD document is loaded.
-        @param context: The XSD load context.
-        @type context: L{LoadContext}
+        pass 
+    
+    def parsed(self, context):
+        """
+        Suds has parsed a WSDL/XSD document.  Provides the plugin
+        with an opportunity to inspect/modify the parsed document.
+        Called after each WSDL/XSD document is parsed.
+        @param context: The document context.
+        @type context: L{DocumentContext}
+        """
+        pass
+
+
+class MessagePlugin(Plugin):
+    """
+    The base class for suds I{soap message} plugins.
+    """
+    
+    def marshalled(self, context):
+        """
+        Suds will send the specified soap envelope.
+        Provides the plugin with the opportunity to inspect/modify
+        the envelope Document before it is sent.
+        @param context: The send context.
+            The I{envelope} is the envelope docuemnt.
+        @type context: L{MessageContext}
         """
         pass
     
@@ -100,9 +132,10 @@ class Plugin:
         """
         Suds will send the specified soap envelope.
         Provides the plugin with the opportunity to inspect/modify
-        the message before it is sent.
+        the message text it is sent.
         @param context: The send context.
-        @type context: L{SendContext}
+            The I{envelope} is the envelope text.
+        @type context: L{MessageContext}
         """
         pass
     
@@ -110,12 +143,35 @@ class Plugin:
         """
         Suds has received the specified reply.
         Provides the plugin with the opportunity to inspect/modify
-        the received XML.
+        the received XML text before it is SAX parsed.
         @param context: The reply context.
-        @type context: L{ReplyContext}
+            The I{reply} is the raw text.
+        @type context: L{MessageContext}
         """
         pass
-   
+    
+    def parsed(self, context):
+        """
+        Suds has sax parsed the received reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the sax parsed DOM tree for the reply before it is unmarshalled.
+        @param context: The reply context.
+            The I{reply} is DOM tree.
+        @type context: L{MessageContext}
+        """
+        pass
+    
+    def unmarshalled(self, context):
+        """
+        Suds has unmarshalled the received reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the unmarshalled reply object before it is returned.
+        @param context: The reply context.
+            The I{reply} is unmarshalled suds object.
+        @type context: L{MessageContext}
+        """
+        pass
+
     
 class PluginContainer:
     """
@@ -126,11 +182,10 @@ class PluginContainer:
     @type ctxclass: dict
     """
     
-    ctxclass = {\
-        'initialized':InitContext,
-        'loaded':LoadContext,
-        'sending':SendContext,
-        'received':ReplyContext,
+    domains = {\
+        'init': (InitContext, InitPlugin),
+        'document': (DocumentContext, DocumentPlugin),
+        'message': (MessageContext, MessagePlugin ),
     }
     
     def __init__(self, plugins):
@@ -141,11 +196,33 @@ class PluginContainer:
         self.plugins = plugins
     
     def __getattr__(self, name):
-        ctx = self.ctxclass.get(name)
-        if ctx:
-            return Method(name, ctx, self.plugins)
+        domain = self.domains.get(name)
+        if domain:
+            plugins = []
+            ctx, pclass = domain
+            for p in self.plugins:
+                if isinstance(p, pclass):
+                    plugins.append(p)
+            return PluginDomain(ctx, plugins)
         else:
-            raise AttributeError(name)
+            raise Exception, 'plugin domain (%s), invalid' % name
+        
+        
+class PluginDomain:
+    """
+    The plugin domain.
+    @ivar ctx: A context.
+    @type ctx: L{Context}
+    @ivar plugins: A list of plugins (targets).
+    @type plugins: list
+    """
+    
+    def __init__(self, ctx, plugins):
+        self.ctx = ctx
+        self.plugins = plugins
+    
+    def __getattr__(self, name):
+        return Method(name, self)
 
 
 class Method:
@@ -153,32 +230,28 @@ class Method:
     Plugin method.
     @ivar name: The method name.
     @type name: str
-    @ivar ctx: A context.
-    @type ctx: L{Context}
-    @ivar plugins: A list of plugins (targets).
-    @type plugins: list
+    @ivar domain: The plugin domain.
+    @type domain: L{PluginDomain}
     """
 
-    def __init__(self, name, ctx, plugins):
+    def __init__(self, name, domain):
         """
         @param name: The method name.
         @type name: str
-        @param ctx: A context.
-        @type ctx: L{Context}
-        @param plugins: A list of plugins (targets).
-        @type plugins: list
+        @param domain: A plugin domain.
+        @type domain: L{PluginDomain}
         """
         self.name = name
-        self.ctx = ctx()
-        self.plugins = plugins
+        self.domain = domain
             
     def __call__(self, **kwargs):
-        self.ctx.__dict__.update(kwargs)
-        for plugin in self.plugins:
+        ctx = self.domain.ctx()
+        ctx.__dict__.update(kwargs)
+        for plugin in self.domain.plugins:
             try:
                 method = getattr(plugin, self.name, None)
-                if method:
-                    method(self.ctx)
+                if method and callable(method):
+                    method(ctx)
             except Exception, pe:
                 log.exception(pe)
-        return self.ctx
+        return ctx
diff --git a/suds/reader.py b/suds/reader.py
index 0bac400..1184f12 100644
--- a/suds/reader.py
+++ b/suds/reader.py
@@ -23,6 +23,7 @@ from suds.sax.parser import Parser
 from suds.transport import Request
 from suds.cache import Cache, NoCache
 from suds.store import DocumentStore
+from suds.plugin import PluginContainer
 from logging import getLogger
 
 
@@ -42,6 +43,7 @@ class Reader:
         @type options: I{Options}
         """
         self.options = options
+        self.plugins = PluginContainer(options.plugins)
 
     def mangle(self, name, x):
         """
@@ -76,6 +78,7 @@ class DocumentReader(Reader):
         if d is None:
             d = self.download(url)
             cache.put(id, d)
+        self.plugins.document.parsed(url=url, document=d.root())
         return d
     
     def download(self, url):
@@ -90,8 +93,12 @@ class DocumentReader(Reader):
         fp = store.open(url)
         if fp is None:
             fp = self.options.transport.open(Request(url))
+        content = fp.read()
+        fp.close()
+        ctx = self.plugins.document.loaded(url=url, document=content)
+        content = ctx.document 
         sax = Parser()
-        return sax.parse(file=fp)
+        return sax.parse(string=content)
     
     def cache(self):
         """
diff --git a/suds/sax/date.py b/suds/sax/date.py
index b564d9a..6e31c4c 100644
--- a/suds/sax/date.py
+++ b/suds/sax/date.py
@@ -341,7 +341,7 @@ class Timezone:
     @cvar local: The (A) local TZ offset.
     @type local: int
     @cvar patten: The regex patten to match TZ.
-    @type patten: L{re.RegexObject}
+    @type patten: re.Pattern
     """
     
     pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
diff --git a/suds/sax/document.py b/suds/sax/document.py
index c7129cb..5a004eb 100644
--- a/suds/sax/document.py
+++ b/suds/sax/document.py
@@ -27,6 +27,8 @@ log = getLogger(__name__)
 
 class Document(Element):
     """ simple document """
+    
+    DECL = '<?xml version="1.0" encoding="UTF-8"?>'
 
     def __init__(self, root=None):
         Element.__init__(self, 'document')
@@ -34,18 +36,26 @@ class Document(Element):
             self.append(root)
         
     def root(self):
-        if len(self.children) > 0:
+        if len(self.children):
             return self.children[0]
         else:
             return None
         
+    def str(self):
+        s = []
+        s.append(self.DECL)
+        s.append('\n')
+        s.append(self.root().str())
+        return ''.join(s)
+    
+    def plain(self):
+        s = []
+        s.append(self.DECL)
+        s.append(self.root().plain())
+        return ''.join(s)
+
     def __str__(self):
         return unicode(self).encode('utf-8')
     
     def __unicode__(self):
-        result = '<?xml version="1.0" encoding="UTF-8"?>'
-        root = self.root()
-        if root is not None:
-            result += '\n'
-            result += root.str()
-        return unicode(result)
+        return self.str()
\ No newline at end of file
diff --git a/suds/sax/element.py b/suds/sax/element.py
index 0a015f1..9dec1f9 100644
--- a/suds/sax/element.py
+++ b/suds/sax/element.py
@@ -767,6 +767,29 @@ class Element:
         result.append('</%s>' % self.qname())
         result = ''.join(result)
         return result
+    
+    def plain(self):
+        """
+        Get a string representation of this XML fragment.
+        @return: A I{plain} string.
+        @rtype: basestring
+        """
+        result = []
+        result.append('<%s' % self.qname())
+        result.append(self.nsdeclarations())
+        for a in [unicode(a) for a in self.attributes]:
+            result.append(' %s' % a)
+        if self.isempty():
+            result.append('/>')
+            return ''.join(result)
+        result.append('>')
+        if self.hasText():
+            result.append(self.text.escape())
+        for c in self.children:
+            result.append(c.plain())
+        result.append('</%s>' % self.qname())
+        result = ''.join(result)
+        return result
 
     def nsdeclarations(self):
         """
diff --git a/suds/soaparray.py b/suds/soaparray.py
index c7b85e5..04847d5 100644
--- a/suds/soaparray.py
+++ b/suds/soaparray.py
@@ -39,7 +39,10 @@ class Attribute(SXAttribute):
         @type aty: The value of wsdl:arrayType.
         """
         SXAttribute.__init__(self, schema, root)
-        self.aty = aty[:-2]
+        if aty.endswith('[]'):
+            self.aty = aty[:-2]
+        else:
+            self.aty = aty
         
     def autoqualified(self):
         aqs = SXAttribute.autoqualified(self)
diff --git a/suds/wsdl.py b/suds/wsdl.py
index 0400e9b..8bba88f 100644
--- a/suds/wsdl.py
+++ b/suds/wsdl.py
@@ -31,7 +31,6 @@ from suds.xsd.schema import Schema, SchemaCollection
 from suds.xsd.query import ElementQuery
 from suds.sudsobject import Object, Facade, Metadata
 from suds.reader import DocumentReader, DefinitionsReader
-from suds.plugin import PluginContainer
 from urlparse import urljoin
 import re, soaparray
 
@@ -136,8 +135,6 @@ class Definitions(WObject):
         reader = DocumentReader(options)
         d = reader.open(url)
         root = d.root()
-        plugins = PluginContainer(options.plugins)
-        plugins.loaded(root=root)
         WObject.__init__(self, root)
         self.id = objid(self)
         self.options = options
diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py
index 63271dc..d7bbc14 100644
--- a/suds/xsd/doctor.py
+++ b/suds/xsd/doctor.py
@@ -22,7 +22,7 @@ schema(s).
 from logging import getLogger
 from suds.sax import splitPrefix, Namespace
 from suds.sax.element import Element
-from suds.plugin import Plugin
+from suds.plugin import DocumentPlugin, DocumentContext
 
 log = getLogger(__name__)
 
@@ -187,7 +187,7 @@ class Import:
         return 0
     
 
-class ImportDoctor(Doctor, Plugin):
+class ImportDoctor(Doctor, DocumentPlugin):
     """
     Doctor used to fix missing imports.
     @ivar imports: A list of imports to apply.
@@ -208,13 +208,19 @@ class ImportDoctor(Doctor, Plugin):
         """
         self.imports += imports
         
-    def examine(self, root):
+    def examine(self, node):
         for imp in self.imports:
-            imp.apply(root)
+            imp.apply(node)
 
-    def loaded(self, context):
-        root = context.root
-        if Namespace.xsd(root.namespace()):
-            self.examine(root)
-        else:
-            pass
\ No newline at end of file
+    def parsed(self, context):
+        node = context.document
+        # xsd root
+        if node.name == 'schema' and Namespace.xsd(node.namespace()):
+            self.examine(node)
+            return
+        # look deeper
+        context = DocumentContext()
+        for child in node:
+            context.document = child
+            self.parsed(context)
+        
\ No newline at end of file
diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py
index 912fb03..cb7d678 100644
--- a/suds/xsd/schema.py
+++ b/suds/xsd/schema.py
@@ -22,7 +22,7 @@ Most of the I{value-add} provided by the model is centered around
 tranparent referenced type resolution and targeted denormalization.
 """
 
-from logging import getLogger
+
 import suds.metrics
 from suds import *
 from suds.xsd import *
@@ -33,7 +33,7 @@ from suds.xsd.sxbase import SchemaObject
 from suds.xsd.deplist import DepList
 from suds.sax.element import Element
 from suds.sax import splitPrefix, Namespace
-from suds.plugin import PluginContainer
+from logging import getLogger
 
 log = getLogger(__name__)
 
@@ -214,8 +214,6 @@ class Schema:
         self.attributes = {}
         self.groups = {}
         self.agrps = {}
-        plugins = PluginContainer(options.plugins)
-        plugins.loaded(root=root)
         if options.doctor is not None:
             options.doctor.examine(root)
         form = self.root.get('elementFormDefault')
diff --git a/suds/xsd/sxbase.py b/suds/xsd/sxbase.py
index 643a118..2577ffd 100644
--- a/suds/xsd/sxbase.py
+++ b/suds/xsd/sxbase.py
@@ -297,6 +297,12 @@ class SchemaObject(object):
         @rtype: boolean
         """
         return False
+    
+    def mixed(self):
+        """
+        Get whether this I{mixed} content.
+        """
+        return False
         
     def find(self, qref, classes=()):
         """
diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py
index 0b536d2..2506e04 100644
--- a/suds/xsd/sxbasic.py
+++ b/suds/xsd/sxbasic.py
@@ -118,6 +118,12 @@ class Complex(SchemaObject):
             if c.extension():
                 return True
         return False
+    
+    def mixed(self):
+        for c in self.rawchildren:
+            if isinstance(c, SimpleContent) and c.mixed():
+                return True
+        return False
 
 
 class Group(SchemaObject):
@@ -195,6 +201,9 @@ class Simple(SchemaObject):
             if isinstance(child, Enumeration):
                 return True
         return False
+    
+    def mixed(self):
+        return len(self)
 
     def description(self):
         return ('name',)
@@ -340,6 +349,9 @@ class SimpleContent(SchemaObject):
             if c.restriction():
                 return True
         return False
+    
+    def mixed(self):
+        return len(self)
 
 
 class Enumeration(Content):
@@ -470,7 +482,7 @@ class Extension(SchemaObject):
 
     def description(self):
         return ('ref',)
-
+    
 
 class Import(SchemaObject):
     """
diff --git a/tests/axis1.py b/tests/axis1.py
index 2206d02..5c72867 100644
--- a/tests/axis1.py
+++ b/tests/axis1.py
@@ -29,7 +29,7 @@ from suds import WebFault
 from suds.client import Client
 from suds.sudsobject import Object
 from suds.transport.https import HttpAuthenticated
-from suds.plugin import Plugin
+from suds.plugin import *
 
 errors = 0
 
@@ -38,19 +38,43 @@ credentials = dict(username='jortel', password='abc123')
 setup_logging()
 
 
-class TestPlugin(Plugin):
-    
+class MyInitPlugin(InitPlugin):
+
     def initialized(self, context):
-        print 'initialized: ctx=%s' % context.__dict__
-        
+        print 'PLUGIN (init): initialized: ctx=%s' % context.__dict__
+
+    
+class MyDocumentPlugin(DocumentPlugin):
+    
     def loaded(self, context):
-        print 'loaded: ctx=%s' % context.__dict__
+        print 'PLUGIN (document): loaded: ctx=%s' % context.__dict__
+
+    def parsed(self, context):
+        print 'PLUGIN (document): parsed: ctx=%s' % context.__dict__
+
+        
+class MyMessagePlugin(MessagePlugin):
+        
+    def marshalled(self, context):
+        print 'PLUGIN (message): marshalled: ctx=%s' % context.__dict__
     
     def sending(self, context):
-        print 'sending: ctx=%s' % context.__dict__
+        print 'PLUGIN (message): sending: ctx=%s' % context.__dict__
 
     def received(self, context):
-        print 'received: ctx=%s' % context.__dict__
+        print 'PLUGIN (message): received: ctx=%s' % context.__dict__
+        
+    def parsed(self, context):
+        print 'PLUGIN (message): parsed: ctx=%s' % context.__dict__
+        
+    def unmarshalled(self, context):
+        print 'PLUGIN: (massage): unmarshalled: ctx=%s' % context.__dict__
+        
+        
+myplugins = (
+    MyInitPlugin(),
+    MyDocumentPlugin(),
+    MyMessagePlugin(),)
 
 
 #logging.getLogger('suds.client').setLevel(logging.DEBUG)
@@ -64,7 +88,7 @@ try:
     url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl'
     start(url)
     t = HttpAuthenticated(**credentials)
-    client = Client(url, transport=t, cache=None, plugins=[TestPlugin()])
+    client = Client(url, transport=t, cache=None, plugins=myplugins)
     print client
     #
     # create a name object using the wsdl
@@ -113,6 +137,20 @@ try:
     print 'addPersion()'
     result = client.service.addPerson(person)
     print '\nreply(\n%s\n)\n' % str(result)
+    
+    #
+    # Async
+    #
+    client.options.nosend=True
+    reply = '<?xml version="1.0" encoding="utf-8"?><soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><soapenv:Body><ns1:addPersonResponse soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" xmlns:ns1="http://basic.suds.fedora.org"><addPersonReturn xsi:type="xsd:string">person (jeff&#x4D2;,ortel) at age 43 with phone numbers (410-555-5138,919-555-4406,205-777-1212, and pets (Chance,) - added.</addPersonReturn></ns1:addPersonResponse></soapenv:Body></soapenv:Envelope>'
+    request = client.service.addPerson(person)
+    result = request.succeeded(reply)
+    error = Object()
+    error.httpcode = '500'
+    client.options.nosend=False
+#    request.failed(error)
+    
+    #
     #
     # create a new name object used to update the person
     #
commit ad8588241b3c105edf6e29c195074723e0d13257
Author: Daniel Baumann <daniel at debian.org>
Date:   Sat Aug 14 16:26:42 2010 +0200

    Adding upstream version 0.4.

diff --git a/python-suds.spec b/python-suds.spec
index 186ae4d..a9b5a88 100644
--- a/python-suds.spec
+++ b/python-suds.spec
@@ -2,7 +2,7 @@
 
 Summary: A python SOAP client
 Name:  python-suds
-Version: 0.3.9
+Version: 0.4
 Release: 1%{?dist}
 Source0: https://fedorahosted.org/releases/s/u/%{name}/%{name}-%{version}.tar.gz
 License: LGPLv3+
@@ -55,6 +55,9 @@ rm -rf $RPM_BUILD_ROOT
 %doc README LICENSE
 
 %changelog
+* Thu Dec 17 2009 jortel <jortel at redhat.com> - 0.4-1
+- 0.4
+
 * Thu Dec 17 2009 jortel <jortel at redhat.com> - 0.3.9-1
 - Bumped python requires to 2.4
 - Replaced stream-based caching in the transport package with document-based caching.
diff --git a/suds/__init__.py b/suds/__init__.py
index 2a5d4b5..4f4cdcd 100644
--- a/suds/__init__.py
+++ b/suds/__init__.py
@@ -26,8 +26,8 @@ import sys
 # Project properties
 #
 
-__version__ = '0.3.9'
-__build__="GA R659-20100219"
+__version__ = '0.4'
+__build__="(beta) R685-20100513"
 
 #
 # Exceptions
diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py
index a6d421e..ef857df 100644
--- a/suds/bindings/binding.py
+++ b/suds/bindings/binding.py
@@ -22,6 +22,7 @@ from logging import getLogger
 from suds import *
 from suds.sax import Namespace
 from suds.sax.parser import Parser
+from suds.sax.document import Document
 from suds.sax.element import Element
 from suds.sudsobject import Factory, Object
 from suds.mx import Content
@@ -109,8 +110,8 @@ class Binding:
         @type args: list
         @param kwargs: Named (keyword) args for the method invoked.
         @type kwargs: dict
-        @return: The soap message.
-        @rtype: str
+        @return: The soap envelope.
+        @rtype: L{Document}
         """
 
         content = self.headercontent(method)
@@ -123,7 +124,7 @@ class Binding:
             env.promotePrefixes()
         else:
             env.refitPrefixes()
-        return env
+        return Document(env)
     
     def get_reply(self, method, reply):
         """
@@ -144,6 +145,7 @@ class Binding:
         soapenv = replyroot.getChild('Envelope')
         soapenv.promotePrefixes()
         soapbody = soapenv.getChild('Body')
+        self.detect_fault(soapbody)
         soapbody = self.multiref.process(soapbody)
         nodes = self.replycontent(method, soapbody)
         rtypes = self.returned_types(method)
@@ -161,6 +163,23 @@ class Binding:
                 return (replyroot, result)
         return (replyroot, None)
     
+    def detect_fault(self, body):
+        """
+        Detect I{hidden} soapenv:Fault element in the soap body.
+        @param body: The soap envelope body.
+        @type body: L{Element}
+        @raise WebFault: When found.
+        """
+        fault = body.getChild('Fault', envns)
+        if fault is None:
+            return
+        unmarshaller = self.unmarshaller(False)
+        p = unmarshaller.process(fault)
+        if self.options().faults:
+            raise WebFault(p, fault)
+        return self
+        
+    
     def replylist(self, rt, nodes):
         """
         Construct a I{list} reply.  This mehod is called when it has been detected
@@ -206,14 +225,19 @@ class Binding:
                     continue
             resolved = rt.resolve(nobuiltin=True)
             sobject = unmarshaller.process(node, resolved)
-            if rt.unbounded():
-                value = getattr(composite, tag, None)
-                if value is None:
+            value = getattr(composite, tag, None)
+            if value is None:
+                if rt.unbounded():
                     value = []
                     setattr(composite, tag, value)
-                value.append(sobject)
+                    value.append(sobject)
+                else:
+                    setattr(composite, tag, sobject)
             else:
-                setattr(composite, tag, sobject)
+                if not isinstance(value, list):
+                    value = [value,]
+                    setattr(composite, tag, value)
+                value.append(sobject)          
         return composite
     
     def get_fault(self, reply):
diff --git a/suds/cache.py b/suds/cache.py
index d83d049..801c23c 100644
--- a/suds/cache.py
+++ b/suds/cache.py
@@ -19,8 +19,11 @@ Contains basic caching classes.
 """
 
 import os
+import suds
 from tempfile import gettempdir as tmp
 from suds.transport import *
+from suds.sax.parser import Parser
+from suds.sax.element import Element
 from datetime import datetime as dt
 from datetime import timedelta
 from cStringIO import StringIO
@@ -115,8 +118,6 @@ class FileCache(Cache):
     """
     A file-based URL cache.
     @cvar fnprefix: The file name prefix.
-    @type fnprefix: str
-    @ivar fnsuffix: The file name suffix.
     @type fnsuffix: str
     @ivar duration: The cached file duration which defines how
         long the file will be cached.
@@ -125,7 +126,6 @@ class FileCache(Cache):
     @type location: str
     """
     fnprefix = 'suds'
-    fnsuffix = 'gcf'
     units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds')
     
     def __init__(self, location=None, **duration):
@@ -142,6 +142,15 @@ class FileCache(Cache):
         self.location = location
         self.duration = (None, 0)
         self.setduration(**duration)
+        self.checkversion()
+        
+    def fnsuffix(self):
+        """
+        Get the file name suffix
+        @return: The suffix
+        @rtype: str
+        """
+        return 'gcf'
         
     def setduration(self, **duration):
         """
@@ -194,8 +203,9 @@ class FileCache(Cache):
             fn = self.__fn(id)
             f = self.open(fn, 'w')
             f.write(fp.read())
+            fp.close()
             f.close()
-            return fp
+            return open(fn)
         except:
             log.debug(id, exc_info=1)
             return fp
@@ -226,7 +236,7 @@ class FileCache(Cache):
         if self.duration[1] < 1:
             return
         created = dt.fromtimestamp(os.path.getctime(fn))
-        d = {self.duration[0] : self.duration[1]}
+        d = { self.duration[0]:self.duration[1] }
         expired = created+timedelta(**d)
         if expired < dt.now():
             log.debug('%s expired, deleted', fn)
@@ -236,7 +246,7 @@ class FileCache(Cache):
         for fn in os.listdir(self.location):
             if os.path.isdir(fn):
                 continue
-            if fn.startswith(self.fnprefix) and fn.endswith(self.fnsuffix):
+            if fn.startswith(self.fnprefix):
                 log.debug('deleted: %s', fn)
                 os.remove(os.path.join(self.location, fn))
                 
@@ -254,15 +264,50 @@ class FileCache(Cache):
         self.mktmp()
         return open(fn, *args)
     
+    def checkversion(self):
+        path = os.path.join(self.location, 'version')
+        try:
+            
+            f = self.open(path)
+            version = f.read()
+            f.close()
+            if version != suds.__version__:
+                raise Exception()
+        except:
+            self.clear()
+            f = self.open(path, 'w')
+            f.write(suds.__version__)
+            f.close()        
+    
     def __fn(self, id):
-        if hasattr(id, 'name') and hasattr(id, 'suffix'):
-            name = id.name
-            suffix = id.suffix
-        else:
-            name = id
-            suffix = self.fnsuffix
-        fn = '%s-%s.%s' % (self.fnprefix, abs(hash(name)), suffix)
+        name = id
+        suffix = self.fnsuffix()
+        fn = '%s-%s.%s' % (self.fnprefix, name, suffix)
         return os.path.join(self.location, fn)
+    
+    
+class DocumentCache(FileCache):
+    """
+    Provides xml document caching.
+    """
+    
+    def fnsuffix(self):
+        return 'xml'
+    
+    def get(self, id):
+        try:
+            fp = FileCache.getf(self, id)
+            if fp is None:
+                return None
+            p = Parser()
+            return p.parse(fp)
+        except:
+            FileCache.purge(self, id)
+    
+    def put(self, id, object):
+        if isinstance(object, Element):
+            FileCache.put(self, id, str(object))
+        return object
 
 
 class ObjectCache(FileCache):
@@ -273,6 +318,9 @@ class ObjectCache(FileCache):
     """
     protocol = 2
     
+    def fnsuffix(self):
+        return 'px'
+    
     def get(self, id):
         try:
             fp = FileCache.getf(self, id)
diff --git a/suds/client.py b/suds/client.py
index b91a7da..0bdde6f 100644
--- a/suds/client.py
+++ b/suds/client.py
@@ -40,6 +40,7 @@ from suds.options import Options
 from suds.properties import Unskin
 from urlparse import urlparse
 from copy import deepcopy
+from suds.plugin import PluginContainer
 from logging import getLogger
 
 log = getLogger(__name__)
@@ -109,6 +110,8 @@ class Client(object):
         self.set_options(**kwargs)
         reader = DefinitionsReader(options, Definitions)
         self.wsdl = reader.open(url)
+        plugins = PluginContainer(options.plugins)
+        plugins.initialized(wsdl=self.wsdl)
         self.factory = Factory(self.wsdl)
         self.service = ServiceSelector(self, self.wsdl.services)
         self.sd = []
@@ -593,13 +596,15 @@ class SoapClient:
         timer.stop()
         metrics.log.debug(
                 "message for '%s' created: %s",
-                self.method.name, timer)
+                self.method.name,
+                timer)
         timer.start()
         result = self.send(msg)
         timer.stop()
         metrics.log.debug(
                 "method '%s' invoked: %s",
-                self.method.name, timer)
+                self.method.name,
+                timer)
         return result
     
     def send(self, msg):
@@ -618,6 +623,8 @@ class SoapClient:
         log.debug('sending to (%s)\nmessage:\n%s', location, msg)
         try:
             self.last_sent(Document(msg))
+            plugins = PluginContainer(self.options.plugins)
+            plugins.sending(envelope=msg.root())
             request = Request(location, str(msg))
             request.headers = self.headers()
             reply = transport.send(request)
@@ -640,7 +647,7 @@ class SoapClient:
         @rtype: dict
         """
         action = self.method.soap.action
-        stock = { 'Content-Type' : 'text/xml', 'SOAPAction': action }
+        stock = { 'Content-Type' : 'text/xml; charset=utf-8', 'SOAPAction': action }
         result = dict(stock, **self.options.headers)
         log.debug('headers = %s', result)
         return result
@@ -655,6 +662,9 @@ class SoapClient:
         @raise WebFault: On server.
         """
         log.debug('http succeeded:\n%s', reply)
+        plugins = PluginContainer(self.options.plugins)
+        ctx = plugins.received(reply=reply)
+        reply = ctx.reply
         if len(reply) > 0:
             r, p = binding.get_reply(self.method, reply)
             self.last_received(r)
diff --git a/suds/mx/appender.py b/suds/mx/appender.py
index 0501415..206abc0 100644
--- a/suds/mx/appender.py
+++ b/suds/mx/appender.py
@@ -191,7 +191,7 @@ class PrimativeAppender(Appender):
         if content.tag.startswith('_'):
             attr = content.tag[1:]
             value = tostr(content.value)
-            if value is not None and len(value):
+            if value:
                 parent.set(attr, value)
         else:
             child = self.node(content)
@@ -305,6 +305,12 @@ class TextAppender(Appender):
     """
 
     def append(self, parent, content):
-        child = self.node(content)
-        child.setText(content.value)
-        parent.append(child)
+        if content.tag.startswith('_'):
+            attr = content.tag[1:]
+            value = tostr(content.value)
+            if value:
+                parent.set(attr, value)
+        else:
+            child = self.node(content)
+            child.setText(content.value)
+            parent.append(child)
diff --git a/suds/options.py b/suds/options.py
index 7e5c069..36f1732 100644
--- a/suds/options.py
+++ b/suds/options.py
@@ -85,9 +85,16 @@ class Options(Skin):
                 - type: I{bool}
                 - default: False
         - B{autoblend} - Flag that ensures that the schema(s) defined within the
-            WSDL import each other.  B{**Experimental**}.
+            WSDL import each other.
                 - type: I{bool}
                 - default: False
+        - B{cachingpolicy} - The caching policy.
+                - type: I{int}
+                  - 0 = Cache XML documents.
+                  - 1 = Cache WSDL (pickled) object.
+                - default: 0
+        - B{plugins} - A plugin container.
+                - type: I{list}
     """    
     def __init__(self, **kwargs):
         domain = __name__
@@ -105,5 +112,7 @@ class Options(Skin):
             Definition('prefixes', bool, True),
             Definition('retxml', bool, False),
             Definition('autoblend', bool, False),
+            Definition('cachingpolicy', int, 0),
+            Definition('plugins', [], (list, tuple)),
         ]
         Skin.__init__(self, domain, definitions, kwargs)
diff --git a/suds/plugin.py b/suds/plugin.py
new file mode 100644
index 0000000..ca0826f
--- /dev/null
+++ b/suds/plugin.py
@@ -0,0 +1,184 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The plugin module provides classes for implementation
+of suds plugins.
+"""
+
+from suds import *
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+class Context(object):
+    """
+    Plugin context.
+    """
+    pass
+
+
+class InitContext(Context):
+    """
+    Init Context.
+    @ivar wsdl: The wsdl.
+    @type wsdl: L{wsdl.Definitions}
+    """
+    pass
+
+
+class LoadContext(Context):
+    """
+    The XSD load context.
+    @ivar root: The loaded xsd document root.
+    @type root: L{sax.Element}
+    """
+    pass
+        
+        
+class SendContext(Context):
+    """
+    The context for sending the soap envelope.
+    @ivar envelope: The soap envelope I{root} element to be sent.
+    @type envelope: L{sax.Element}
+    """
+    pass
+        
+        
+class ReplyContext(Context):
+    """
+    The context for the text received as a reply
+    to method invocation.
+    @ivar reply: The received text.
+    @type reply: unicode
+    """
+    pass
+    
+
+
+class Plugin:
+    """
+    The base class for suds plugins.
+    All plugins should implement this interface.
+    """
+    
+    def initialized(self, context):
+        """
+        Suds initialization.
+        Called after wsdl the has been loaded.  Provides the plugin
+        with the opportunity to inspect/modify the WSDL.
+        @param context: The init context.
+        @type context: L{InitContext}
+        """
+        pass
+    
+    def loaded(self, context):
+        """
+        Suds has loaded an XSD document.  Provides the plugin
+        with an opportunity to inspect/modify the loaded XSD.
+        Called after each XSD document is loaded.
+        @param context: The XSD load context.
+        @type context: L{LoadContext}
+        """
+        pass
+    
+    def sending(self, context):
+        """
+        Suds will send the specified soap envelope.
+        Provides the plugin with the opportunity to inspect/modify
+        the message before it is sent.
+        @param context: The send context.
+        @type context: L{SendContext}
+        """
+        pass
+    
+    def received(self, context):
+        """
+        Suds has received the specified reply.
+        Provides the plugin with the opportunity to inspect/modify
+        the received XML.
+        @param context: The reply context.
+        @type context: L{ReplyContext}
+        """
+        pass
+   
+    
+class PluginContainer:
+    """
+    Plugin container provides easy method invocation.
+    @ivar plugins: A list of plugin objects.
+    @type plugins: [L{Plugin},]
+    @cvar ctxclass: A dict of plugin method / context classes.
+    @type ctxclass: dict
+    """
+    
+    ctxclass = {\
+        'initialized':InitContext,
+        'loaded':LoadContext,
+        'sending':SendContext,
+        'received':ReplyContext,
+    }
+    
+    def __init__(self, plugins):
+        """
+        @param plugins: A list of plugin objects.
+        @type plugins: [L{Plugin},]
+        """
+        self.plugins = plugins
+    
+    def __getattr__(self, name):
+        ctx = self.ctxclass.get(name)
+        if ctx:
+            return Method(name, ctx, self.plugins)
+        else:
+            raise AttributeError(name)
+
+
+class Method:
+    """
+    Plugin method.
+    @ivar name: The method name.
+    @type name: str
+    @ivar ctx: A context.
+    @type ctx: L{Context}
+    @ivar plugins: A list of plugins (targets).
+    @type plugins: list
+    """
+
+    def __init__(self, name, ctx, plugins):
+        """
+        @param name: The method name.
+        @type name: str
+        @param ctx: A context.
+        @type ctx: L{Context}
+        @param plugins: A list of plugins (targets).
+        @type plugins: list
+        """
+        self.name = name
+        self.ctx = ctx()
+        self.plugins = plugins
+            
+    def __call__(self, **kwargs):
+        self.ctx.__dict__.update(kwargs)
+        for plugin in self.plugins:
+            try:
+                method = getattr(plugin, self.name, None)
+                if method:
+                    method(self.ctx)
+            except Exception, pe:
+                log.exception(pe)
+        return self.ctx
diff --git a/suds/reader.py b/suds/reader.py
index 84e6822..0bac400 100644
--- a/suds/reader.py
+++ b/suds/reader.py
@@ -21,6 +21,7 @@ Contains xml document reader classes.
 
 from suds.sax.parser import Parser
 from suds.transport import Request
+from suds.cache import Cache, NoCache
 from suds.store import DocumentStore
 from logging import getLogger
 
@@ -28,31 +29,34 @@ from logging import getLogger
 log = getLogger(__name__)
 
 
-class ObjectId(object):
-    
-    def __init__(self, name, suffix):
-        self.name = name
-        self.suffix = suffix
-
-
-class DocumentReader:
+class Reader:
     """
-    The XML document reader provides an integration
-    between the SAX L{Parser} and the document cache.
-    @cvar suffix: The cache file suffix.
-    @type suffix: str
+    The reader provides integration with cache.
     @ivar options: An options object.
     @type options: I{Options}
     """
-    
-    suffix = 'pxd'
-    
+
     def __init__(self, options):
         """
         @param options: An options object.
         @type options: I{Options}
         """
         self.options = options
+
+    def mangle(self, name, x):
+        """
+        Mangle the name by hashing the I{name} and appending I{x}.
+        @return: the mangled name.
+        """
+        h = abs(hash(name))
+        return '%s-%s' % (h, x)
+
+
+class DocumentReader(Reader):
+    """
+    The XML document reader provides an integration
+    between the SAX L{Parser} and the document cache.
+    """
     
     def open(self, url):
         """
@@ -66,8 +70,8 @@ class DocumentReader:
         @return: The specified XML document.
         @rtype: I{Document}
         """
-        id = ObjectId(url, self.suffix)
-        cache = self.options.cache
+        cache = self.cache()
+        id = self.mangle(url, 'document')
         d = cache.get(id)
         if d is None:
             d = self.download(url)
@@ -88,23 +92,28 @@ class DocumentReader:
             fp = self.options.transport.open(Request(url))
         sax = Parser()
         return sax.parse(file=fp)
+    
+    def cache(self):
+        """
+        Get the cache.
+        @return: The I{options} when I{cachingpolicy} = B{0}.
+        @rtype: L{Cache}
+        """
+        if self.options.cachingpolicy == 0:
+            return self.options.cache
+        else:
+            return NoCache()
 
 
-class DefinitionsReader:
+class DefinitionsReader(Reader):
     """
     The WSDL definitions reader provides an integration
     between the Definitions and the object cache.
-    @cvar suffix: The cache file suffix.
-    @type suffix: str
-    @ivar options: An options object.
-    @type options: I{Options}
     @ivar fn: A factory function (constructor) used to
         create the object not found in the cache.
     @type fn: I{Constructor}
     """
     
-    suffix = 'pw'
-    
     def __init__(self, options, fn):
         """
         @param options: An options object.
@@ -113,7 +122,7 @@ class DefinitionsReader:
             create the object not found in the cache.
         @type fn: I{Constructor}
         """
-        self.options = options
+        Reader.__init__(self, options)
         self.fn = fn
     
     def open(self, url):
@@ -129,8 +138,8 @@ class DefinitionsReader:
         @return: The WSDL object.
         @rtype: I{Definitions}
         """
-        id = ObjectId(url, self.suffix)
-        cache = self.options.cache
+        cache = self.cache()
+        id = self.mangle(url, 'wsdl')
         d = cache.get(id)
         if d is None:
             d = self.fn(url, self.options)
@@ -140,3 +149,14 @@ class DefinitionsReader:
             for imp in d.imports:
                 imp.imported.options = self.options
         return d
+
+    def cache(self):
+        """
+        Get the cache.
+        @return: The I{options} when I{cachingpolicy} = B{1}.
+        @rtype: L{Cache}
+        """
+        if self.options.cachingpolicy == 1:
+            return self.options.cache
+        else:
+            return NoCache()
\ No newline at end of file
diff --git a/suds/sax/__init__.py b/suds/sax/__init__.py
index b4d343e..3d71432 100644
--- a/suds/sax/__init__.py
+++ b/suds/sax/__init__.py
@@ -69,6 +69,10 @@ class Namespace:
         return (p, u)
     
     @classmethod
+    def none(cls, ns):
+        return ( ns == cls.default )
+    
+    @classmethod
     def xsd(cls, ns):
         try:
             return cls.w3(ns) and ns[1].endswith('XMLSchema')
diff --git a/suds/sax/date.py b/suds/sax/date.py
index f07cf84..b564d9a 100644
--- a/suds/sax/date.py
+++ b/suds/sax/date.py
@@ -120,6 +120,8 @@ class Time:
         - HH:MI:SS.ms(z|Z)
         - HH:MI:SS(+|-)06:00
         - HH:MI:SS.ms(+|-)06:00
+    @ivar tz: The timezone
+    @type tz: L{Timezone}
     @ivar date: The object value.
     @type date: B{datetime}.I{time}
     """
@@ -132,6 +134,7 @@ class Time:
         @type adjusted: boolean
         @raise ValueError: When I{time} is invalid.
         """
+        self.tz = Timezone()
         if isinstance(time, dt.time):
             self.time = time
             return
@@ -180,8 +183,7 @@ class Time:
         """
         if hasattr(self, 'offset'):
             today = dt.date.today()
-            tz = Timezone()
-            delta = Timezone.adjustment(self.offset)
+            delta = self.tz.adjustment(self.offset)
             d = dt.datetime.combine(today, self.time)
             d = ( d + delta )
             self.time = d.time()
@@ -245,7 +247,7 @@ class Time:
         if len(s) == len('-00:00'):
             return int(s[:3])
         if len(s) == 0:
-            return Timezone.local
+            return self.tz.local
         if len(s) == 1:
             return 0
         raise Exception()
@@ -255,7 +257,10 @@ class Time:
     
     def __unicode__(self):
         time = self.time.isoformat()
-        return '%s%+.2d:00' % (time, Timezone.local)
+        if self.tz.local:
+            return '%s%+.2d:00' % (time, self.tz.local)
+        else:
+            return '%sZ' % time
 
 
 class DateTime(Date,Time):
@@ -299,8 +304,7 @@ class DateTime(Date,Time):
         """
         if not hasattr(self, 'offset'):
             return
-        tz = Timezone()
-        delta = Timezone.adjustment(self.offset)
+        delta = self.tz.adjustment(self.offset)
         try:
             d = ( self.datetime + delta )
             self.datetime = d
@@ -319,6 +323,18 @@ class DateTime(Date,Time):
         return 'T'.join(s)
     
     
+class UTC(DateTime):
+    """
+    Represents current UTC time.
+    """
+    
+    def __init__(self, date=None):
+        if date is None:
+            date = dt.datetime.utcnow()
+        DateTime.__init__(self, date)
+        self.tz.local = 0
+    
+    
 class Timezone:
     """
     Timezone object used to do TZ conversions
@@ -327,10 +343,16 @@ class Timezone:
     @cvar patten: The regex patten to match TZ.
     @type patten: L{re.RegexObject}
     """
-
-    local = ( 0-time.timezone/60/60 )
+    
     pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
     
+    LOCAL = ( 0-time.timezone/60/60 )
+
+    def __init__(self, offset=None):
+        if offset is None:
+            offset = self.LOCAL
+        self.local = offset
+    
     @classmethod
     def split(cls, s):
         """
@@ -345,13 +367,12 @@ class Timezone:
             return (s,)
         x = m.start(0)
         return (s[:x], s[x:])
-    
-    @classmethod
-    def adjustment(cls, offset):
+
+    def adjustment(self, offset):
         """
         Get the adjustment to the I{local} TZ.
         @return: The delta between I{offset} and local TZ.
         @rtype: B{datetime}.I{timedelta}
         """
-        delta = ( cls.local - offset )
+        delta = ( self.local - offset )
         return dt.timedelta(hours=delta)
diff --git a/suds/sax/element.py b/suds/sax/element.py
index ca44801..0a015f1 100644
--- a/suds/sax/element.py
+++ b/suds/sax/element.py
@@ -923,6 +923,41 @@ class Element:
     
     def __unicode__(self):
         return self.str()
+    
+    def __iter__(self):
+        return NodeIterator(self)
+    
+
+class NodeIterator:
+    """
+    The L{Element} child node iterator.
+    @ivar pos: The current position
+    @type pos: int
+    @ivar children: A list of a child nodes.
+    @type children: [L{Element},..] 
+    """
+    
+    def __init__(self, parent):
+        """
+        @param parent: An element to iterate.
+        @type parent: L{Element}
+        """
+        self.pos = 0
+        self.children = parent.children
+        
+    def next(self):
+        """
+        Get the next child.
+        @return: The next child.
+        @rtype: L{Element}
+        @raise StopIterator: At the end.
+        """
+        try:
+            child = self.children[self.pos]
+            self.pos += 1
+            return child
+        except:
+            raise StopIteration()
 
 
 class PrefixNormalizer:
diff --git a/suds/umx/core.py b/suds/umx/core.py
index 7fb4ac4..07d33c4 100644
--- a/suds/umx/core.py
+++ b/suds/umx/core.py
@@ -135,7 +135,7 @@ class Core:
         @param content: The current content being unmarshalled.
         @type content: L{Content}
         """
-        for child in content.node.children:
+        for child in content.node:
             cont = Content(child)
             cval = self.append(cont)
             key = reserved.get(child.name, child.name)
diff --git a/suds/wsdl.py b/suds/wsdl.py
index 26f15b5..0400e9b 100644
--- a/suds/wsdl.py
+++ b/suds/wsdl.py
@@ -31,6 +31,7 @@ from suds.xsd.schema import Schema, SchemaCollection
 from suds.xsd.query import ElementQuery
 from suds.sudsobject import Object, Facade, Metadata
 from suds.reader import DocumentReader, DefinitionsReader
+from suds.plugin import PluginContainer
 from urlparse import urljoin
 import re, soaparray
 
@@ -135,6 +136,8 @@ class Definitions(WObject):
         reader = DocumentReader(options)
         d = reader.open(url)
         root = d.root()
+        plugins = PluginContainer(options.plugins)
+        plugins.loaded(root=root)
         WObject.__init__(self, root)
         self.id = objid(self)
         self.options = options
@@ -310,8 +313,8 @@ class Import(WObject):
         log.debug('importing (%s)', url)
         if '://' not in url:
             url = urljoin(definitions.url, url)
-        reader = DefinitionsReader(definitions.options, Definitions)
-        d = reader.open(url)
+        options = definitions.options
+        d = Definitions(url, options)
         if d.root.match(Definitions.Tag, wsdlns):
             self.import_definitions(definitions, d)
             return
diff --git a/suds/wsse.py b/suds/wsse.py
index 0fc8301..2a697c1 100644
--- a/suds/wsse.py
+++ b/suds/wsse.py
@@ -22,6 +22,7 @@ from logging import getLogger
 from suds import *
 from suds.sudsobject import Object
 from suds.sax.element import Element
+from suds.sax.date import UTC
 from datetime import datetime, timedelta
 
 try:
@@ -88,8 +89,13 @@ class Token(Object):
         return datetime.now()
     
     @classmethod
+    def utc(cls):
+        return datetime.utcnow()
+    
+    @classmethod
     def sysdate(cls):
-        return cls.now().isoformat()
+        utc = UTC()
+        return str(utc)
     
     def __init__(self):
             Object.__init__(self)
@@ -144,11 +150,11 @@ class UsernameToken(Token):
         """
         Set I{created}.
         @param dt: The created date & time.
-            Set as datetime.now() when I{None}.
+            Set as datetime.utc() when I{None}.
         @type dt: L{datetime}
         """
         if dt is None:
-            self.created = Token.now()
+            self.created = Token.utc()
         else:
             self.created = dt
         
@@ -172,7 +178,7 @@ class UsernameToken(Token):
             root.append(n)
         if self.created is not None:
             n = Element('Created', ns=wsuns)
-            n.setText(self.created.isoformat())
+            n.setText(str(UTC(self.created)))
             root.append(n)
         return root
 
@@ -192,15 +198,15 @@ class Timestamp(Token):
         @type validity: int
         """
         Token.__init__(self)
-        self.created = Token.now()
+        self.created = Token.utc()
         self.expires = self.created + timedelta(seconds=validity)
         
     def xml(self):
         root = Element("Timestamp", ns=wsuns)
         created = Element('Created', ns=wsuns)
-        created.setText(self.created.isoformat())
+        created.setText(str(UTC(self.created)))
         expires = Element('Expires', ns=wsuns)
-        expires.setText(self.expires.isoformat())
+        expires.setText(str(UTC(self.expires)))
         root.append(created)
         root.append(expires)
         return root
\ No newline at end of file
diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py
index 84c9151..63271dc 100644
--- a/suds/xsd/doctor.py
+++ b/suds/xsd/doctor.py
@@ -22,6 +22,7 @@ schema(s).
 from logging import getLogger
 from suds.sax import splitPrefix, Namespace
 from suds.sax.element import Element
+from suds.plugin import Plugin
 
 log = getLogger(__name__)
 
@@ -186,7 +187,7 @@ class Import:
         return 0
     
 
-class ImportDoctor(Doctor):
+class ImportDoctor(Doctor, Plugin):
     """
     Doctor used to fix missing imports.
     @ivar imports: A list of imports to apply.
@@ -210,3 +211,10 @@ class ImportDoctor(Doctor):
     def examine(self, root):
         for imp in self.imports:
             imp.apply(root)
+
+    def loaded(self, context):
+        root = context.root
+        if Namespace.xsd(root.namespace()):
+            self.examine(root)
+        else:
+            pass
\ No newline at end of file
diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py
index a22f1c5..912fb03 100644
--- a/suds/xsd/schema.py
+++ b/suds/xsd/schema.py
@@ -33,6 +33,7 @@ from suds.xsd.sxbase import SchemaObject
 from suds.xsd.deplist import DepList
 from suds.sax.element import Element
 from suds.sax import splitPrefix, Namespace
+from suds.plugin import PluginContainer
 
 log = getLogger(__name__)
 
@@ -166,16 +167,22 @@ class Schema:
     @type baseurl: str
     @ivar container: A schema collection containing this schema.
     @type container: L{SchemaCollection}
-    @ivar types: A schema types cache.
-    @type types: {name:L{SchemaObject}}
-    @ivar groups: A schema groups cache.
-    @type groups: {name:L{SchemaObject}}
     @ivar children: A list of direct top level children.
     @type children: [L{SchemaObject},...]
     @ivar all: A list of all (includes imported) top level children.
     @type all: [L{SchemaObject},...]
+    @ivar types: A schema types cache.
+    @type types: {name:L{SchemaObject}}
     @ivar imports: A list of import objects.
     @type imports: [L{SchemaObject},...]
+    @ivar elements: A list of <element/> objects.
+    @type elements: [L{SchemaObject},...]
+    @ivar attributes: A list of <attribute/> objects.
+    @type attributes: [L{SchemaObject},...]
+    @ivar groups: A list of group objects.
+    @type groups: [L{SchemaObject},...]
+    @ivar agrps: A list of attribute group objects.
+    @type agrps: [L{SchemaObject},...]
     @ivar form_qualified: The flag indicating:
         (@elementFormDefault).
     @type form_qualified: bool
@@ -207,6 +214,8 @@ class Schema:
         self.attributes = {}
         self.groups = {}
         self.agrps = {}
+        plugins = PluginContainer(options.plugins)
+        plugins.loaded(root=root)
         if options.doctor is not None:
             options.doctor.examine(root)
         form = self.root.get('elementFormDefault')
diff --git a/suds/xsd/sxbase.py b/suds/xsd/sxbase.py
index c03f525..643a118 100644
--- a/suds/xsd/sxbase.py
+++ b/suds/xsd/sxbase.py
@@ -23,6 +23,7 @@ from logging import getLogger
 from suds import *
 from suds.xsd import *
 from suds.sax.element import Element
+from suds.sax import Namespace
 
 log = getLogger(__name__)
 
@@ -220,6 +221,14 @@ class SchemaObject(object):
     def sequence(self):
         """
         Get whether this is an <xs:sequence/>
+        @return: True if <xs:sequence/>, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def xslist(self):
+        """
+        Get whether this is an <xs:list/>
         @return: True if any, else False
         @rtype: boolean
         """
@@ -346,9 +355,14 @@ class SchemaObject(object):
     def qualify(self):
         """
         Convert attribute values, that are references to other
-        objects, into I{qref}.
+        objects, into I{qref}.  Qualfied using default document namespace.
+        Since many wsdls are written improperly: when the document does
+        not define a default namespace, the schema target namespace is used
+        to qualify references.
         """
         defns = self.root.defaultNamespace()
+        if Namespace.none(defns):
+            defns = self.schema.tns
         for a in self.autoqualified():
             ref = getattr(self, a)
             if ref is None:
diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py
index 4c7e8a4..0b536d2 100644
--- a/suds/xsd/sxbasic.py
+++ b/suds/xsd/sxbasic.py
@@ -188,7 +188,7 @@ class Simple(SchemaObject):
     """
 
     def childtags(self):
-        return ('restriction', 'any',)
+        return ('restriction', 'any', 'list',)
     
     def enum(self):
         for child, ancestry in self.children():
@@ -210,6 +210,21 @@ class Simple(SchemaObject):
             if c.restriction():
                 return True
         return False
+    
+
+class List(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:list/> node
+    """
+
+    def childtags(self):
+        return ()
+
+    def description(self):
+        return ('name',)
+    
+    def xslist(self):
+        return True
 
    
 class Restriction(SchemaObject):
@@ -687,7 +702,8 @@ class Factory:
         'complexType' : Complex,
         'group' : Group,
         'attributeGroup' : AttributeGroup, 
-        'simpleType' : Simple, 
+        'simpleType' : Simple,
+        'list' : List,
         'element' : Element,
         'attribute' : Attribute,
         'sequence' : Sequence,
diff --git a/tests/axis1.py b/tests/axis1.py
index d06823d..2206d02 100644
--- a/tests/axis1.py
+++ b/tests/axis1.py
@@ -29,6 +29,7 @@ from suds import WebFault
 from suds.client import Client
 from suds.sudsobject import Object
 from suds.transport.https import HttpAuthenticated
+from suds.plugin import Plugin
 
 errors = 0
 
@@ -37,6 +38,21 @@ credentials = dict(username='jortel', password='abc123')
 setup_logging()
 
 
+class TestPlugin(Plugin):
+    
+    def initialized(self, context):
+        print 'initialized: ctx=%s' % context.__dict__
+        
+    def loaded(self, context):
+        print 'loaded: ctx=%s' % context.__dict__
+    
+    def sending(self, context):
+        print 'sending: ctx=%s' % context.__dict__
+
+    def received(self, context):
+        print 'received: ctx=%s' % context.__dict__
+
+
 #logging.getLogger('suds.client').setLevel(logging.DEBUG)
 
 def start(url):
@@ -48,7 +64,7 @@ try:
     url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl'
     start(url)
     t = HttpAuthenticated(**credentials)
-    client = Client(url, transport=t, cache=None)
+    client = Client(url, transport=t, cache=None, plugins=[TestPlugin()])
     print client
     #
     # create a name object using the wsdl
diff --git a/tests/builtin.py b/tests/builtin.py
index 31ca3c3..ca6c2e6 100644
--- a/tests/builtin.py
+++ b/tests/builtin.py
@@ -16,6 +16,7 @@
 
 import sys
 sys.path.append('../')
+import unittest
 from suds.sax.date import Timezone as Tz
 from suds.xsd.sxbuiltin import *
 from unittest import TestCase
@@ -50,7 +51,7 @@ class DateTest(TestCase):
         self.equalsTimezone(6)
         
     def testUtcTimezone(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.date(1941, 12, 7)
         s = '%.4d-%.2d-%.2dZ' % (ref.year, ref.month, ref.day)
         xdate = Date()
@@ -58,7 +59,7 @@ class DateTest(TestCase):
         self.assertEqual(d, ref)
         
     def equalsTimezone(self, tz):
-        Timezone.local = tz
+        Timezone.LOCAL = tz
         ref = dt.date(1941, 12, 7)
         s = '%.4d-%.2d-%.2d%+.2d:00' % (ref.year, ref.month, ref.day, tz)
         xdate = Date()
@@ -104,7 +105,7 @@ class TimeTest(TestCase):
         self.equalsTimezone(-6)
         
     def testUtcTimezone(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.time(10, 30, 22)
         s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
         xtime = Time()
@@ -112,7 +113,7 @@ class TimeTest(TestCase):
         self.assertEqual(t, ref)
         
     def equalsTimezone(self, tz):
-        Timezone.local = tz
+        Timezone.LOCAL = tz
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, tz)
         xtime = Time()
@@ -120,7 +121,7 @@ class TimeTest(TestCase):
         self.assertEqual(t, ref)
         
     def testConvertNegativeToGreaterNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, -5)
         xtime = Time()
@@ -130,7 +131,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToLesserNegative(self):
-        Timezone.local = -5
+        Timezone.LOCAL = -5
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, -6)
         xtime = Time()
@@ -140,7 +141,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToGreaterPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, 2)
         xtime = Time()
@@ -150,7 +151,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToLesserPositive(self):
-        Timezone.local = 2
+        Timezone.LOCAL = 2
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, 3)
         xtime = Time()
@@ -160,7 +161,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, 3)
         xtime = Time()
@@ -170,7 +171,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, -6)
         xtime = Time()
@@ -180,7 +181,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToUtc(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, -6)
         xtime = Time()
@@ -190,7 +191,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToUtc(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.time(10, 30, 22)
         s = self.strTime(ref.hour, ref.minute, ref.second, 3)
         xtime = Time()
@@ -200,7 +201,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertUtcToPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.time(10, 30, 22)
         s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
         xtime = Time()
@@ -210,7 +211,7 @@ class TimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertUtcToNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.time(10, 30, 22)
         s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
         xtime = Time()
@@ -272,7 +273,7 @@ class DateTimeTest(TestCase):
         self.equalsTimezone(-6)
         
     def testUtcTimezone(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d' \
             % (ref.year,
@@ -286,7 +287,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(t, ref)
         
     def equalsTimezone(self, tz):
-        Timezone.local = tz
+        Timezone.LOCAL = tz
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -301,7 +302,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(t, ref)
         
     def testConvertNegativeToGreaterNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -321,7 +322,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToLesserNegative(self):
-        Timezone.local = -5
+        Timezone.LOCAL = -5
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -341,7 +342,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToGreaterPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -361,7 +362,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToLesserPositive(self):
-        Timezone.local = 2
+        Timezone.LOCAL = 2
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -381,7 +382,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -401,7 +402,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -421,7 +422,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToUtc(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -441,7 +442,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertPositiveToUtc(self):
-        Timezone.local = 0
+        Timezone.LOCAL = 0
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -461,7 +462,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertUtcToPositive(self):
-        Timezone.local = 3
+        Timezone.LOCAL = 3
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
             % (ref.year,
@@ -480,7 +481,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertUtcToNegative(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.datetime(1941, 12, 7, 10, 30, 22)
         s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
             % (ref.year,
@@ -499,7 +500,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToGreaterNegativeAndPreviousDay(self):
-        Timezone.local = -6
+        Timezone.LOCAL = -6
         ref = dt.datetime(1941, 12, 7, 0, 30, 22)
         s = self.strDateTime(
                 ref.year,
@@ -519,7 +520,7 @@ class DateTimeTest(TestCase):
         self.assertEqual(ref.second, t.second)
         
     def testConvertNegativeToLesserNegativeAndNextDay(self):
-        Timezone.local = -5
+        Timezone.LOCAL = -5
         ref = dt.datetime(1941, 12, 7, 23, 30, 22)
         s = self.strDateTime(
                 ref.year,
diff --git a/tests/public.py b/tests/public.py
index ca39cc5..5132c7f 100644
--- a/tests/public.py
+++ b/tests/public.py
@@ -101,11 +101,11 @@ try:
     url = 'http://jira.atlassian.com/rpc/soap/jirasoapservice-v2?wsdl'
     start(url+'  ** cloned **')
     client = Client(url).clone()
-    print client
+    print '**clone**\n%s' % client
     token = client.service.login('soaptester', 'soaptester')
-    print 'token="%s"' % token
+    print '**clone** token="%s"' % token
     user = client.service.getUser(token, 'soaptester')
-    print 'user="%s"' % user
+    print '**clone** user="%s"' % user
 except WebFault, f:
     errors += 1
     print f
diff --git a/tests/rhq.py b/tests/rhq.py
index 9c87d0b..2b755d1 100644
--- a/tests/rhq.py
+++ b/tests/rhq.py
@@ -28,40 +28,34 @@ import suds.metrics as metrics
 from tests import *
 from suds import null, WebFault
 from suds.client import Client
-from suds.xsd.sxbasic import Import
-from suds.transport.https import HttpAuthenticated
-from suds.transport.cache import FileCache
+
 
 errors = 0
 
 setup_logging()
 
-Import.bind('http://schemas.xmlsoap.org/soap/encoding/')
-
-#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+logging.getLogger('suds.client').setLevel(logging.DEBUG)
 #logging.getLogger('suds.metrics').setLevel(logging.DEBUG)
 #logging.getLogger('suds').setLevel(logging.DEBUG)
 
-class MyTransport(HttpAuthenticated):
-    pass
-
-mycache = FileCache(days=90)
-mytransport = MyTransport(cache=mycache)
 
 def start(url):
     global errors
     print '\n________________________________________________________________\n' 
     print 'Test @ ( %s ) %d' % (url, errors)
 
-def basic_doc_literal():
+
+def rhqTest():
     
     global errors
+    
+    url = 'http://localhost.localdomain:7080/rhq-rhq-enterprise-server-ejb3/WebservicesManagerBean?wsdl'
+    start(url)
+    client = Client(url)
+    print client
 
     try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceTestBean?wsdl'
-        start(url)
-        client = Client(url, transport=mytransport)
-        print client
+
         #
         # create name
         #
@@ -80,6 +74,15 @@ def basic_doc_literal():
         phoneB.nxx = 555
         phoneB.number = 4406
         #
+        # lets add some animals
+        #
+        dog = client.factory.create('dog')
+        dog.name = 'rover'
+        dog.age = 3
+        cat = client.factory.create('cat')
+        cat.name = 'kitty'
+        cat.age = 4
+        #
         # create a person object using the wsdl
         #
         person = client.factory.create('person')
@@ -88,6 +91,8 @@ def basic_doc_literal():
         person.age = 43
         person.phone.append(phoneA)
         person.phone.append(phoneB)
+        person.pet.append(dog)
+        person.pet.append(cat)
         print person       
         #
         # addPerson()
@@ -207,334 +212,9 @@ def basic_doc_literal():
         errors += 1
         print e
         tb.print_exc()
-        
-    #
-    # test faults
-    #
-    try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceTestBean?wsdl'
-        start(url)
-        client = Client(url, faults=False)
-        print 'testExceptions() faults=No'
-        result = client.service.testExceptions()
-        sent = client.last_sent()
-        rcvd = client.last_received()
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-        
-def basic_rpc_literal():
-    
-    global errors
-
-    try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceRPCTestBean?wsdl'
-        start(url)
-        client = Client(url)
-        print client
-        #
-        # create name
-        #
-        name = client.factory.create('name')
-        name.first = u'jeff'+unichr(1234)
-        name.last = 'ortel'
-        #
-        # create a phone object using the wsdl
-        #
-        phoneA = client.factory.create('phone')
-        phoneA.npa = 410
-        phoneA.nxx = 555
-        phoneA.number = 5138
-        phoneB = client.factory.create('phone')
-        phoneB.npa = 919
-        phoneB.nxx = 555
-        phoneB.number = 4406
-        #
-        # create a person object using the wsdl
-        #
-        person = client.factory.create('person')
-        print person
-        person.name = name
-        person.age = 43
-        person.phone.append(phoneA)
-        person.phone.append(phoneB)
-        print person       
-        #
-        # addPerson()
-        #
-        print 'addPersion()'
-        result = client.service.addPerson(person)
-        print '\nreply(\n%s\n)\n' % result
-        #
-        # create a new name object used to update the person
-        #
-        newname = client.factory.create('name')
-        newname.first = 'Todd'
-        newname.last = None
-        #
-        # update the person's name (using the webservice)
-        #
-        print 'updatePersion()'
-        result = client.service.updatePerson(person, newname)
-        print '\nreply(\n%s\n)\n' % str(result)
-        result = client.service.updatePerson(person, null())
-        print '\nreply(\n%s\n)\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-  
-    try:
-        print "echo('this is cool')"
-        result = client.service.echo('this is cool')
-        print '\nreply( %s )\n' % str(result)
-        print 'echo(None)'
-        result = client.service.echo(null())
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-        
-    try:
-        print 'hello()'
-        result = client.service.hello()
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-
-    try:
-        print 'testVoid()'
-        result = client.service.testVoid()
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-
-    try:
-        array = client.factory.create('ns0:stringArray')
-        array.item = ['my', 'dog', 'likes', 'steak']
-        print 'testListArgs()\n%s\n' % array
-        result = client.service.testListArg(array)
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-    
-    try:
-        s = 'hello'
-        for n in range(0, 3):
-            print 'getList(%s, %d)' % (s, n)
-            result = client.service.getList(s, n)
-            print '\nreply( %s )\n' % str(result)
-            if n > 0 and n != len(result.item):
-                errors += 1
-                print 'expected (%d), reply (%d)' % (n, len(result.item))
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-    
-    try:
-        print 'testExceptions()' 
-        result = client.service.testExceptions()
-        print '\nreply( %s )\n' % tostr(result)
-        raise Exception('Fault expected and not raised')
-    except WebFault, f:
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-
-    try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceRPCTestBean?wsdl'
-        start(url)
-        client = Client(url, faults=False)
-        print 'testExceptions()'
-        result = client.service.testExceptions()
-        print '\nreply( %s )\n' % str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-        
-def authentication():
-    
-    global subject, errors
-    
-    try:
-        
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/SubjectManagerBean?wsdl'
-        start(url)
-        client = Client(url)
-        print client
-        #
-        # test enumerations
-        #
-        permission = client.factory.create('permission')
-        #
-        # login
-        #
-        print 'login()'
-        subject = client.service.login('rhqadmin', 'rhqadmin')
-        print '\nreply(\n%s\n)\n' % str(subject)
-        #
-        # create page control and get all subjects
-        #
-        pc = client.factory.create('pageControl')
-        pc.pageNumber = 0
-        pc.pageSize = 0
-        #
-        # getAllSubjects()
-        #
-        print 'getAllSubjects()'
-        users = client.service.getAllSubjects(pc)
-        print 'Reply:\n(\n%s\n)\n' % str(users)
-        #
-        # get user preferences
-        #
-        print 'loadUserConfiguration()'
-        id = subject.id
-        print subject
-        prefs = client.service.loadUserConfiguration(id)
-        print 'Reply:\n(\n%s\n)\n' % str(prefs)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-        
-def perspectives():
-    
-    global subject, errors
-    
-    try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/PerspectiveManagerBean?wsdl'
-        start(url)
-        client = Client(url)
-        print client
-        #
-        # get all (content) perspectives
-        #
-        print 'getPerspective(content)'
-        perspectives = client.service.getPerspective("content")
-        print 'perspectives: ', str(perspectives)
-        #
-        # get all perspectives
-        #     
-        print 'getAllPerspective()'
-        perspectives = client.service.getAllPerspectives()
-        print 'perspectives: ', str(perspectives)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
-        
-def content_source():
-    
-    global subject, errors
-    
-    try:
-        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/ContentSourceManagerBean?wsdl'
-        start(url)
-        client = Client(url, xstq=False)
-        print client
-        #
-        # create a configuration
-        #
-        configuration = client.factory.create('configuration')
-        entry = client.factory.create('configuration.properties.entry')
-        simple = client.factory.create('propertySimple')
-        entry.key = 'location'
-        simple.name = 'location'
-        simple.stringValue = 'http://download.skype.com/linux/repos/fedora/updates/i586'
-        entry.value = simple
-        configuration.properties.entry.append(entry)
-        configuration.notes = 'SkipeAdapter'
-        configuration.version = 1234
-        print configuration
-        #
-        # create: name, description and type.
-        #
-        name = 'SkipeAdapter'
-        description = 'The skipe adapter'
-        type = 'YumSource'
-        #
-        # create a content source.
-        #
-        print 'createContentSource()'
-        result = client.service.createContentSource(
-                            subject, 
-                            name, 
-                            description, 
-                            type, 
-                            configuration, 
-                            False)
-        print 'createContentSource: ', str(result)
-    except WebFault, f:
-        errors += 1
-        print f
-        print f.fault
-    except Exception, e:
-        errors += 1
-        print e
-        tb.print_exc()
 
         
 if __name__ == '__main__':
-    
     errors = 0
-    basic_doc_literal()
-    basic_rpc_literal()
-    authentication()
-    perspectives()
-    content_source()
-    
+    rhqTest()
     print '\nFinished: errors=%d' % errors
commit 39b86acd5800a0164df7293f17b35d363194d07f
Author: Daniel Baumann <daniel at debian.org>
Date:   Fri Jun 4 17:53:49 2010 +0200

    Adding upstream version 0.3.9.

diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..32fa870
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+         GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions. 
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version. 
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/README b/README
new file mode 100644
index 0000000..f207ae5
--- /dev/null
+++ b/README
@@ -0,0 +1,578 @@
+OVERVIEW
+
+The "Suds" web services client is a lightweight soap-based client for python
+the is licensed under LGPL.
+
+For details, visit:
+
+  * Project site: https://fedorahosted.org/suds/
+  * Documentation: https://fedorahosted.org/suds/wiki/Documentation
+  * Epydocs: http://jortel.fedorapeople.org/suds/doc/
+   
+
+RELEASE-NOTES:
+=================================================
+
+version 0.3.6 (04-31-09):
+  * Change hard coded /tmp/suds to tempfile.gettempdir() and create suds/ on demand.
+  * Fix return type for Any.get_attribute().
+  * Update http caching to ignore file:// urls.
+  * Better logging of messages when only the reply is injected.
+  * Fix XInteger and XFloat types to translate returned arrays properly.
+  * Fix xs:import schema with same namespace.
+  * Update parser to not load external references and add Import.bind() for XMLSchema.xsd location.
+  * Add schema doctor - used to patch XSDs at runtime. (See Option.doctor)
+  * Fix deprecation warnings in python 2.6.
+  * Add behavior for @default defined on <element/>.
+  * Change @xsi:type value to always be qualified for doc/literal. (reverts 0.3.5 change).
+  * Add Option.xstq option to control when @xsi:type is qualified.
+  * Fixed Tickets: #64, #129, #205, #206, #217, #221, #222, #224, #225, #228, #229, #230 
+
+version 0.3.5 (04-16-09):
+  * Adds http caching. Default is (1) day. Does not apply to method invocation. See: documentation for details.
+  * Removed checking fedora version check in spec since no longer building < fc9.
+  * Updated makefile to roll tarball with tar.sh.
+  * Moved bare/wrapped determination to wsdl for document/literal.
+  * Refactored Transport into a package (provides better logging of http headers).
+  * Fixed Tickets: #207, #209, #210, #212, #214, #215 
+
+version 0.3.4 (02-24-09):
+  * Static (automatic) Import.bind('http://schemas.xmlsoap.org/soap/encoding/'),
+      users no longer need to do this.
+  * Basic ws-security with {{{UsernameToken}}} and clear-text password only.
+  * Add support for ''sparse'' soap headers via passing dictionary
+  * Add support for arbitrary user defined soap headers
+  * Fixes service operations with multiple soap header entries.
+  * Schema loading and dereferencing algorithm enhancements.
+  * Nested soap multirefs fixed.
+  * Better (true) support for elementFormDefault="unqualified" provides more 
+      accurate namespaing.
+  * WSDL part types no longer default to WSDL targetNamespace.
+  * Fixed Tickets: #4, #6, #21, #32, #62, #66, #71, #72, #114, #155, #201.
+
+version 0.3.3 (11-31-08):
+  * No longer installs (tests) package.
+  * Implements API-3 proposal ( https://fedorahosted.org/suds/wiki/Api3Proposal )
+     - Pluggable transport
+    - Keyword method arguments
+    - Baisc http authentication in default transport
+  * Add namespace prefix normalization in soap message.
+  * Better soap message pruning of empty nodes.
+  * Fixed Tickets: #51 - #60
+
+  
+version 0.3.2 (11-7-08):
+  * SOAP {{{MultiRef}}} support ''(1st pass added r300)''
+  * Add support for new schema tags:
+     * <xs:include/>
+     * <xs:simpleContent/>
+     * <xs:group/>
+     * <xs:attributeGroup/>
+  * Added support for new xs <--> python type conversions:
+     * xs:int
+     * xs:long
+     * xs:float
+     * xs:double
+  * Revise marshaller and binding to further sharpen the namespacing of nodes produced.
+  * Infinite recursion fixed in ''xsd'' package dereference() during schema loading.
+  * Add support for <wsdl:import/> of schema files into the wsdl root <definitions/>.
+  * Fix double encoding of (&)
+  * Add Client API:
+     * setheaders() - same as keyword but works for all invocations.
+     * addprefix() - mapping of namespace prefixes.
+    * setlocation() - Override the location in the wsdl; same as keyword except for all calls.
+    * setproxy() - same as proxy keyword but for all invocations.
+  * Add proper namespace prefix for soap headers.
+  * Fixed Tickets: #5, #12, #34, #37, #40, #44, #45, #46, #48, #49, #50, #51
+
+  
+version 0.3.1 (10-1-08):
+
+ * Quick follow up to the 0.3 release that made working multi-port service definitions
+    harder then necessary.  After consideration (and a good night sleep),
+    it seemed obvious that a few changes would make this much easier: 1) filter out 
+    the non-soap bindings - they were causing the real trouble;  2) since most servers
+    are happy with any of the soap bindings (soap 1.1 and 1.2), ambigious references to methods 
+    when invoking then without the port qualification will work just fine in almost every
+    case.  So, why not just allow suds to select the port.  Let's not make the user do it 
+    when it's not necessary.  In most cases, uses on 0.2.9 and earlier will not have to
+    update there code when moving to 0.3.1 as they might have in 0.3.
+
+    
+version 0.3 (9-30-08):
+
+  * Extends the support for multi-port services introduced in 0.2.9.  This addition,
+     provides for multiple services to define the *same* method and suds will
+     handle it properly.  See section 'SERVICES WITH MULTIPLE PORTS:'
+     
+  * Add support for multi-document document/literal soap binding style.
+     See section 'MULTI-DOCUMENT Docuemnt/Literal:'
+  
+  * Add support for (xs:group, xs:attributeGroup) tags.
+  
+  * Add Client.last_sent() and Client.last_received().
+    
+version 0.2.9 (9-09-08):
+   
+   * Support for multiple ports within a service.
+   * Attribute references <xs:attribute ref=""/>
+   * Make XML special character encoder in sax package - pluggable 
+
+
+version 0.2.8 (8-28-08):
+
+   * Update document/literal binding to always send the document root referenced by the <part/>.
+      After yet another review of the space and user input, seems like the referenced 
+      element is ALWAYS the document root.
+
+   * Add support for 'binding' schemaLocations to namespace-uri.  
+     This is for imports that don's specify a schemaLocation and still expect the schema 
+     to be downloaded.  Eg: Axis references 'http://schemas.xmlsoap.org/soap/encoding/' 
+     without a schemaLocation.  So, by doing this:
+        >
+        > from suds.xsd.sxbasic import Import.
+        > Import.bind('http://schemas.xmlsoap.org/soap/encoding/')
+        >
+     The schema is bound to a schemaLocation and it is downloaded.
+
+   * Basic unmarshaller doesn't need a /schema/. 
+      Should have been removed during refactoring but was missed.
+
+   * Update client to pass kwargs to send() and add /location/ kwarg for overriding the
+      service location in the wsdl.
+
+   * Update marshaller to NOT emit XML for object attributes that represent elements and/or attributes that
+      are *both* optional and value=None.
+         * Update factory (builder) to include all attributes.
+         * Add optional() method to SchemaObject.
+
+   * Update wsdl to override namespace in operation if specified.
+   
+   * Fix schema loading issue - build all schemas before processing imports.
+   
+   * Update packaging in preparation of submission to fedora
+
+      
+version 0.2.7 (8-11-08):
+
+   * Add detection/support for document/literal - wrapped and unwrapped.
+   * Update document/literal {wrapped} to set document root (under <body/>) to be the
+      wrapper element (w/ proper namespace).
+   * Add support for <sequence/>, <all/> and <choice/> having maxOccurs and have the
+      which causes the unmarshaller to set values for elements contained in an unbounded
+      collection as a list.
+   * Update client.factory (builder) to omit children of <choice/> since the 'user' really needs
+      to decide which children to include.
+   * Update flattening algorithm to prevent re-flattening of types from imported schemas.
+   * Adjustments to flattening/merging algorithms.
+   
+
+version 0.2.6 (8-5-08):
+
+   * Fix ENUMs broken during xsd package overhaul.
+   * Fix type as defined in ticket #24.
+   * Fix duplicate param names in method signatures as reported in ticket #30.
+   * Suds licensed as LGPL.
+   * Remove logging setup in suds.__init__() as suggested by patch in ticket #31.  Users will
+      now need to configure the logger.
+   * Add support for Client.Factory.create() alt: syntax for fully qualifying the type to be
+      built as: {namespace}name.  Eg: client.factory.create('{http://blabla.com/ns}Person')
+
+
+version 0.2.5 (8-01-08):
+
+   * Overhauled the (XSD) package.  This new (merging) approach is simpler and should be
+      more reliable and maintainable.  Also, should provide better performance since the merged
+      schema performes lookups via dictionary lookup.
+      
+      This overhaul should fix current TypeNotFound and <xs:extension/> problems, I hope :-).
+      
+   * Fixed dateTime printing bug.
+
+   * Added infinite recursion prevention in builder.Builder for xsd types that contain themselves.
+
+
+version 0.2.4 (7-28-08):
+
+   * Added support for WSDL imports: <wsdl:import/>
+   * Added support for xsd<->python type conversions (thanks: Nathan Van Gheem) for:
+      * xs:date
+      * xs:time
+      * xs:dateTime
+   * Fixed:
+      * Bug: Schema <import/> with schemaLocation specified.
+      * Bug: Namespaces specified in service description not valid until client/proxy is printed.
+
+
+version 0.2.3 (7-23-08):
+
+   * Optimizations.
+
+
+version 0.2.2 (7-8-08):
+
+   * Update exceptions to be more /standard/ python by using Exception.__init__() to set Exception.message as
+     suggested by Ticket #14; update bindings to raise WebFault passing (p)
+   
+   * Add capability in bindings to handle multiple root nodes in the returned values;
+     returned as a composite object unlike when lists are returned
+   
+   * Fix soapAction to be enclosed by quotes
+   
+   * Add support for <xs:all/>
+   
+   * Fix unbounded() method in SchemaObject
+   
+   * Refactored schema into new (xsd) package.  Files just getting too big. Added execute() to
+     Query and retrofitted suds to execute() query instead of using Schema.find() directly.  
+     Also, move hokey start() methods from schema, as well as, query incrementation.
+   
+   * Add inject keyword used to inject outbound soap messages and/or inbound reply messages.
+      Refactor SoapClient and 
+        1) rename send() to invoke()
+        2) split message sending from invoke() and place in send();
+      Add TestClient which allows for invocation kwargs to have inject={'msg=, and reply='} for message
+      and reply injection
+     
+   * Add Namespace class to sax for better management of namespace behavior;
+     retrofix suds to import and use Namespace
+   
+   * Change the default namespace used to resolve referenced types (having attribues @base="", at type="")
+      so that when no prefix is specified: uses XML (node) namesapce instead of the targetNamespace.
+   
+   * Apply fix as defined by davidglick at onenw.org in ticket #13
+   
+   * Update service definition to print to display service methods as ' my_method(xs:int arg0, Person arg1) '
+      instead of ' my_method(arg0{xs:int}, arg1{Person}) ' which is more like traditional method signatures
+   
+   * Add xsd/python type converstion to unmarshaller (XBoolean only); refactor unmarshaller to use Content
+      class which makes apis cleaner, adds symmetry between marshaller(s) and unmarshaller(s),
+      provides good mechanism for schema-property based type conversions
+   
+   * Refactor marshaller with Appenders; add nobuiltin flag to resolve() to support fix for
+      returned_type() and returnes_collection() in bindings.
+   
+   * Add support for (202,204) http codes
+   
+   * Add XBoolean and mappings; add findattr() to TreeResolver in preparation for type conversions
+   
+   * Updated schema and schema property loading (deep recusion stopped); Changed Imported schemas so then no
+      longer copy imported schemas, rather the import proxies find requests; Add ServiceDefinition class which
+      provides better service inspection; also provides namespace mapping and show types; schema property api simplified;
+      support for xs:any and xs:anyType added; Some schema lookup problems fixed; Binding classes refactored slightly;
+      A lot of debug logging added (might have to comment some out for performance - some of the args are expensive).
+   
+   * Add sudsobject.Property; a property is a special Object that contains a (value) attributeand is returned by the
+      Builder (factory) for schema-types without children such as: <element/> and <simpleType/>; Builder, Marshallers
+      and Resolvers updated to handle Properties; Resolver, Schema also updated to handle attribute lookups (this was missing)
+   
+   * Add groundwork for user defined soap headers.
+   
+   * Fix elementFormDefault per ticket #7
+   
+   * Remove unused kwargs from bindings; cache bindings in wsdl; retrofit legacy ServiceProxy to delegate to {new} Client API;
+      remove keyword nil_supported in favor of natural handling by 'nillable' attribute on <element/> within schemas
+   
+   * Add support for <element/> attribute flags (nillable and form)
+   
+   * Add the Proxy (2nd generation API) class
+   
+   * Add accessor/conversion functions to that user don't need to access __x__ attributes.
+      Also add todict() and get_items() for easy conversion to dictionary and iteration
+   
+   * Search top-level elements for @ref before looking deeper
+   
+   * Add derived() to SchemaObject.  This is needed to ensure that all derived types (wsdl classes)
+      are qualified by xsi:type without specifying the xsi:type for all custom types as did in earlier
+      releases of suds.  Update the literal marshaller to only add the xsi:type when the type needs
+      to be specified.
+   
+   * Change ns promotion in sax to prevent ns promoted to parent when parent has the prefix.
+   
+   * Changed binding returned_type() to return the (unresolved) Element 
+   
+   * In order to support the new features and fix reported bugs,
+      I'm in the process of refactoring and hopefully evolving the components
+      in Suds that provide the input/output translations:
+   
+     * Builder ( translates: XSD objects => python objects )
+     * Marshaller ( translates: python objects => XML/SOAP )
+     * Unmarshaller ( translates: soap/xml => python objects )
+   
+     This evolution will provide better symmetry between these components as follows:
+   
+     The Builder and Unmarshaller will produce python
+     (subclass of sudsobject.Object) objects with:
+   
+     *  __metadata__.__type__ = XSD type (SchemaObject)
+     * subclass name ( __class__.__name__ ) = schema-type name.
+   
+     and
+   
+     The Marshaller(s), while consuming python objects produced by the Builder or
+     Unmarshaller, will leverage this standard information to
+     produce the appropriate output ( XML/SOAP ).
+   
+     The 0.2.1 code behaves *mostly* like this but ... not quite.
+     Also, the implementations have some redundancy.
+   
+     While doing this, it made sense to factor out the common schema-type "lookup"
+     functionality used by the Builder, Marshallers and Unmarshaller classes into a
+     hierarchy of "Resolver" classes.  This reduces the complexity and redundancy
+     of the Builder, Marshallers and Unmarshaller classes and allows for better
+     modularity.  Once this refactoring was complete, the difference between the
+     literal/encoded Marshallers became very small.  Given that the amount of code
+     in the bindings.literal and bindings.encoded packages was small (and getting smaller)
+     and in the interest of keeping the Suds code base compact, I moved all of the
+     marshalling classes to the bindings.marshaller module.
+     All of the bindings.XX sub-packages will be removed.
+   
+     The net effect:
+   
+     All of the Suds major components:
+   
+     * client (old: service proxy)
+     * wsdl
+       * schema (xsd package)
+       * resolvers
+     * output (marshalling)
+     * builder
+     * input (unmarshalling)
+   
+     Now have better:
+   
+      * modularity
+      * symmetry with regard to Object metadata.
+      * code re-use (< 1% code duplication --- i hope)
+      * looser coupling
+   
+     ... and better provide for the following features/bug-fixes:
+   
+     * (fix) Proper level of XML element qualification based on
+        <schema elementFormDefault=""/> attribute.  This will ensure that when
+        elementFormDefault="qualified", Suds will include the proper namespace on
+        root elements for both literal and encoded bindings.  In order for this to
+        work properly, the literal marshaller (like the encoded marshaller) needed
+        to be schema-type aware.  Had i added the same schema-type lookup as the
+        encoded marshaller instead of the refactoring described above, the two
+        classes would have been almost a complete duplicate of each other :-(
+   
+   * The builder and unmarshaller used the schema.Schema.find()
+     to resolve schema-types.  They constructed a path as "person.name.first"
+     to resolve types in proper context.  Since the Schema.find() was stateless,
+     it resolved the intermediate path elements on every call.  The new resolver
+     classes are statefull and resolve child types *much* more efficiently.
+   
+   * Prevent name collisions in sudsobject.Object like the items() method.  I've moved all
+     methods (including class methods) to a Factory class that is included in the Object class
+     as a class attr ( __factory__ ).  Now that *all* attributes have python built-in naming,
+     we should not have any more name collisions.  This of course assumes that no wsdl/schema
+     entity names will have a name with the python built-in naming convention
+     but I have to draw the line somewhere :-)
+     
+
+version 0.2.1 (5-8-08):
+
+   * Update the schema.py SchemaProperty loading sequence so that the schema is loaded in 3 steps: 
+         1) build the raw tree. 
+         2) resolve dependancies such as @ref and @base.  
+         3) promote grandchildren as needed to flatten (denormalize) the tree.
+         
+       The wsdl was also changed to only load the schema once and store it.  The schema collection was 
+       changed to load schemas in 2 steps:
+         1) create all raw schema objects.
+         2) load schemas.  
+       
+       This ensure that local <import/>'d schemas can be found when referenced out of order.  
+       The sax.py Element interface changed: attribute() replaced by get() and set().  
+       Also, __getitem__ and __setitem__ can be used to access attribute values.  
+       Epydocs updated for sax.py.  And ... last <element ref=/> now supported properly.
+
+   * fix logging by: NOT setting to info in suds.__init__.logger(); set handler on root logger 
+      only; moved logger (log) from classes to modules and use __name__ for logger name.
+      NOTE: This means that to enable soap message logging:
+           >
+           > logger('suds.serviceproxy').setLevel(logging.DEBUG)
+           > 
+         -- instead of --
+           >
+           > logger('serviceproxy').setLevel(logging.DEBUG)
+           >
+      
+   * Add support for (xsd) schema <attribute/> nodes which primarily affects objects returned by the Builder
+   
+   * Update serviceproxy.py:set_proxies() to log DEBUG instead of INFO.
+   
+   * Enhance schema __str__ to show both the raw xml and the model (mostly for debugging).
+
+
+version-0.2 (04-28-08):
+
+  * Contains the first cut at the rpc/encoded soap style.
+ 
+  * Replaced Property class with suds.sudsobject.Object.  The Property class was developed a long time
+     ago with a slightly different purpose.  The suds Object is a simpler (more straight forward) approach that 
+     requires less code and works better in the debugger. 
+
+  * The Binding (and the encoding) is selected on a per-method basis which is more consistent with the wsdl.
+     In <= 0.1.7, the binding was selected when the ServiceProxy was constructed and used for all service
+     methods.  The binding was stored as self.binding.  Since the WSDL provides for a separate binding style and
+     encoding for each operation, Suds needed to be change to work the same way.
+ 
+  * The (nil_supported) and (faults) flag(s) passed into the service proxy using **kwargs.  In addition to these
+     flags, a (http_proxy) flag has been added and is passed to the urllib2.Request object.  The following args
+     are supported:
+       * faults = Raise faults raised by server (default:True), else return tuple from service method invocation
+                        as (http code, object).
+       * nil_supported = The bindings will set the xsi:nil="true" on nodes that have a value=None when this
+                                      flag is True (default:True).  Otherwise, an empty node <x/> is sent.
+       * proxy = An http proxy to be specified on requests (default:{}).
+                       The proxy is defined as {protocol:proxy,}
+                                      
+  * Http proxy supported (see above).
+  
+  * ServiceProxy refactored to delegate to a SoapClient.  Since the service proxy exposes web services via getattr(),
+     any attribute (including methods) provided by the ServiceProxy class hides WS operations defined by the
+     wsdl.  So, by moving everything to the SoapClient, wsdl operations are no longer hidden without
+     having to use *hoky* names for attributes and methods in the service proxy.  Instead, the service proxy has
+     __client__ and __factory__ attributes (which really should be at low risk for name collision).  For now the 
+     get_instance() and get_enum() methods have not been moved to preserve backward compatibility.  Although,
+     the prefered API change would to replace:
+     
+     > service = ServiceProxy('myurl')
+     > person = service.get_instance('person')
+     
+     ... with something like ...
+     
+     > service = ServiceProxy('myurl')
+     > person = service.__factory__.get_instance('person')
+     
+     After a few releases giving time for users to switch the new API, the get_instance() and get_enum()
+     methods may be removed with a notice in big letters.
+     
+   * Fixed problem where a wsdl doesn't define a <schema/> section and Suds can't resolve the prefixes for the
+      http://www.w3.org/2001/XMLSchema namespace to detect builtin types such as (xs:string).
+      
+      
+version-0.1.7 (04-08-08):
+
+  * Added Binding.nil_supported to controls how property values (out) = None and empty tag (in) are processed.
+    * service.binding.nil_supported = True -- means that property values = None are marshalled (out) as
+      <x xsi:nil=true/> and <x/> is unmarshalled as '' and <x xsi:nil/> is unmarshalled as None.
+    * service.binding.nil_supported = False -- means that property values = None are marshalled (out) as
+      <x/> and <x/> *and* <x xsi:nil=true/> is unmarshalled as None.
+      The xsi:nil is really ignored.
+    * THE DEFAULT IS (TRUE)
+
+  * Sax handler updated to handle multiple character() callbacks when the sax parser "chunks" the text.
+    When the node.text is None, the node.text is set to the characters.  Else, the characters are appended.
+    Thanks - andrea.spinelli at imteam.it
+  
+  * Replaced special (text) attribute with __text__ to allow for natural elements named "text"
+  
+  * Add unicode support by:
+    * Add __unicode__ to all classes with __str__
+    * Replace all str() calls with unicode().
+    * __str__() returns UTF-8 encoded result of __unicode__.
+
+  * XML output encoded as UTF-8 which matches the HTTP header and supports unicode.
+  
+  * SchemaCollection changed to provide the builtin() and custom() methods.  To support this, findPrefixes() was added to the
+    Element in sax.py.  This is a better approach anyway since the wsdl and schemas may have many prefixes
+    to http://www.w3.org/2001/XMLSchema.  Tested with both doc/lit and rpc/lit bindings
+  
+  * Refactored bindings packages from document & rpc to literal & encoded
+  
+  * Contains the completion of *full* namespace support as follows:
+  
+    * Namespace prefixes are no longer stripped from attribute values that
+      reference types defined in the wsdl.
+    * Schema's imported using <import/> should properly handle namespace and prefix
+      mapping and re-mapping as needed.
+    * All types are resolved, using fully qualified (w/ namespaces) lookups.
+    * Schema.get_type() supports paths with and without ns prefixes.  When no prefix
+      is specified the type is matched using the schema's target namespace.
+  
+  * Property maintains attribute names (keys) in the order added. This also means
+    that get_item() and get_names() return ordered values.
+     ( Although, I suspect ordering really needs to be done in the marshaller using the
+        order specified in the wsdl/schema )
+  
+  Major refactoring of the schema.py. The primary goals is perparation for type lookups that are
+  fully qualified by namespace.  Once completed, the prefixes on attribute values will not longer
+  be stripped (purged).
+  Change Summary:
+    1) SchemaProperty overlay classes created at __init__ instead of on-demand.
+    2) schema imports performed by new Import class instead of by Schema.
+    3) Schema loads top level properties using a factory.
+    4) All SchemaProperty /children/ lists are sorted by __cmp__ in SchemaProperty derived classes.
+       This ensures that types with the same name are resolved in the following order (Import, Complex, Simple, Element).
+    5) All /children/ SchemaProperty lists are constructed at __init__ instead of on-demand.
+    6) The SchemaGroup created and WSDL class updated.  This works better then having the wsdl aggregate the <schema/>
+       nodes which severs linkage to the wsdl parent element that have namespace prefix mapping.
+    7) <import/> element handles properly in that both namespace remapping and prefix re-mapping of the imported schema's
+       targetNamespace and associated prefix mapping - is performed.
+         Eg: SCHMEA-A has prefix (tns) mapped as xmlns:tns=http://nsA and has targetNamespace=http://nsA.
+         SCHEMA-B is importing schema A and has prefix (abc) mapped as xmlns:abc=http://nsABC.
+         SCHEMA-B imports A as <import namespace=http://nsB xxx schemaLocation=http://nsA/schema-a.xsd>.
+         So, since SCHEMA-B will be referencing elements of SCHEMA-A with prefix (abc) such as abc:something, SCHEMA-A's
+         targetNamespace must be updated as http://nsABC and all element with type=tns:something must be updated to be
+         type=abc:something so then can be resolved.
+
+  * Fixes unmarshalling problem where nodes are added to property as (text, value).  This as introduced when the
+    bindings were refactored.
+
+  * Fixed various Property print problems.
+
+  Notes:
+
+    Thanks to Jesper Noehr of Coniuro for the majority of the rpc/literal binding and
+    for lots of collaboration on #suds.
+    
+    
+version-0.1.6 (03-06-08):
+
+ * Provides proper handling of wsdls that contain schema sections containing
+    xsd schema imports: <import namespace="" schemaLocation=""?>.  The
+    referenced schemas are imported when a schemaLocation is specified.
+* Raises exceptions for http status codes not already handled.
+
+
+version-0.1.5( 02-21-08 ):
+
+ * Provides better logging in the modules get logger by hierarchal names.
+ * Refactored as needed to truely support other bindings.
+ * Add sax module which replaces ElementTree.  This is faster, simpler and
+   handles namespaces (prefixes) properly.
+   
+   
+version-0.1.4 (12-21-07):
+
+  * Provides for service method parameters to be None.
+  * Add proper handling of method params that are lists of property
+    objects.
+    
+    
+version-0.1.3 (12-19-07):
+
+  * Fixes problem where nodes marked as a collection (maxOccurs > 1) not
+     creating property objects with value=[] when mapped-in with < 2
+     values by the DocumentReader.  Caused by missing the 
+     bindings.Document.ReplyHint.stripns() (which uses the DocumentReader.stripns())
+     conversion to DocumentReader.stripn() now returning a tuple (ns,tag) as
+     of 0.1.2.
+
+
+version-0.1.2 (12-18-07):
+
+  This release contains an update to property adds:
+    * metadata support
+    * overrides: __getitem__, __setitem__, __contans__
+    * changes property(reader|writer) to use the property.metadata
+       to handle namespaces for XML documents.
+    * fixes setup.py requires.
+    
+    
+version-0.1.1 (12-17-07):
+
+  This release marks the first release in fedora hosted.
diff --git a/makefile b/makefile
new file mode 100644
index 0000000..32a3f9e
--- /dev/null
+++ b/makefile
@@ -0,0 +1,71 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+#
+
+PKG = python-suds
+SPEC = $(PKG).spec
+SETUP = setup.py
+DOCTAR = suds-docs.tar.gz
+FEDORAPEOPLE = jortel at fedorapeople.org
+
+all : rpm docs
+
+dist : clean
+	mkdir dist
+	./sdist
+	./sdist python
+
+rpm : dist
+	cp dist/$(PKG)*.gz /usr/src/redhat/SOURCES
+	rpmbuild -ba $(SPEC)
+	cp /usr/src/redhat/RPMS/noarch/$(PKG)*.rpm dist
+	cp /usr/src/redhat/SRPMS/$(PKG)*.rpm dist
+	rpmlint -i dist/$(PKG)*.rpm
+
+release : rpm rdocs
+	scp dist/python*.tar.gz fedorahosted.org:suds
+	scp dist/python*.rpm fedorahosted.org:suds
+
+register :
+	python setup.py sdist bdist_egg register upload
+
+rdocs : docs
+	scp /tmp/$(DOCTAR) $(FEDORAPEOPLE):
+	ssh $(FEDORAPEOPLE) 'cd public_html/suds; rm -rf doc; tar xmzvf ~/$(DOCTAR)'
+
+docs :
+	rm -rf doc
+	rm -f /tmp/$(DOCTAR)
+	epydoc -vo doc `find suds -name "*.py"`
+	tar czvf /tmp/$(DOCTAR) doc
+
+pdf :
+	epydoc -vo doc --pdf `find suds -name \*.py`
+	mv doc/api.pdf doc/sudsapi.pdf
+
+clean :
+	rm -rf dist
+	rm -rf build
+	rm -rf doc
+	rm -rf *.egg-info
+	rm -rf /usr/src/redhat/BUILD/$(PKG)*
+	rm -rf /usr/src/redhat/RPMS/noarch/$(PKG)*
+	rm -rf /usr/src/redhat/SOURCES/$(PKG)*
+	rm -rf /usr/src/redhat/SRPMS/$(PKG)*
+	find . -name "*.pyc" -exec rm -f {} \;
+	find . -name "*~" -exec rm -f {} \;
+
+.PHONY : clean register docs pdf
diff --git a/python-suds.spec b/python-suds.spec
new file mode 100644
index 0000000..186ae4d
--- /dev/null
+++ b/python-suds.spec
@@ -0,0 +1,195 @@
+%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}
+
+Summary: A python SOAP client
+Name:  python-suds
+Version: 0.3.9
+Release: 1%{?dist}
+Source0: https://fedorahosted.org/releases/s/u/%{name}/%{name}-%{version}.tar.gz
+License: LGPLv3+
+Group: Development/Libraries
+BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
+BuildArch: noarch
+Requires: python >= 2.4
+BuildRequires: python-setuptools-devel
+Url: https://fedorahosted.org/suds
+
+%description
+The suds project is a python soap web services client lib.  Suds leverages
+python meta programming to provide an intuative API for consuming web
+services.  Objectification of types defined in the WSDL is provided
+without class generation.  Programmers rarely need to read the WSDL since
+services and WSDL based objects can be easily inspected.  Supports
+pluggable soap bindings.
+
+%prep
+%setup -q
+
+%build
+python setup.py sdist
+
+%install
+rm -rf $RPM_BUILD_ROOT
+python setup.py install --optimize=1 --root=$RPM_BUILD_ROOT
+
+%clean
+rm -rf $RPM_BUILD_ROOT
+
+%files
+%defattr(-,root,root,-)
+%{python_sitelib}/suds*.egg-info
+%dir %{python_sitelib}/suds
+%dir %{python_sitelib}/suds/bindings
+%dir %{python_sitelib}/suds/sax
+%dir %{python_sitelib}/suds/xsd
+%dir %{python_sitelib}/suds/mx
+%dir %{python_sitelib}/suds/umx
+%dir %{python_sitelib}/suds/transport
+%{python_sitelib}/suds/*.py*
+%{python_sitelib}/suds/bindings/*.py*
+%{python_sitelib}/suds/sax/*.py*
+%{python_sitelib}/suds/xsd/*.py*
+%{python_sitelib}/suds/mx/*.py*
+%{python_sitelib}/suds/umx/*.py*
+%{python_sitelib}/suds/transport/*.py*
+
+%doc README LICENSE
+
+%changelog
+* Thu Dec 17 2009 jortel <jortel at redhat.com> - 0.3.9-1
+- Bumped python requires to 2.4
+- Replaced stream-based caching in the transport package with document-based caching.
+- Caches pickled Document objects instead of XML text. 2x Faster!
+- No more SAX parsing exceptions on damaged or incomplete cached files. 
+- Cached WSDL objects. Entire Definitions object including contained Schema object cached via pickle.
+- Copy of soap encoding schema packaged with suds.
+- Refactor Transports to use ProxyHandler instead of urllib2.Request.set_proxy().
+- Added WSSE enhancements <Timestamp/> and <Expires/> support. See: Timestamp token. 
+- Fixed Tickets: #256, #291, #294, #295, #296
+
+* Wed Dec 9 2009 jortel <jortel at redhat.com> - 0.3.8-1
+- Includeds Windows NTLM Transport.
+- Add missing self.messages in Client.clone().
+- Changed default behavior for WSDL PartElement to be optional.
+- Add support for services/ports defined without <address/> element in WSDL.
+- Fix sax.attribute.Element.attrib() to find by name only when ns is not specified; renamed to Element.getAttribute().
+- Update HttpTransport to pass timeout parameter to urllib2 open() methods when supported by urllib2.
+- Add null class to pass explicit NULL values for parameters and optional elements.
+- Soap encoded array (soap-enc:Array) enhancement for rpc/encoded.
+  Arrays passed as python arrays - works like document/literal now.
+  No more using the factory to create the Array.
+  Automatically includes arrayType attribute.  Eg: soap-enc:arrayType="Array[2]".
+  Reintroduced ability to pass complex (objects) using python dict instead of suds object via factory.
+- Fixed tickets: #84, #261, #262, #263, #265, #266, #278, #280, #282.
+
+* Thu Oct 16 2009 jortel <jortel at redhat.com> - 0.3.7-1
+- Better soap header support
+- Added new transport HttpAuthenticated for active (not passive) basic authentication.
+- New options (prefixes, timeout, retxml)
+- WSDL processing enhancements.
+- Expanded builtin XSD type support.
+- Fixed <xs:iniclude/>
+- Better XML date/datetime conversion.
+- Client.clone() method added for lightweight copy of client object.
+- XSD processing fixes/enhancements.
+- Better <simpleType/> by <xs:restriction/> support.
+- Performance enhancements. 
+- Fixed tickets: #65, #232, #233, #235, #241, #242, #244, #247, #254, #254, #256, #257, #258
+
+* Sun Jul 26 2009 Fedora Release Engineering <rel-eng at lists.fedoraproject.org> - 0.3.6-2
+- Rebuilt for https://fedoraproject.org/wiki/Fedora_12_Mass_Rebuild
+
+* Wed May 1 2009 jortel <jortel at redhat.com> - 0.3.6-1
+- Change hard coded /tmp/suds to tempfile.gettempdir() and create suds/ on demand.
+- Fix return type for Any.get_attribute().
+- Update http caching to ignore file:// urls.
+- Better logging of messages when only the reply is injected.
+- Fix XInteger and XFloat types to translate returned arrays properly.
+- Fix xs:import schema with same namespace.
+- Update parser to not load external references and add Import.bind() for XMLSchema.xsd location.
+- Add schema doctor - used to patch XSDs at runtime.  (See Options.doctor)
+- Fix deprecation warnings in python 2.6.
+- Add behavior for @default defined on <element/>.
+- Change @xsi:type value to always be qualified for doc/literal.
+- Add Option.xstq option to control when @xsi:type is qualified.
+- Fixed Tickets: #64, #129, #205, #206, #217, #221, #222, #224, #225, #228, #229, #230
+
+* Wed Feb 25 2009 jortel <jortel at redhat.com> - 0.3.5-1
+- Adds http caching.  Default is (1) day.
+- Removed checking fc version in spec since no longer building < fc9.
+- Updated makefile to roll tarball with tar.sh.
+- Moved bare/wrapped determination to wsdl for document/literal.
+- Refactored Transport to provide better visibility into http headers.
+- Fixed Tickets: #207, #207, #209, #210, #212, #214, #215
+
+* Mon Dec 08 2008 jortel <jortel at redhat.com> - 0.3.4-1
+- Static (automatic) Import.bind('http://schemas.xmlsoap.org/soap/encoding/')
+- Basic ws-security with {{{UsernameToken}}} and clear-text password only.
+- Add support for ''sparse'' soap headers via passing dictionary
+- Add support for arbitrary user defined soap headers
+- Fixes service operations with multiple soap header entries.
+- Schema loading and dereferencing algorithm enhancements.
+- Nested soap multirefs fixed.
+- Better (true) support for elementFormDefault="unqualified" provides more accurate namespaing.
+- WSDL part types no longer default to WSDL targetNamespace.
+- Fixed Tickets: #4, #6, #21, #32, #62, #66, #71, #72, #114, #155, #201.
+
+* Wed Dec 04 2008 jortel <jortel at redhat.com> - 0.3.3-2
+- Rebuild for Python 2.6
+
+* Wed Dec 04 2008 jortel <jortel at redhat.com> - 0.3.3-1
+- No longer installs (tests) package.
+- Implements API-3 proposal
+    Pluggable transport
+    Keyword method arguments
+    Baisc http authentication in default transport
+- Add namespace prefix normalization in soap message.
+- Better soap message pruning of empty nodes.
+- Fixed Tickets: #51 - #60.
+
+* Sat Nov 29 2008 Ignacio Vazquez-Abrams <ivazqueznet+rpm at gmail.com> - 0.3.2-2
+- Rebuild for Python 2.6
+
+* Fri Nov 06 2008 jortel <jortel at redhat.com> - 0.3.2-1
+- Add SOAP MultiRef support
+- Add support for new schema tags:
+    <xs:include/>
+    <xs:simpleContent/>
+    <xs:group/>
+    <xs:attributeGroup/>
+- Added support for new xs <--> python type conversions:
+    xs:int
+    xs:long
+    xs:float
+    xs:double
+- Revise marshaller and binding to further sharpen the namespacing of nodes produced.
+- Infinite recursion fixed in ''xsd'' package dereference() during schema loading.
+- Add support for <wsdl:import/> of schema files into the wsdl root <definitions/>.
+- Fix double encoding of (&)
+- Add Client API:
+    setheaders()  - Same as keyword but works for all invocations.
+    addprefix()   - Mapping of namespace prefixes.
+    setlocation() - Override the location in the wsdl.
+    setproxy()    - Same as proxy keyword but for all invocations.
+- Add proper namespace prefix for soap headers.
+- Fixed Tickets: #5, #12, #34, #37, #40, #44, #45, #46, #48, #49, #50, #51
+
+* Fri Nov 03 2008 jortel <jortel at redhat.com> - 0.3.1-5
+- Add LICENSE to %%doc.
+
+* Fri Oct 28 2008 jortel <jortel at redhat.com> - 0.3.1-4
+- Changes acc. #466496 Comment #8
+
+* Fri Oct 27 2008 jortel <jortel at redhat.com> - 0.3.1-3
+- Add "rm -rf $RPM_BUILD_ROOT" to install
+
+* Fri Oct 16 2008 jortel <jortel at redhat.com> - 0.3.1-2
+- Changes acc. #466496 Comment #1
+
+* Fri Oct 10 2008 jortel <jortel at redhat.com> - 0.3.1-1
+- Extends the support for multi-port services introduced earlier. This addition, 
+  provides for multiple services to define the *same* method and suds will
+  handle it properly.  See section 'SERVICES WITH MULTIPLE PORTS:'
+- Add support for multi-document document/literal soap binding style.
+  See section 'MULTI-DOCUMENT Docuemnt/Literal:'
+- Add support for (xs:group, xs:attributeGroup) tags.
+- Add Client.last_sent() and Client.last_received().
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..2dca2f8
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,2 @@
+[install]
+optimize = 1
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..659e123
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,33 @@
+#!/usr/bin/python
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+import suds
+from setuptools import setup, find_packages
+
+setup(
+    name="suds",
+    version=suds.__version__,
+    description="Lightweight SOAP client",
+    author="Jeff Ortel",
+    author_email="jortel at redhat.com",
+    maintainer="Jeff Ortel",
+    maintainer_email="jortel at redhat.com",
+    packages=find_packages(exclude=['tests']),
+    url="https://fedorahosted.org/suds",
+)
diff --git a/suds/__init__.py b/suds/__init__.py
new file mode 100644
index 0000000..2a5d4b5
--- /dev/null
+++ b/suds/__init__.py
@@ -0,0 +1,154 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Suds is a lightweight SOAP python client that provides a
+service proxy for Web Services.
+"""
+
+import os
+import sys
+
+#
+# Project properties
+#
+
+__version__ = '0.3.9'
+__build__="GA R659-20100219"
+
+#
+# Exceptions
+#
+
+class MethodNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, "Method not found: '%s'" % name)
+        
+class PortNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, "Port not found: '%s'" % name)
+        
+class ServiceNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, "Service not found: '%s'" % name)
+    
+class TypeNotFound(Exception):
+    def __init__(self, name):
+        Exception.__init__(self, "Type not found: '%s'" % tostr(name))
+    
+class BuildError(Exception):
+    msg = \
+        """
+        An error occured while building a instance of (%s).  As a result
+        the object you requested could not be constructed.  It is recommended
+        that you construct the type manually using a Suds object.
+        Please open a ticket with a description of this error.
+        Reason: %s
+        """
+    def __init__(self, name, exception):
+        Exception.__init__(self, BuildError.msg % (name, exception))
+        
+class SoapHeadersNotPermitted(Exception):
+    msg = \
+        """
+        Method (%s) was invoked with SOAP headers.  The WSDL does not
+        define SOAP headers for this method.  Retry without the soapheaders
+        keyword argument.
+        """
+    def __init__(self, name):
+        Exception.__init__(self, self.msg % name)
+    
+class WebFault(Exception):
+    def __init__(self, fault, document):
+        if hasattr(fault, 'faultstring'):
+            Exception.__init__(self, "Server raised fault: '%s'" % fault.faultstring)
+        self.fault = fault
+        self.document = document
+
+#
+# Logging
+#
+
+class Repr:
+    def __init__(self, x):
+        self.x = x
+    def __str__(self):
+        return repr(self.x)  
+
+#
+# Utility
+#
+
+def tostr(object, encoding=None):
+    """ get a unicode safe string representation of an object """
+    if isinstance(object, basestring):
+        if encoding is None:
+            return object
+        else:
+            return object.encode(encoding)
+    if isinstance(object, tuple):
+        s = ['(']
+        for item in object:
+            if isinstance(item, basestring):
+                s.append(item)
+            else:
+                s.append(tostr(item))
+            s.append(', ')
+        s.append(')')
+        return ''.join(s)
+    if isinstance(object, list):
+        s = ['[']
+        for item in object:
+            if isinstance(item, basestring):
+                s.append(item)
+            else:
+                s.append(tostr(item))
+            s.append(', ')
+        s.append(']')
+        return ''.join(s)
+    if isinstance(object, dict):
+        s = ['{']
+        for item in object.items():
+            if isinstance(item[0], basestring):
+                s.append(item[0])
+            else:
+                s.append(tostr(item[0]))
+            s.append(' = ')
+            if isinstance(item[1], basestring):
+                s.append(item[1])
+            else:
+                s.append(tostr(item[1]))
+            s.append(', ')
+        s.append('}')
+        return ''.join(s)
+    try:
+        return unicode(object)
+    except:
+        return str(object)
+    
+class null:
+    """
+    The I{null} object.
+    Used to pass NULL for optional XML nodes.
+    """
+    pass
+    
+def objid(obj):
+    return obj.__class__.__name__\
+        +':'+hex(id(obj))
+
+
+import client
diff --git a/suds/bindings/__init__.py b/suds/bindings/__init__.py
new file mode 100644
index 0000000..5471eba
--- /dev/null
+++ b/suds/bindings/__init__.py
@@ -0,0 +1,20 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides modules containing classes to support Web Services (SOAP)
+bindings.
+"""
\ No newline at end of file
diff --git a/suds/bindings/binding.py b/suds/bindings/binding.py
new file mode 100644
index 0000000..a6d421e
--- /dev/null
+++ b/suds/bindings/binding.py
@@ -0,0 +1,511 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides classes for (WS) SOAP bindings.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax import Namespace
+from suds.sax.parser import Parser
+from suds.sax.element import Element
+from suds.sudsobject import Factory, Object
+from suds.mx import Content
+from suds.mx.literal import Literal as MxLiteral
+from suds.umx.basic import Basic as UmxBasic
+from suds.umx.typed import Typed as UmxTyped
+from suds.bindings.multiref import MultiRef
+from suds.xsd.query import TypeQuery, ElementQuery
+from suds.xsd.sxbasic import Element as SchemaElement
+from suds.options import Options
+from copy import deepcopy 
+
+log = getLogger(__name__)
+
+envns = ('SOAP-ENV', 'http://schemas.xmlsoap.org/soap/envelope/')
+
+
+class Binding:
+    """
+    The soap binding class used to process outgoing and imcoming
+    soap messages per the WSDL port binding.
+    @cvar replyfilter: The reply filter function.
+    @type replyfilter: (lambda s,r: r)
+    @ivar wsdl: The wsdl.
+    @type wsdl: L{suds.wsdl.Definitions}
+    @ivar schema: The collective schema contained within the wsdl.
+    @type schema: L{xsd.schema.Schema}
+    @ivar options: A dictionary options.
+    @type options: L{Options}
+    """
+    
+    replyfilter = (lambda s,r: r)
+
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A wsdl.
+        @type wsdl: L{wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.multiref = MultiRef()
+        
+    def schema(self):
+        return self.wsdl.schema
+    
+    def options(self):
+        return self.wsdl.options
+        
+    def unmarshaller(self, typed=True):
+        """
+        Get the appropriate XML decoder.
+        @return: Either the (basic|typed) unmarshaller.
+        @rtype: L{UmxTyped}
+        """
+        if typed:
+            return UmxTyped(self.schema())
+        else:
+            return UmxBasic()
+        
+    def marshaller(self):
+        """
+        Get the appropriate XML encoder.
+        @return: An L{MxLiteral} marshaller.
+        @rtype: L{MxLiteral}
+        """
+        return MxLiteral(self.schema(), self.options().xstq)
+    
+    def param_defs(self, method):
+        """
+        Get parameter definitions.  
+        Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject})
+        @param method: A servic emethod.
+        @type method: I{service.Method}
+        @return: A collection of parameter definitions
+        @rtype: [I{pdef},..]
+        """
+        raise Exception, 'not implemented'
+
+    def get_message(self, method, args, kwargs):
+        """
+        Get the soap message for the specified method, args and soapheaders.
+        This is the entry point for creating the outbound soap message.
+        @param method: The method being invoked.
+        @type method: I{service.Method}
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The soap message.
+        @rtype: str
+        """
+
+        content = self.headercontent(method)
+        header = self.header(content)
+        content = self.bodycontent(method, args, kwargs)
+        body = self.body(content)
+        env = self.envelope(header, body)
+        if self.options().prefixes:
+            body.normalizePrefixes()
+            env.promotePrefixes()
+        else:
+            env.refitPrefixes()
+        return env
+    
+    def get_reply(self, method, reply):
+        """
+        Process the I{reply} for the specified I{method} by sax parsing the I{reply}
+        and then unmarshalling into python object(s).
+        @param method: The name of the invoked method.
+        @type method: str
+        @param reply: The reply XML received after invoking the specified method.
+        @type reply: str
+        @return: The unmarshalled reply.  The returned value is an L{Object} for a
+            I{list} depending on whether the service returns a single object or a 
+            collection.
+        @rtype: tuple ( L{Element}, L{Object} )
+        """
+        reply = self.replyfilter(reply)
+        sax = Parser()
+        replyroot = sax.parse(string=reply)
+        soapenv = replyroot.getChild('Envelope')
+        soapenv.promotePrefixes()
+        soapbody = soapenv.getChild('Body')
+        soapbody = self.multiref.process(soapbody)
+        nodes = self.replycontent(method, soapbody)
+        rtypes = self.returned_types(method)
+        if len(rtypes) > 1:
+            result = self.replycomposite(rtypes, nodes)
+            return (replyroot, result)
+        if len(rtypes) == 1:
+            if rtypes[0].unbounded():
+                result = self.replylist(rtypes[0], nodes)
+                return (replyroot, result)
+            if len(nodes):
+                unmarshaller = self.unmarshaller()
+                resolved = rtypes[0].resolve(nobuiltin=True)
+                result = unmarshaller.process(nodes[0], resolved)
+                return (replyroot, result)
+        return (replyroot, None)
+    
+    def replylist(self, rt, nodes):
+        """
+        Construct a I{list} reply.  This mehod is called when it has been detected
+        that the reply is a list.
+        @param rt: The return I{type}.
+        @type rt: L{suds.xsd.sxbase.SchemaObject}
+        @param nodes: A collection of XML nodes.
+        @type nodes: [L{Element},...]
+        @return: A list of I{unmarshalled} objects.
+        @rtype: [L{Object},...]
+        """
+        result = []
+        resolved = rt.resolve(nobuiltin=True)
+        unmarshaller = self.unmarshaller()
+        for node in nodes:
+            sobject = unmarshaller.process(node, resolved)
+            result.append(sobject)
+        return result
+    
+    def replycomposite(self, rtypes, nodes):
+        """
+        Construct a I{composite} reply.  This method is called when it has been
+        detected that the reply has multiple root nodes.
+        @param rtypes: A list of known return I{types}.
+        @type rtypes: [L{suds.xsd.sxbase.SchemaObject},...]
+        @param nodes: A collection of XML nodes.
+        @type nodes: [L{Element},...]
+        @return: The I{unmarshalled} composite object.
+        @rtype: L{Object},...
+        """
+        dictionary = {}
+        for rt in rtypes:
+            dictionary[rt.name] = rt
+        unmarshaller = self.unmarshaller()
+        composite = Factory.object('reply')
+        for node in nodes:
+            tag = node.name
+            rt = dictionary.get(tag, None)
+            if rt is None:
+                if node.get('id') is None:
+                    raise Exception('<%s/> not mapped to message part' % tag)
+                else:
+                    continue
+            resolved = rt.resolve(nobuiltin=True)
+            sobject = unmarshaller.process(node, resolved)
+            if rt.unbounded():
+                value = getattr(composite, tag, None)
+                if value is None:
+                    value = []
+                    setattr(composite, tag, value)
+                value.append(sobject)
+            else:
+                setattr(composite, tag, sobject)
+        return composite
+    
+    def get_fault(self, reply):
+        """
+        Extract the fault from the specified soap reply.  If I{faults} is True, an
+        exception is raised.  Otherwise, the I{unmarshalled} fault L{Object} is
+        returned.  This method is called when the server raises a I{web fault}.
+        @param reply: A soap reply message.
+        @type reply: str
+        @return: A fault object.
+        @rtype: tuple ( L{Element}, L{Object} )
+        """
+        reply = self.replyfilter(reply)
+        sax = Parser()
+        faultroot = sax.parse(string=reply)
+        soapenv = faultroot.getChild('Envelope')
+        soapbody = soapenv.getChild('Body')
+        fault = soapbody.getChild('Fault')
+        unmarshaller = self.unmarshaller(False)
+        p = unmarshaller.process(fault)
+        if self.options().faults:
+            raise WebFault(p, faultroot)
+        return (faultroot, p.detail)
+    
+    def mkparam(self, method, pdef, object):
+        """
+        Builds a parameter for the specified I{method} using the parameter
+        definition (pdef) and the specified value (object).
+        @param method: A method name.
+        @type method: str
+        @param pdef: A parameter definition.
+        @type pdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
+        @param object: The parameter value.
+        @type object: any
+        @return: The parameter fragment.
+        @rtype: L{Element}
+        """
+        marshaller = self.marshaller()
+        content = \
+            Content(tag=pdef[0],
+                    value=object, 
+                    type=pdef[1], 
+                    real=pdef[1].resolve())
+        return marshaller.process(content)
+    
+    def mkheader(self, method, hdef, object):
+        """
+        Builds a soapheader for the specified I{method} using the header
+        definition (hdef) and the specified value (object).
+        @param method: A method name.
+        @type method: str
+        @param hdef: A header definition.
+        @type hdef: tuple: (I{name}, L{xsd.sxbase.SchemaObject})
+        @param object: The header value.
+        @type object: any
+        @return: The parameter fragment.
+        @rtype: L{Element}
+        """
+        marshaller = self.marshaller()
+        if isinstance(object, (list, tuple)):
+            tags = []
+            for item in object:
+                tags.append(self.mkheader(method, hdef, item))
+            return tags
+        content = Content(tag=hdef[0], value=object, type=hdef[1])
+        return marshaller.process(content)
+            
+    def envelope(self, header, body):
+        """
+        Build the B{<Envelope/>} for an soap outbound message.
+        @param header: The soap message B{header}.
+        @type header: L{Element}
+        @param body: The soap message B{body}.
+        @type body: L{Element}
+        @return: The soap envelope containing the body and header.
+        @rtype: L{Element}
+        """
+        env = Element('Envelope', ns=envns)
+        env.addPrefix(Namespace.xsins[0], Namespace.xsins[1])
+        env.append(header)
+        env.append(body)
+        return env
+    
+    def header(self, content):
+        """
+        Build the B{<Body/>} for an soap outbound message.
+        @param content: The header content.
+        @type content: L{Element}
+        @return: the soap body fragment.
+        @rtype: L{Element}
+        """
+        header = Element('Header', ns=envns)
+        header.append(content)
+        return header
+    
+    def bodycontent(self, method, args, kwargs):
+        """
+        Get the content for the soap I{body} node.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param args: method parameter values
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The xml content for the <body/>
+        @rtype: [L{Element},..]
+        """
+        raise Exception, 'not implemented'
+    
+    def headercontent(self, method):
+        """
+        Get the content for the soap I{Header} node.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: The xml content for the <body/>
+        @rtype: [L{Element},..]
+        """
+        n = 0
+        content = []
+        wsse = self.options().wsse
+        if wsse is not None:
+            content.append(wsse.xml())
+        headers = self.options().soapheaders
+        if not isinstance(headers, (tuple,list,dict)):
+            headers = (headers,)
+        if len(headers) == 0:
+            return content
+        pts = self.headpart_types(method)
+        if isinstance(headers, (tuple,list)):
+            for header in headers:
+                if isinstance(header, Element):
+                    content.append(deepcopy(header))
+                    continue
+                if len(pts) == n: break
+                h = self.mkheader(method, pts[n], header)
+                ns = pts[n][1].namespace('ns0')
+                h.setPrefix(ns[0], ns[1])
+                content.append(h)
+                n += 1
+        else:
+            for pt in pts:
+                header = headers.get(pt[0])
+                if header is None:
+                    continue
+                h = self.mkheader(method, pt, header)
+                ns = pt[1].namespace('ns0')
+                h.setPrefix(ns[0], ns[1])
+                content.append(h)
+        return content
+    
+    def replycontent(self, method, body):
+        """
+        Get the reply body content.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param body: The soap body
+        @type body: L{Element}
+        @return: the body content
+        @rtype: [L{Element},...]
+        """
+        raise Exception, 'not implemented'
+    
+    def body(self, content):
+        """
+        Build the B{<Body/>} for an soap outbound message.
+        @param content: The body content.
+        @type content: L{Element}
+        @return: the soap body fragment.
+        @rtype: L{Element}
+        """
+        body = Element('Body', ns=envns)
+        body.append(content)
+        return body
+    
+    def bodypart_types(self, method, input=True):
+        """
+        Get a list of I{parameter definitions} (pdef) defined for the specified method.
+        Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject})
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param input: Defines input/output message.
+        @type input: boolean
+        @return:  A list of parameter definitions
+        @rtype: [I{pdef},]
+        """
+        result = []
+        if input:
+            parts = method.soap.input.body.parts
+        else:
+            parts = method.soap.output.body.parts
+        for p in parts:
+            if p.element is not None:
+                query = ElementQuery(p.element)
+            else:
+                query = TypeQuery(p.type)
+            pt = query.execute(self.schema())
+            if pt is None:
+                raise TypeNotFound(query.ref)
+            if p.type is not None:
+                pt = PartElement(p.name, pt)
+            if input:
+                if pt.name is None:
+                    result.append((p.name, pt))
+                else:
+                    result.append((pt.name, pt))
+            else:
+                result.append(pt)
+        return result
+    
+    def headpart_types(self, method, input=True):
+        """
+        Get a list of I{parameter definitions} (pdef) defined for the specified method.
+        Each I{pdef} is a tuple (I{name}, L{xsd.sxbase.SchemaObject})
+        @param method: A service method.
+        @type method: I{service.Method}
+        @param input: Defines input/output message.
+        @type input: boolean
+        @return:  A list of parameter definitions
+        @rtype: [I{pdef},]
+        """
+        result = []
+        if input:
+            headers = method.soap.input.headers
+        else:
+            headers = method.soap.output.headers
+        for header in headers:
+            part = header.part
+            if part.element is not None:
+                query = ElementQuery(part.element)
+            else:
+                query = TypeQuery(part.type)
+            pt = query.execute(self.schema())
+            if pt is None:
+                raise TypeNotFound(query.ref)
+            if part.type is not None:
+                pt = PartElement(part.name, pt)
+            if input:
+                if pt.name is None:
+                    result.append((part.name, pt))
+                else:
+                    result.append((pt.name, pt))
+            else:
+                result.append(pt)
+        return result
+    
+    def returned_types(self, method):
+        """
+        Get the L{xsd.sxbase.SchemaObject} returned by the I{method}.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: The name of the type return by the method.
+        @rtype: [I{rtype},..]
+        """
+        result = []
+        for rt in self.bodypart_types(method, input=False):
+            result.append(rt)
+        return result
+
+
+class PartElement(SchemaElement):
+    """
+    A part used to represent a message part when the part
+    references a schema type and thus assumes to be an element.
+    @ivar resolved: The part type.
+    @type resolved: L{suds.xsd.sxbase.SchemaObject}
+    """
+    
+    def __init__(self, name, resolved):
+        """
+        @param name: The part name.
+        @type name: str
+        @param resolved: The part type.
+        @type resolved: L{suds.xsd.sxbase.SchemaObject}
+        """
+        root = Element('element', ns=Namespace.xsdns)
+        SchemaElement.__init__(self, resolved.schema, root)
+        self.__resolved = resolved
+        self.name = name
+        self.form_qualified = False
+        
+    def implany(self):
+        return self
+    
+    def optional(self):
+        return True
+        
+    def namespace(self, prefix=None):
+        return Namespace.default
+        
+    def resolve(self, nobuiltin=False):
+        if nobuiltin and self.__resolved.builtin():
+            return self
+        else:
+            return self.__resolved
+    
\ No newline at end of file
diff --git a/suds/bindings/document.py b/suds/bindings/document.py
new file mode 100644
index 0000000..cace0d5
--- /dev/null
+++ b/suds/bindings/document.py
@@ -0,0 +1,160 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides classes for the (WS) SOAP I{document/literal}.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.bindings.binding import Binding
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+
+class Document(Binding):
+    """
+    The document/literal style.  Literal is the only (@use) supported
+    since document/encoded is pretty much dead.
+    Although the soap specification supports multiple documents within the soap
+    <body/>, it is very uncommon.  As such, suds presents an I{RPC} view of
+    service methods defined with a single document parameter.  This is done so 
+    that the user can pass individual parameters instead of one, single document.
+    To support the complete specification, service methods defined with multiple documents
+    (multiple message parts), must present a I{document} view for that method.
+    """
+        
+    def bodycontent(self, method, args, kwargs):
+        #
+        # The I{wrapped} vs I{bare} style is detected in 2 ways.
+        # If there is 2+ parts in the message then it is I{bare}.
+        # If there is only (1) part and that part resolves to a builtin then
+        # it is I{bare}.  Otherwise, it is I{wrapped}.
+        #
+        if not len(method.soap.input.body.parts):
+            return ()
+        wrapped = method.soap.input.body.wrapped
+        if wrapped:
+            pts = self.bodypart_types(method)
+            root = self.document(pts[0])
+        else:
+            root = []
+        n = 0
+        for pd in self.param_defs(method):
+            if n < len(args):
+                value = args[n]
+            else:
+                value = kwargs.get(pd[0])
+            n += 1
+            p = self.mkparam(method, pd, value)
+            if p is None:
+                continue
+            if not wrapped:
+                ns = pd[1].namespace('ns0')
+                p.setPrefix(ns[0], ns[1])
+            root.append(p)
+        return root
+
+    def replycontent(self, method, body):
+        wrapped = method.soap.output.body.wrapped
+        if wrapped:
+            return body[0].children
+        else:
+            return body.children
+        
+    def document(self, wrapper):
+        """
+        Get the document root.  For I{document/literal}, this is the
+        name of the wrapper element qualifed by the schema tns.
+        @param wrapper: The method name.
+        @type wrapper: L{xsd.sxbase.SchemaObject}
+        @return: A root element.
+        @rtype: L{Element}
+        """
+        tag = wrapper[1].name
+        ns = wrapper[1].namespace('ns0')
+        d = Element(tag, ns=ns)
+        return d
+    
+    def mkparam(self, method, pdef, object):
+        #
+        # Expand list parameters into individual parameters
+        # each with the type information.  This is because in document
+        # arrays are simply unbounded elements.
+        #
+        if isinstance(object, (list, tuple)):
+            tags = []
+            for item in object:
+                tags.append(self.mkparam(method, pdef, item))
+            return tags
+        else:
+            return Binding.mkparam(self, method, pdef, object)
+        
+    def param_defs(self, method):
+        #
+        # Get parameter definitions for document literal.
+        # The I{wrapped} vs I{bare} style is detected in 2 ways.
+        # If there is 2+ parts in the message then it is I{bare}.
+        # If there is only (1) part and that part resolves to a builtin then
+        # it is I{bare}.  Otherwise, it is I{wrapped}.
+        #
+        pts = self.bodypart_types(method)
+        wrapped = method.soap.input.body.wrapped
+        if not wrapped:
+            return pts
+        result = []
+        # wrapped
+        for p in pts:
+            resolved = p[1].resolve()
+            for child, ancestry in resolved:
+                if child.isattr():
+                    continue
+                if self.bychoice(ancestry):
+                    log.debug(
+                        '%s\ncontained by <choice/>, excluded as param for %s()',
+                        child,
+                        method.name)
+                    continue
+                result.append((child.name, child))
+        return result
+    
+    def returned_types(self, method):
+        result = []
+        wrapped = method.soap.output.body.wrapped
+        rts = self.bodypart_types(method, input=False)
+        if wrapped:
+            for pt in rts:
+                resolved = pt.resolve(nobuiltin=True)
+                for child, ancestry in resolved:
+                    result.append(child)
+                break
+        else:
+            result += rts
+        return result
+    
+    def bychoice(self, ancestry):
+        """
+        The ancestry contains a <choice/>
+        @param ancestry: A list of ancestors.
+        @type ancestry: list
+        @return: True if contains <choice/>
+        @rtype: boolean
+        """
+        for x in ancestry:
+            if x.choice():
+                return True
+        return False
\ No newline at end of file
diff --git a/suds/bindings/multiref.py b/suds/bindings/multiref.py
new file mode 100644
index 0000000..e539592
--- /dev/null
+++ b/suds/bindings/multiref.py
@@ -0,0 +1,126 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides classes for handling soap multirefs.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+soapenc = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
+
+class MultiRef:
+    """
+    Resolves and replaces multirefs.
+    @ivar nodes: A list of non-multiref nodes.
+    @type nodes: list
+    @ivar catalog: A dictionary of multiref nodes by id.
+    @type catalog: dict 
+    """
+    
+    def __init__(self):
+        self.nodes = []
+        self.catalog = {}
+    
+    def process(self, body):
+        """
+        Process the specified soap envelope body and replace I{multiref} node
+        references with the contents of the referenced node.
+        @param body: A soap envelope body node.
+        @type body: L{Element}
+        @return: The processed I{body}
+        @rtype: L{Element}
+        """
+        self.nodes = []
+        self.catalog = {}
+        self.build_catalog(body)
+        self.update(body)
+        body.children = self.nodes
+        return body
+    
+    def update(self, node):
+        """
+        Update the specified I{node} by replacing the I{multiref} references with
+        the contents of the referenced nodes and remove the I{href} attribute.
+        @param node: A node to update.
+        @type node: L{Element}
+        @return: The updated node
+        @rtype: L{Element}
+        """
+        self.replace_references(node)
+        for c in node.children:
+            self.update(c)
+        return node
+            
+    def replace_references(self, node):
+        """
+        Replacing the I{multiref} references with the contents of the 
+        referenced nodes and remove the I{href} attribute.  Warning:  since
+        the I{ref} is not cloned, 
+        @param node: A node to update.
+        @type node: L{Element}
+        """
+        href = node.getAttribute('href')
+        if href is None:
+            return
+        id = href.getValue()
+        ref = self.catalog.get(id)
+        if ref is None:
+            log.error('soap multiref: %s, not-resolved', id)
+            return
+        node.append(ref.children)
+        node.setText(ref.getText())
+        for a in ref.attributes:
+            if a.name != 'id':
+                node.append(a)
+        node.remove(href)
+            
+    def build_catalog(self, body):
+        """
+        Create the I{catalog} of multiref nodes by id and the list of
+        non-multiref nodes.
+        @param body: A soap envelope body node.
+        @type body: L{Element}
+        """
+        for child in body.children:
+            if self.soaproot(child):
+                self.nodes.append(child)
+            id = child.get('id')
+            if id is None: continue
+            key = '#%s' % id
+            self.catalog[key] = child
+
+    def soaproot(self, node):
+        """
+        Get whether the specified I{node} is a soap encoded root.
+        This is determined by examining @soapenc:root='1'.
+        The node is considered to be a root when the attribute
+        is not specified.
+        @param node: A node to evaluate.
+        @type node: L{Element}
+        @return: True if a soap encoded root.
+        @rtype: bool
+        """
+        root = node.getAttribute('root', ns=soapenc)
+        if root is None:
+            return True
+        else:
+            return ( root.value == '1' )
+        
\ No newline at end of file
diff --git a/suds/bindings/rpc.py b/suds/bindings/rpc.py
new file mode 100644
index 0000000..f780aa4
--- /dev/null
+++ b/suds/bindings/rpc.py
@@ -0,0 +1,98 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides classes for the (WS) SOAP I{rpc/literal} and I{rpc/encoded} bindings.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx.encoded import Encoded as MxEncoded
+from suds.umx.encoded import Encoded as UmxEncoded
+from suds.bindings.binding import Binding, envns
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+
+encns = ('SOAP-ENC', 'http://schemas.xmlsoap.org/soap/encoding/')
+
+class RPC(Binding):
+    """
+    RPC/Literal binding style.
+    """
+    
+    def param_defs(self, method):
+        return self.bodypart_types(method)
+        
+    def envelope(self, header, body):
+        env = Binding.envelope(self, header, body)
+        env.addPrefix(encns[0], encns[1])
+        env.set('%s:encodingStyle' % envns[0], 
+                'http://schemas.xmlsoap.org/soap/encoding/')
+        return env
+        
+    def bodycontent(self, method, args, kwargs):
+        n = 0
+        root = self.method(method)
+        for pd in self.param_defs(method):
+            if n < len(args):
+                value = args[n]
+            else:
+                value = kwargs.get(pd[0])
+            p = self.mkparam(method, pd, value)
+            if p is not None:
+                root.append(p)
+            n += 1
+        return root
+    
+    def replycontent(self, method, body):
+        return body[0].children
+        
+    def method(self, method):
+        """
+        Get the document root.  For I{rpc/(literal|encoded)}, this is the
+        name of the method qualifed by the schema tns.
+        @param method: A service method.
+        @type method: I{service.Method}
+        @return: A root element.
+        @rtype: L{Element}
+        """
+        ns = method.soap.input.body.namespace
+        if ns[0] is None:
+            ns = ('ns0', ns[1])
+        method = Element(method.name, ns=ns)
+        return method
+    
+
+class Encoded(RPC):
+    """
+    RPC/Encoded (section 5)  binding style.
+    """
+
+    def marshaller(self):
+        return MxEncoded(self.schema())
+
+    def unmarshaller(self, typed=True):
+        """
+        Get the appropriate XML decoder.
+        @return: Either the (basic|typed) unmarshaller.
+        @rtype: L{UmxTyped}
+        """
+        if typed:
+            return UmxEncoded(self.schema())
+        else:
+            return RPC.unmarshaller(self, typed)
diff --git a/suds/builder.py b/suds/builder.py
new file mode 100644
index 0000000..95f81c4
--- /dev/null
+++ b/suds/builder.py
@@ -0,0 +1,116 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{builder} module provides an wsdl/xsd defined types factory
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sudsobject import Factory
+
+log = getLogger(__name__)
+
+
+class Builder:
+    """ Builder used to construct an object for types defined in the schema """
+    
+    def __init__(self, resolver):
+        """
+        @param resolver: A schema object name resolver.
+        @type resolver: L{resolver.Resolver}
+        """
+        self.resolver = resolver
+        
+    def build(self, name):
+        """ build a an object for the specified typename as defined in the schema """
+        if isinstance(name, basestring):
+            type = self.resolver.find(name)
+            if type is None:
+                raise TypeNotFound(name)
+        else:
+            type = name
+        cls = type.name
+        if len(type):
+            data = Factory.object(cls)
+        else:
+            data = Factory.property(cls)
+        resolved = type.resolve()
+        md = data.__metadata__
+        md.sxtype = resolved
+        md.ordering = self.ordering(resolved)
+        history = []
+        self.add_attributes(data, resolved)
+        for child, ancestry in type.children():
+            if self.skip_child(child, ancestry):
+                continue
+            self.process(data, child, history[:])
+        return data
+            
+    def process(self, data, type, history):
+        """ process the specified type then process its children """
+        if type in history:
+            return
+        if type.enum():
+            return
+        history.append(type)
+        resolved = type.resolve()
+        value = None
+        if type.unbounded():
+            value = []
+        else:
+            if len(resolved) > 0:
+                value = Factory.object(resolved.name)
+                md = value.__metadata__
+                md.sxtype = resolved
+                md.ordering = self.ordering(resolved)
+        setattr(data, type.name, value)
+        if value is not None:
+            data = value
+        if not isinstance(data, list):
+            self.add_attributes(data, resolved)
+            for child, ancestry in resolved.children():
+                if self.skip_child(child, ancestry):
+                    continue
+                self.process(data, child, history[:])
+
+    def add_attributes(self, data, type):
+        """ add required attributes """
+        for attr, ancestry in type.attributes():
+            name = '_%s' % attr.name
+            value = attr.get_default()
+            setattr(data, name, value)
+                
+    def skip_child(self, child, ancestry):
+        """ get whether or not to skip the specified child """
+        if child.any(): return True
+        for x in ancestry:
+            if x.choice():
+                return True
+        return False
+    
+    def ordering(self, type):
+        """ get the ordering """
+        result = []
+        for child, ancestry in type.resolve():
+            name = child.name
+            if child.name is None:
+                continue
+            if child.isattr():
+                name = '_%s' % child.name
+            result.append(name)
+        return result
+            
\ No newline at end of file
diff --git a/suds/cache.py b/suds/cache.py
new file mode 100644
index 0000000..d83d049
--- /dev/null
+++ b/suds/cache.py
@@ -0,0 +1,289 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains basic caching classes.
+"""
+
+import os
+from tempfile import gettempdir as tmp
+from suds.transport import *
+from datetime import datetime as dt
+from datetime import timedelta
+from cStringIO import StringIO
+from logging import getLogger
+try:
+    import cPickle as pickle
+except:
+    import pickle
+
+log = getLogger(__name__)
+
+
+class Cache:
+    """
+    An object object cache.
+    """
+
+    def get(self, id):
+        """
+        Get a object from the cache by ID.
+        @param id: The object ID.
+        @type id: str
+        @return: The object, else None
+        @rtype: any
+        """
+        raise Exception('not-implemented')
+    
+    def getf(self, id):
+        """
+        Get a object from the cache by ID.
+        @param id: The object ID.
+        @type id: str
+        @return: The object, else None
+        @rtype: any
+        """
+        raise Exception('not-implemented')
+    
+    def put(self, id, object):
+        """
+        Put a object into the cache.
+        @param id: The object ID.
+        @type id: str
+        @param object: The object to add.
+        @type object: any
+        """
+        raise Exception('not-implemented')
+    
+    def putf(self, id, fp):
+        """
+        Write a fp into the cache.
+        @param id: The object ID.
+        @type id: str
+        @param fp: File pointer.
+        @type fp: file-like object.
+        """
+        raise Exception('not-implemented')
+    
+    def purge(self, id):
+        """
+        Purge a object from the cache by id.
+        @param id: A object ID.
+        @type id: str        
+        """
+        raise Exception('not-implemented')
+    
+    def clear(self):
+        """
+        Clear all objects from the cache.
+        """
+        raise Exception('not-implemented')
+    
+
+class NoCache(Cache):
+    """
+    The passthru object cache.
+    """
+    
+    def get(self, id):
+        return None
+    
+    def getf(self, id):
+        return None
+    
+    def put(self, id, object):
+        pass
+
+    def putf(self, id, fp):
+        pass
+
+
+class FileCache(Cache):
+    """
+    A file-based URL cache.
+    @cvar fnprefix: The file name prefix.
+    @type fnprefix: str
+    @ivar fnsuffix: The file name suffix.
+    @type fnsuffix: str
+    @ivar duration: The cached file duration which defines how
+        long the file will be cached.
+    @type duration: (unit, value)
+    @ivar location: The directory for the cached files.
+    @type location: str
+    """
+    fnprefix = 'suds'
+    fnsuffix = 'gcf'
+    units = ('months', 'weeks', 'days', 'hours', 'minutes', 'seconds')
+    
+    def __init__(self, location=None, **duration):
+        """
+        @param location: The directory for the cached files.
+        @type location: str
+        @param duration: The cached file duration which defines how
+            long the file will be cached.  A duration=0 means forever.
+            The duration may be: (months|weeks|days|hours|minutes|seconds).
+        @type duration: {unit:value}
+        """
+        if location is None:
+            location = os.path.join(tmp(), 'suds')
+        self.location = location
+        self.duration = (None, 0)
+        self.setduration(**duration)
+        
+    def setduration(self, **duration):
+        """
+        Set the caching duration which defines how long the 
+        file will be cached.
+        @param duration: The cached file duration which defines how
+            long the file will be cached.  A duration=0 means forever.
+            The duration may be: (months|weeks|days|hours|minutes|seconds).
+        @type duration: {unit:value}
+        """
+        if len(duration) == 1:
+            arg = duration.items()[0]
+            if not arg[0] in self.units:
+                raise Exception('must be: %s' % str(self.units))
+            self.duration = arg
+        return self
+    
+    def setlocation(self, location):
+        """
+        Set the location (directory) for the cached files.
+        @param location: The directory for the cached files.
+        @type location: str
+        """
+        self.location = location
+            
+    def mktmp(self):
+        """
+        Make the I{location} directory if it doesn't already exits.
+        """
+        try:
+            if not os.path.isdir(self.location):
+                os.makedirs(self.location)
+        except:
+            log.debug(self.location, exc_info=1)
+        return self
+    
+    def put(self, id, bfr):
+        try:
+            fn = self.__fn(id)
+            f = self.open(fn, 'w')
+            f.write(bfr)
+            f.close()
+            return bfr
+        except:
+            log.debug(id, exc_info=1)
+            return bfr
+        
+    def putf(self, id, fp):
+        try:
+            fn = self.__fn(id)
+            f = self.open(fn, 'w')
+            f.write(fp.read())
+            f.close()
+            return fp
+        except:
+            log.debug(id, exc_info=1)
+            return fp
+        
+    def get(self, id):
+        try:
+            f = self.getf(id)
+            bfr = f.read()
+            f.close()
+            return bfr
+        except:
+            pass
+    
+    def getf(self, id):
+        try:
+            fn = self.__fn(id)
+            self.validate(fn)
+            return self.open(fn)
+        except:
+            pass
+
+    def validate(self, fn):
+        """
+        Validate that the file has not expired based on the I{duration}.
+        @param fn: The file name.
+        @type fn: str
+        """
+        if self.duration[1] < 1:
+            return
+        created = dt.fromtimestamp(os.path.getctime(fn))
+        d = {self.duration[0] : self.duration[1]}
+        expired = created+timedelta(**d)
+        if expired < dt.now():
+            log.debug('%s expired, deleted', fn)
+            os.remove(fn)
+ 
+    def clear(self):
+        for fn in os.listdir(self.location):
+            if os.path.isdir(fn):
+                continue
+            if fn.startswith(self.fnprefix) and fn.endswith(self.fnsuffix):
+                log.debug('deleted: %s', fn)
+                os.remove(os.path.join(self.location, fn))
+                
+    def purge(self, id):
+        fn = self.__fn(id)
+        try:
+            os.remove(fn)
+        except:
+            pass
+                
+    def open(self, fn, *args):
+        """
+        Open the cache file making sure the directory is created.
+        """
+        self.mktmp()
+        return open(fn, *args)
+    
+    def __fn(self, id):
+        if hasattr(id, 'name') and hasattr(id, 'suffix'):
+            name = id.name
+            suffix = id.suffix
+        else:
+            name = id
+            suffix = self.fnsuffix
+        fn = '%s-%s.%s' % (self.fnprefix, abs(hash(name)), suffix)
+        return os.path.join(self.location, fn)
+
+
+class ObjectCache(FileCache):
+    """
+    Provides pickled object caching.
+    @cvar protocol: The pickling protocol.
+    @type protocol: int
+    """
+    protocol = 2
+    
+    def get(self, id):
+        try:
+            fp = FileCache.getf(self, id)
+            if fp is None:
+                return None
+            else:
+                return pickle.load(fp)
+        except:
+            FileCache.purge(self, id)
+    
+    def put(self, id, object):
+        bfr = pickle.dumps(object, self.protocol)
+        FileCache.put(self, id, bfr)
+        return object
diff --git a/suds/client.py b/suds/client.py
new file mode 100644
index 0000000..b91a7da
--- /dev/null
+++ b/suds/client.py
@@ -0,0 +1,767 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{2nd generation} service proxy provides access to web services.
+See I{README.txt}
+"""
+
+import suds
+import suds.metrics as metrics
+from cookielib import CookieJar
+from suds import *
+from suds.reader import DefinitionsReader
+from suds.transport import TransportError, Request
+from suds.transport.https import HttpAuthenticated
+from suds.servicedefinition import ServiceDefinition
+from suds import sudsobject
+from sudsobject import Factory as InstFactory
+from sudsobject import Object
+from suds.resolver import PathResolver
+from suds.builder import Builder
+from suds.wsdl import Definitions
+from suds.cache import ObjectCache
+from suds.sax.document import Document
+from suds.sax.parser import Parser
+from suds.options import Options
+from suds.properties import Unskin
+from urlparse import urlparse
+from copy import deepcopy
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+class Client(object):
+    """ 
+    A lightweight web services client.
+    I{(2nd generation)} API.
+    @ivar wsdl: The WSDL object.
+    @type wsdl:L{Definitions}
+    @ivar service: The service proxy used to invoke operations.
+    @type service: L{Service}
+    @ivar factory: The factory used to create objects.
+    @type factory: L{Factory}
+    @ivar sd: The service definition
+    @type sd: L{ServiceDefinition}
+    @ivar messages: The last sent/received messages.
+    @type messages: str[2]
+    """
+    @classmethod
+    def items(cls, sobject):
+        """
+        Extract the I{items} from a suds object much like the
+        items() method works on I{dict}.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: A list of items contained in I{sobject}.
+        @rtype: [(key, value),...]
+        """
+        return sudsobject.items(sobject)
+    
+    @classmethod
+    def dict(cls, sobject):
+        """
+        Convert a sudsobject into a dictionary.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: A python dictionary containing the
+            items contained in I{sobject}.
+        @rtype: dict
+        """
+        return sudsobject.asdict(sobject)
+    
+    @classmethod
+    def metadata(cls, sobject):
+        """
+        Extract the metadata from a suds object.
+        @param sobject: A suds object
+        @type sobject: L{Object}
+        @return: The object's metadata
+        @rtype: L{sudsobject.Metadata}
+        """
+        return sobject.__metadata__
+
+    def __init__(self, url, **kwargs):
+        """
+        @param url: The URL for the WSDL.
+        @type url: str
+        @param kwargs: keyword arguments.
+        @see: L{Options}
+        """
+        options = Options()
+        options.transport = HttpAuthenticated()
+        self.options = options
+        options.cache = ObjectCache(days=1)
+        self.set_options(**kwargs)
+        reader = DefinitionsReader(options, Definitions)
+        self.wsdl = reader.open(url)
+        self.factory = Factory(self.wsdl)
+        self.service = ServiceSelector(self, self.wsdl.services)
+        self.sd = []
+        for s in self.wsdl.services:
+            sd = ServiceDefinition(self.wsdl, s)
+            self.sd.append(sd)
+        self.messages = dict(tx=None, rx=None)
+        
+    def set_options(self, **kwargs):
+        """
+        Set options.
+        @param kwargs: keyword arguments.
+        @see: L{Options}
+        """
+        p = Unskin(self.options)
+        p.update(kwargs)
+        
+    def add_prefix(self, prefix, uri):
+        """
+        Add I{static} mapping of an XML namespace prefix to a namespace.
+        This is useful for cases when a wsdl and referenced schemas make heavy
+        use of namespaces and those namespaces are subject to changed.
+        @param prefix: An XML namespace prefix.
+        @type prefix: str
+        @param uri: An XML namespace URI.
+        @type uri: str
+        @raise Exception: when prefix is already mapped.
+        """
+        root = self.wsdl.root
+        mapped = root.resolvePrefix(prefix, None)
+        if mapped is None:
+            root.addPrefix(prefix, uri)
+            return
+        if mapped[1] != uri:
+            raise Exception('"%s" already mapped as "%s"' % (prefix, mapped))
+        
+    def last_sent(self):
+        """
+        Get last sent I{soap} message.
+        @return: The last sent I{soap} message.
+        @rtype: L{Document}
+        """
+        return self.messages.get('tx')
+    
+    def last_received(self):
+        """
+        Get last received I{soap} message.
+        @return: The last received I{soap} message.
+        @rtype: L{Document}
+        """
+        return self.messages.get('rx')
+    
+    def clone(self):
+        """
+        Get a shallow clone of this object.
+        The clone only shares the WSDL.  All other attributes are
+        unique to the cloned object including options.
+        @return: A shallow clone.
+        @rtype: L{Client}
+        """
+        class Uninitialized(Client):
+            def __init__(self):
+                pass
+        clone = Uninitialized()
+        clone.options = Options()
+        cp = Unskin(clone.options)
+        mp = Unskin(self.options)
+        cp.update(deepcopy(mp))
+        clone.wsdl = self.wsdl
+        clone.factory = self.factory
+        clone.service = ServiceSelector(clone, self.wsdl.services)
+        clone.sd = self.sd
+        clone.messages = dict(tx=None, rx=None)
+        return clone
+ 
+    def __str__(self):
+        return unicode(self)
+        
+    def __unicode__(self):
+        s = ['\n']
+        build = suds.__build__.split()
+        s.append('Suds ( https://fedorahosted.org/suds/ )')
+        s.append('  version: %s' % suds.__version__)
+        s.append(' %s  build: %s' % (build[0], build[1]))
+        for sd in self.sd:
+            s.append('\n\n%s' % unicode(sd))
+        return ''.join(s)
+
+
+class Factory:
+    """
+    A factory for instantiating types defined in the wsdl
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{PathResolver}
+    @ivar builder: A schema object builder.
+    @type builder: L{Builder}
+    """
+    
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A schema object.
+        @type wsdl: L{wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.resolver = PathResolver(wsdl)
+        self.builder = Builder(self.resolver)
+    
+    def create(self, name):
+        """
+        create a WSDL type by name
+        @param name: The name of a type defined in the WSDL.
+        @type name: str
+        @return: The requested object.
+        @rtype: L{Object}
+        """
+        timer = metrics.Timer()
+        timer.start()
+        type = self.resolver.find(name)
+        if type is None:
+            raise TypeNotFound(name)
+        if type.enum():
+            result = InstFactory.object(name)
+            for e, a in type.children():
+                setattr(result, e.name, e.name)
+        else:
+            try:
+                result = self.builder.build(type)
+            except Exception, e:
+                log.error("create '%s' failed", name, exc_info=True)
+                raise BuildError(name, e)
+        timer.stop()
+        metrics.log.debug('%s created: %s', name, timer)
+        return result
+    
+    def separator(self, ps):
+        """
+        Set the path separator.
+        @param ps: The new path separator.
+        @type ps: char
+        """
+        self.resolver = PathResolver(self.wsdl, ps)
+
+
+class ServiceSelector:
+    """
+    The B{service} selector is used to select a web service.
+    In most cases, the wsdl only defines (1) service in which access
+    by subscript is passed through to a L{PortSelector}.  This is also the
+    behavior when a I{default} service has been specified.  In cases
+    where multiple services have been defined and no default has been
+    specified, the service is found by name (or index) and a L{PortSelector}
+    for the service is returned.  In all cases, attribute access is
+    forwarded to the L{PortSelector} for either the I{first} service or the
+    I{default} service (when specified).
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __services: A list of I{wsdl} services.
+    @type __services: list
+    """
+    def __init__(self, client, services):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param services: A list of I{wsdl} services.
+        @type services: list
+        """
+        self.__client = client
+        self.__services = services
+    
+    def __getattr__(self, name):
+        """
+        Request to access an attribute is forwarded to the
+        L{PortSelector} for either the I{first} service or the
+        I{default} service (when specified).
+        @param name: The name of a method.
+        @type name: str
+        @return: A L{PortSelector}.
+        @rtype: L{PortSelector}. 
+        """
+        default = self.__ds()
+        if default is None:
+            port = self.__find(0)
+        else:
+            port = default
+        return getattr(port, name)
+    
+    def __getitem__(self, name):
+        """
+        Provides selection of the I{service} by name (string) or 
+        index (integer).  In cases where only (1) service is defined
+        or a I{default} has been specified, the request is forwarded
+        to the L{PortSelector}.
+        @param name: The name (or index) of a service.
+        @type name: (int|str)
+        @return: A L{PortSelector} for the specified service.
+        @rtype: L{PortSelector}. 
+        """
+        if len(self.__services) == 1:
+            port = self.__find(0)
+            return port[name]
+        default = self.__ds()
+        if default is not None:
+            port = default
+            return port[name]
+        return self.__find(name)
+    
+    def __find(self, name):
+        """
+        Find a I{service} by name (string) or index (integer).
+        @param name: The name (or index) of a service.
+        @type name: (int|str)
+        @return: A L{PortSelector} for the found service.
+        @rtype: L{PortSelector}. 
+        """
+        service = None
+        if not len(self.__services):
+            raise Exception, 'No services defined'
+        if isinstance(name, int):
+            try:
+                service = self.__services[name]
+                name = service.name
+            except IndexError:
+                raise ServiceNotFound, 'at [%d]' % name
+        else:
+            for s in self.__services:
+                if name == s.name:
+                    service = s
+                    break
+        if service is None:
+            raise ServiceNotFound, name
+        return PortSelector(self.__client, service.ports, name)
+    
+    def __ds(self):
+        """
+        Get the I{default} service if defined in the I{options}.
+        @return: A L{PortSelector} for the I{default} service.
+        @rtype: L{PortSelector}. 
+        """
+        ds = self.__client.options.service
+        if ds is None:
+            return None
+        else:
+            return self.__find(ds)
+
+
+class PortSelector:
+    """
+    The B{port} selector is used to select a I{web service} B{port}.
+    In cases where multiple ports have been defined and no default has been
+    specified, the port is found by name (or index) and a L{MethodSelector}
+    for the port is returned.  In all cases, attribute access is
+    forwarded to the L{MethodSelector} for either the I{first} port or the
+    I{default} port (when specified).
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __ports: A list of I{service} ports.
+    @type __ports: list
+    @ivar __qn: The I{qualified} name of the port (used for logging).
+    @type __qn: str
+    """
+    def __init__(self, client, ports, qn):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param ports: A list of I{service} ports.
+        @type ports: list
+        @param qn: The name of the service.
+        @type qn: str
+        """
+        self.__client = client
+        self.__ports = ports
+        self.__qn = qn
+    
+    def __getattr__(self, name):
+        """
+        Request to access an attribute is forwarded to the
+        L{MethodSelector} for either the I{first} port or the
+        I{default} port (when specified).
+        @param name: The name of a method.
+        @type name: str
+        @return: A L{MethodSelector}.
+        @rtype: L{MethodSelector}. 
+        """
+        default = self.__dp()
+        if default is None:
+            m = self.__find(0)
+        else:
+            m = default
+        return getattr(m, name)
+    
+    def __getitem__(self, name):
+        """
+        Provides selection of the I{port} by name (string) or 
+        index (integer).  In cases where only (1) port is defined
+        or a I{default} has been specified, the request is forwarded
+        to the L{MethodSelector}.
+        @param name: The name (or index) of a port.
+        @type name: (int|str)
+        @return: A L{MethodSelector} for the specified port.
+        @rtype: L{MethodSelector}. 
+        """
+        default = self.__dp()
+        if default is None:
+            return self.__find(name)
+        else:
+            return default
+    
+    def __find(self, name):
+        """
+        Find a I{port} by name (string) or index (integer).
+        @param name: The name (or index) of a port.
+        @type name: (int|str)
+        @return: A L{MethodSelector} for the found port.
+        @rtype: L{MethodSelector}. 
+        """
+        port = None
+        if not len(self.__ports):
+            raise Exception, 'No ports defined: %s' % self.__qn
+        if isinstance(name, int):
+            qn = '%s[%d]' % (self.__qn, name)
+            try:
+                port = self.__ports[name]
+            except IndexError:
+                raise PortNotFound, qn
+        else:
+            qn = '.'.join((self.__qn, name))
+            for p in self.__ports:
+                if name == p.name:
+                    port = p
+                    break
+        if port is None:
+            raise PortNotFound, qn
+        qn = '.'.join((self.__qn, port.name))
+        return MethodSelector(self.__client, port.methods, qn)
+    
+    def __dp(self):
+        """
+        Get the I{default} port if defined in the I{options}.
+        @return: A L{MethodSelector} for the I{default} port.
+        @rtype: L{MethodSelector}. 
+        """
+        dp = self.__client.options.port
+        if dp is None:
+            return None
+        else:
+            return self.__find(dp)
+    
+
+class MethodSelector:
+    """
+    The B{method} selector is used to select a B{method} by name.
+    @ivar __client: A suds client.
+    @type __client: L{Client}
+    @ivar __methods: A dictionary of methods.
+    @type __methods: dict
+    @ivar __qn: The I{qualified} name of the method (used for logging).
+    @type __qn: str
+    """
+    def __init__(self, client, methods, qn):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param methods: A dictionary of methods.
+        @type methods: dict
+        @param qn: The I{qualified} name of the port.
+        @type qn: str
+        """
+        self.__client = client
+        self.__methods = methods
+        self.__qn = qn
+    
+    def __getattr__(self, name):
+        """
+        Get a method by name and return it in an I{execution wrapper}.
+        @param name: The name of a method.
+        @type name: str
+        @return: An I{execution wrapper} for the specified method name.
+        @rtype: L{Method}
+        """
+        return self[name]
+    
+    def __getitem__(self, name):
+        """
+        Get a method by name and return it in an I{execution wrapper}.
+        @param name: The name of a method.
+        @type name: str
+        @return: An I{execution wrapper} for the specified method name.
+        @rtype: L{Method}
+        """
+        m = self.__methods.get(name)
+        if m is None:
+            qn = '.'.join((self.__qn, name))
+            raise MethodNotFound, qn
+        return Method(self.__client, m)
+
+
+class Method:
+    """
+    The I{method} (namespace) object.
+    @ivar client: A client object.
+    @type client: L{Client}
+    @ivar method: A I{wsdl} method.
+    @type I{wsdl} Method.
+    """
+
+    def __init__(self, client, method):
+        """
+        @param client: A client object.
+        @type client: L{Client}
+        @param method: A I{raw} method.
+        @type I{raw} Method.
+        """
+        self.client = client
+        self.method = method
+    
+    def __call__(self, *args, **kwargs):
+        """
+        Invoke the method.
+        """
+        clientclass = self.clientclass(kwargs)
+        client = clientclass(self.client, self.method)
+        if not self.faults():
+            try:
+                return client.invoke(args, kwargs)
+            except WebFault, e:
+                return (500, e)
+        else:
+            return client.invoke(args, kwargs)
+        
+    def faults(self):
+        """ get faults option """
+        return self.client.options.faults
+        
+    def clientclass(self, kwargs):
+        """ get soap client class """
+        if SimClient.simulation(kwargs):
+            return SimClient
+        else:
+            return SoapClient
+
+
+class SoapClient:
+    """
+    A lightweight soap based web client B{**not intended for external use}
+    @ivar service: The target method.
+    @type service: L{Service}
+    @ivar method: A target method.
+    @type method: L{Method}
+    @ivar options: A dictonary of options.
+    @type options: dict
+    @ivar cookiejar: A cookie jar.
+    @type cookiejar: libcookie.CookieJar
+    """
+
+    def __init__(self, client, method):
+        """
+        @param client: A suds client.
+        @type client: L{Client}
+        @param method: A target method.
+        @type method: L{Method}
+        """
+        self.client = client
+        self.method = method
+        self.options = client.options
+        self.cookiejar = CookieJar()
+        
+    def invoke(self, args, kwargs):
+        """
+        Send the required soap message to invoke the specified method
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The result of the method invocation.
+        @rtype: I{builtin}|I{subclass of} L{Object}
+        """
+        timer = metrics.Timer()
+        timer.start()
+        result = None
+        binding = self.method.binding.input
+        msg = binding.get_message(self.method, args, kwargs)
+        timer.stop()
+        metrics.log.debug(
+                "message for '%s' created: %s",
+                self.method.name, timer)
+        timer.start()
+        result = self.send(msg)
+        timer.stop()
+        metrics.log.debug(
+                "method '%s' invoked: %s",
+                self.method.name, timer)
+        return result
+    
+    def send(self, msg):
+        """
+        Send soap message.
+        @param msg: A soap message to send.
+        @type msg: basestring
+        @return: The reply to the sent message.
+        @rtype: I{builtin} or I{subclass of} L{Object}
+        """
+        result = None
+        location = self.location()
+        binding = self.method.binding.input
+        transport = self.options.transport
+        retxml = self.options.retxml
+        log.debug('sending to (%s)\nmessage:\n%s', location, msg)
+        try:
+            self.last_sent(Document(msg))
+            request = Request(location, str(msg))
+            request.headers = self.headers()
+            reply = transport.send(request)
+            if retxml:
+                result = reply.message
+            else:
+                result = self.succeeded(binding, reply.message)
+        except TransportError, e:
+            if e.httpcode in (202,204):
+                result = None
+            else:
+                log.error(self.last_sent())
+                result = self.failed(binding, e)
+        return result
+    
+    def headers(self):
+        """
+        Get http headers or the http/https request.
+        @return: A dictionary of header/values.
+        @rtype: dict
+        """
+        action = self.method.soap.action
+        stock = { 'Content-Type' : 'text/xml', 'SOAPAction': action }
+        result = dict(stock, **self.options.headers)
+        log.debug('headers = %s', result)
+        return result
+    
+    def succeeded(self, binding, reply):
+        """
+        Request succeeded, process the reply
+        @param binding: The binding to be used to process the reply.
+        @type binding: L{bindings.binding.Binding}
+        @return: The method result.
+        @rtype: I{builtin}, L{Object}
+        @raise WebFault: On server.
+        """
+        log.debug('http succeeded:\n%s', reply)
+        if len(reply) > 0:
+            r, p = binding.get_reply(self.method, reply)
+            self.last_received(r)
+            if self.options.faults:
+                return p
+            else:
+                return (200, p)
+        else:
+            if self.options.faults:
+                return None
+            else:
+                return (200, None)
+        
+    def failed(self, binding, error):
+        """
+        Request failed, process reply based on reason
+        @param binding: The binding to be used to process the reply.
+        @type binding: L{suds.bindings.binding.Binding}
+        @param error: The http error message
+        @type error: L{transport.TransportError}
+        """
+        status, reason = (error.httpcode, tostr(error))
+        reply = error.fp.read()
+        log.debug('http failed:\n%s', reply)
+        if status == 500:
+            if len(reply) > 0:
+                r, p = binding.get_fault(reply)
+                self.last_received(r)
+                return (status, p)
+            else:
+                return (status, None)
+        if self.options.faults:
+            raise Exception((status, reason))
+        else:
+            return (status, None)
+
+    def location(self):
+        p = Unskin(self.options)
+        return p.get('location', self.method.location)
+    
+    def last_sent(self, d=None):
+        key = 'tx'
+        messages = self.client.messages
+        if d is None:
+            return messages.get(key)
+        else:
+            messages[key] = d
+        
+    def last_received(self, d=None):
+        key = 'rx'
+        messages = self.client.messages
+        if d is None:
+            return messages.get(key)
+        else:
+            messages[key] = d
+
+
+class SimClient(SoapClient):
+    """
+    Loopback client used for message/reply simulation.
+    """
+    
+    injkey = '__inject'
+    
+    @classmethod
+    def simulation(cls, kwargs):
+        """ get whether loopback has been specified in the I{kwargs}. """
+        return kwargs.has_key(SimClient.injkey)
+        
+    def invoke(self, args, kwargs):
+        """
+        Send the required soap message to invoke the specified method
+        @param args: A list of args for the method invoked.
+        @type args: list
+        @param kwargs: Named (keyword) args for the method invoked.
+        @type kwargs: dict
+        @return: The result of the method invocation.
+        @rtype: I{builtin} or I{subclass of} L{Object}
+        """
+        simulation = kwargs[self.injkey]
+        msg = simulation.get('msg')
+        reply = simulation.get('reply')
+        fault = simulation.get('fault')
+        if msg is None:
+            if reply is not None:
+                return self.__reply(reply, args, kwargs)
+            if fault is not None:
+                return self.__fault(fault)
+            raise Exception('(reply|fault) expected when msg=None')
+        sax = Parser()
+        msg = sax.parse(string=msg)
+        return self.send(msg)
+    
+    def __reply(self, reply, args, kwargs):
+        """ simulate the reply """
+        binding = self.method.binding.input
+        msg = binding.get_message(self.method, args, kwargs)
+        log.debug('inject (simulated) send message:\n%s', msg)
+        binding = self.method.binding.output
+        return self.succeeded(binding, reply)
+    
+    def __fault(self, reply):
+        """ simulate the (fault) reply """
+        binding = self.method.binding.output
+        if self.options.faults:
+            r, p = binding.get_fault(reply)
+            self.last_received(r)
+            return (500, p)
+        else:
+            return (500, None)
diff --git a/suds/metrics.py b/suds/metrics.py
new file mode 100644
index 0000000..403224a
--- /dev/null
+++ b/suds/metrics.py
@@ -0,0 +1,62 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{metrics} module defines classes and other resources
+designed for collecting and reporting performance metrics.
+"""
+
+import time
+from logging import getLogger
+from suds import *
+from math import modf
+
+log = getLogger(__name__)
+
+class Timer:
+
+    def __init__(self):
+        self.started = 0
+        self.stopped = 0
+
+    def start(self):
+        self.started = time.time()
+        self.stopped = 0
+        return self
+
+    def stop(self):
+        if self.started > 0:
+            self.stopped = time.time()
+        return self
+
+    def duration(self):
+        return ( self.stopped - self.started )
+
+    def __str__(self):
+        if self.started == 0:
+            return 'not-running'
+        if self.started > 0 and self.stopped == 0:
+            return 'started: %d (running)' % self.started
+        duration = self.duration()
+        jmod = ( lambda m : (m[1], m[0]*1000) )
+        if duration < 1:
+            ms = (duration*1000)
+            return '%d (ms)' % ms           
+        if duration < 60:
+            m = modf(duration)
+            return '%d.%.3d (seconds)' % jmod(m)
+        m = modf(duration/60)
+        return '%d.%.3d (minutes)' % jmod(m)
diff --git a/suds/mx/__init__.py b/suds/mx/__init__.py
new file mode 100644
index 0000000..77e6ca1
--- /dev/null
+++ b/suds/mx/__init__.py
@@ -0,0 +1,59 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides modules containing classes to support
+marshalling (XML).
+"""
+
+from suds.sudsobject import Object
+
+
+class Content(Object):
+    """
+    Marshaller Content.
+    @ivar tag: The content tag.
+    @type tag: str
+    @ivar value: The content's value.
+    @type value: I{any}
+    """
+    
+    extensions = []
+    
+    def __init__(self, tag=None, value=None, **kwargs):
+        """
+        @param tag: The content tag.
+        @type tag: str
+        @param value: The content's value.
+        @type value: I{any}
+        """
+        Object.__init__(self)
+        self.tag = tag
+        self.value = value
+        for k,v in kwargs.items():
+            setattr(self, k, v)
+            
+    def __getattr__(self, name):
+        if name not in self.__dict__:
+            if name in self.extensions:
+                v = None
+                setattr(self, name, v)
+            else:
+                raise AttributeError, \
+                    'Content has no attribute %s' % name
+        else:
+            v = self.__dict__[name]
+        return v
\ No newline at end of file
diff --git a/suds/mx/appender.py b/suds/mx/appender.py
new file mode 100644
index 0000000..0501415
--- /dev/null
+++ b/suds/mx/appender.py
@@ -0,0 +1,310 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides appender classes for I{marshalling}.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.sudsobject import footprint
+from suds.sudsobject import Object, Property
+from suds.sax.element import Element
+from suds.sax.text import Text
+from copy import deepcopy
+
+log = getLogger(__name__)
+
+class Matcher:
+    """
+    Appender matcher.
+    @ivar cls: A class object.
+    @type cls: I{classobj}
+    """
+
+    def __init__(self, cls):
+        """
+        @param cls: A class object.
+        @type cls: I{classobj}
+        """
+        self.cls = cls
+
+    def __eq__(self, x):
+        if self.cls is None:
+            return ( x is None )
+        else:
+            return isinstance(x, self.cls)
+
+
+class ContentAppender:
+    """
+    Appender used to add content to marshalled objects.
+    @ivar default: The default appender.
+    @type default: L{Appender}
+    @ivar appenders: A I{table} of appenders mapped by class.
+    @type appenders: I{table}
+    """
+
+    def __init__(self, marshaller):
+        """
+        @param marshaller: A marshaller.
+        @type marshaller: L{suds.mx.core.Core}
+        """
+        self.default = PrimativeAppender(marshaller)
+        self.appenders = (
+            (Matcher(None),
+                NoneAppender(marshaller)),
+            (Matcher(null),
+                NoneAppender(marshaller)),
+            (Matcher(Property),
+                PropertyAppender(marshaller)),
+            (Matcher(Object),
+                ObjectAppender(marshaller)),
+            (Matcher(Element), 
+                ElementAppender(marshaller)),
+            (Matcher(Text), 
+                TextAppender(marshaller)),
+            (Matcher(list), 
+                ListAppender(marshaller)),
+            (Matcher(tuple), 
+                ListAppender(marshaller)),
+            (Matcher(dict), 
+                DictAppender(marshaller)),
+        )
+        
+    def append(self, parent, content):
+        """
+        Select an appender and append the content to parent.
+        @param parent: A parent node.
+        @type parent: L{Element}
+        @param content: The content to append.
+        @type content: L{Content}
+        """
+        appender = self.default
+        for a in self.appenders:
+            if a[0] == content.value:
+                appender = a[1]
+                break
+        appender.append(parent, content)
+
+
+class Appender:
+    """
+    An appender used by the marshaller to append content.
+    @ivar marshaller: A marshaller.
+    @type marshaller: L{suds.mx.core.Core}
+    """
+    
+    def __init__(self, marshaller):
+        """
+        @param marshaller: A marshaller.
+        @type marshaller: L{suds.mx.core.Core}
+        """
+        self.marshaller  = marshaller
+        
+    def node(self, content):
+        """
+        Create and return an XML node that is qualified
+        using the I{type}.  Also, make sure all referenced namespace
+        prefixes are declared.
+        @param content: The content for which proccessing has ended.
+        @type content: L{Object}
+        @return: A new node.
+        @rtype: L{Element}
+        """
+        return self.marshaller.node(content)
+    
+    def setnil(self, node, content):
+        """
+        Set the value of the I{node} to nill.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content for which proccessing has ended.
+        @type content: L{Object}
+        """
+        self.marshaller.setnil(node, content)
+        
+    def setdefault(self, node, content):
+        """
+        Set the value of the I{node} to a default value.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content for which proccessing has ended.
+        @type content: L{Object}
+        @return: The default.
+        """
+        return self.marshaller.setdefault(node, content)
+    
+    def optional(self, content):
+        """
+        Get whether the specified content is optional.
+        @param content: The content which to check.
+        @type content: L{Content}
+        """
+        return self.marshaller.optional(content)
+        
+    def suspend(self, content):
+        """
+        Notify I{marshaller} that appending this content has suspended.
+        @param content: The content for which proccessing has been suspended.
+        @type content: L{Object}
+        """
+        self.marshaller.suspend(content)
+        
+    def resume(self, content):
+        """
+        Notify I{marshaller} that appending this content has resumed.
+        @param content: The content for which proccessing has been resumed.
+        @type content: L{Object}
+        """
+        self.marshaller.resume(content)
+    
+    def append(self, parent, content):
+        """
+        Append the specified L{content} to the I{parent}.
+        @param content: The content to append.
+        @type content: L{Object}
+        """
+        self.marshaller.append(parent, content)
+
+       
+class PrimativeAppender(Appender):
+    """
+    An appender for python I{primative} types.
+    """
+        
+    def append(self, parent, content):
+        if content.tag.startswith('_'):
+            attr = content.tag[1:]
+            value = tostr(content.value)
+            if value is not None and len(value):
+                parent.set(attr, value)
+        else:
+            child = self.node(content)
+            child.setText(tostr(content.value))
+            parent.append(child)
+
+
+class NoneAppender(Appender):
+    """
+    An appender for I{None} values.
+    """
+        
+    def append(self, parent, content):
+        child = self.node(content)
+        default = self.setdefault(child, content)
+        if default is None:
+            self.setnil(child, content)
+        parent.append(child)
+
+
+class PropertyAppender(Appender):
+    """
+    A L{Property} appender.
+    """
+        
+    def append(self, parent, content):
+        p = content.value
+        child = self.node(content)
+        child.setText(p.get())
+        parent.append(child)
+        for item in p.items():
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+
+            
+class ObjectAppender(Appender):
+    """
+    An L{Object} appender.
+    """
+        
+    def append(self, parent, content):
+        object = content.value
+        if self.optional(content) and footprint(object) == 0:
+            return
+        child = self.node(content)
+        parent.append(child)
+        for item in object:
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+            
+
+class DictAppender(Appender):
+    """
+    An python I{dict} appender.
+    """
+        
+    def append(self, parent, content):
+        d = content.value
+        if self.optional(content) and len(d) == 0:
+            return
+        child = self.node(content)
+        parent.append(child)
+        for item in d.items():
+            cont = Content(tag=item[0], value=item[1])
+            Appender.append(self, child, cont)
+            
+
+class ElementWrapper(Element):
+    """
+    Element wrapper.
+    """
+    
+    def __init__(self, content):
+        Element.__init__(self, content.name, content.parent)
+        self.__content = content
+        
+    def str(self, indent=0):
+        return self.__content.str(indent)
+
+
+class ElementAppender(Appender):
+    """
+    An appender for I{Element} types.
+    """
+
+    def append(self, parent, content):
+        if content.tag.startswith('_'):
+            raise Exception('raw XML not valid as attribute value')
+        child = ElementWrapper(content.value)
+        parent.append(child)
+
+
+class ListAppender(Appender):
+    """
+    A list/tuple appender.
+    """
+
+    def append(self, parent, content):
+        collection = content.value
+        if len(collection):
+            self.suspend(content)
+            for item in collection:
+                cont = Content(tag=content.tag, value=item)
+                Appender.append(self, parent, cont)
+            self.resume(content)
+
+
+class TextAppender(Appender):
+    """
+    An appender for I{Text} values.
+    """
+
+    def append(self, parent, content):
+        child = self.node(content)
+        child.setText(content.value)
+        parent.append(child)
diff --git a/suds/mx/basic.py b/suds/mx/basic.py
new file mode 100644
index 0000000..336f684
--- /dev/null
+++ b/suds/mx/basic.py
@@ -0,0 +1,48 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides basic I{marshaller} classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.mx.core import Core
+
+log = getLogger(__name__)
+
+
+class Basic(Core):
+    """
+    A I{basic} (untyped) marshaller.
+    """
+    
+    def process(self, value, tag=None):
+        """
+        Process (marshal) the tag with the specified value using the
+        optional type information.
+        @param value: The value (content) of the XML node.
+        @type value: (L{Object}|any)
+        @param tag: The (optional) tag name for the value.  The default is
+            value.__class__.__name__
+        @type tag: str
+        @return: An xml node.
+        @rtype: L{Element}
+        """
+        content = Content(tag=tag, value=value)
+        result = Core.process(self, content)
+        return result
\ No newline at end of file
diff --git a/suds/mx/core.py b/suds/mx/core.py
new file mode 100644
index 0000000..3c9ef59
--- /dev/null
+++ b/suds/mx/core.py
@@ -0,0 +1,158 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides I{marshaller} core classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.mx.appender import ContentAppender
+from suds.sax.element import Element
+from suds.sax.document import Document
+from suds.sudsobject import Property
+
+
+log = getLogger(__name__)
+
+
+class Core:
+    """
+    An I{abstract} marshaller.  This class implement the core
+    functionality of the marshaller.
+    @ivar appender: A content appender.
+    @type appender: L{ContentAppender}
+    """
+
+    def __init__(self):
+        """
+        """
+        self.appender = ContentAppender(self)
+
+    def process(self, content):
+        """
+        Process (marshal) the tag with the specified value using the
+        optional type information.
+        @param content: The content to process.
+        @type content: L{Object}
+        """
+        log.debug('processing:\n%s', content)
+        self.reset()
+        if content.tag is None:
+            content.tag = content.value.__class__.__name__
+        document = Document()
+        if isinstance(content.value, Property):
+            root = self.node(content)
+            self.append(document, content)
+        else:
+            self.append(document, content)
+        return document.root()
+    
+    def append(self, parent, content):
+        """
+        Append the specified L{content} to the I{parent}.
+        @param parent: The parent node to append to.
+        @type parent: L{Element}
+        @param content: The content to append.
+        @type content: L{Object}
+        """
+        log.debug('appending parent:\n%s\ncontent:\n%s', parent, content)
+        if self.start(content):
+            self.appender.append(parent, content)
+            self.end(parent, content)
+
+    def reset(self):
+        """
+        Reset the marshaller.
+        """
+        pass
+
+    def node(self, content):
+        """
+        Create and return an XML node.
+        @param content: The content for which proccessing has been suspended.
+        @type content: L{Object}
+        @return: An element.
+        @rtype: L{Element}
+        """
+        return Element(content.tag)
+    
+    def start(self, content):
+        """
+        Appending this content has started.
+        @param content: The content for which proccessing has started.
+        @type content: L{Content}
+        @return: True to continue appending
+        @rtype: boolean
+        """
+        return True
+    
+    def suspend(self, content):
+        """
+        Appending this content has suspended.
+        @param content: The content for which proccessing has been suspended.
+        @type content: L{Content}
+        """
+        pass
+    
+    def resume(self, content):
+        """
+        Appending this content has resumed.
+        @param content: The content for which proccessing has been resumed.
+        @type content: L{Content}
+        """
+        pass
+
+    def end(self, parent, content):
+        """
+        Appending this content has ended.
+        @param parent: The parent node ending.
+        @type parent: L{Element}
+        @param content: The content for which proccessing has ended.
+        @type content: L{Content}
+        """
+        pass
+    
+    def setnil(self, node, content):
+        """
+        Set the value of the I{node} to nill.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content to set nil.
+        @type content: L{Content}
+        """
+        pass
+
+    def setdefault(self, node, content):
+        """
+        Set the value of the I{node} to a default value.
+        @param node: A I{nil} node.
+        @type node: L{Element}
+        @param content: The content to set the default value.
+        @type content: L{Content}
+        @return: The default.
+        """
+        pass
+    
+    def optional(self, content):
+        """
+        Get whether the specified content is optional.
+        @param content: The content which to check.
+        @type content: L{Content}
+        """
+        return False
+
diff --git a/suds/mx/encoded.py b/suds/mx/encoded.py
new file mode 100644
index 0000000..9cbc8c5
--- /dev/null
+++ b/suds/mx/encoded.py
@@ -0,0 +1,133 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides encoded I{marshaller} classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.mx.literal import Literal
+from suds.mx.typer import Typer
+from suds.sudsobject import Factory, Object
+from suds.xsd.query import TypeQuery
+
+log = getLogger(__name__)
+
+#
+# Add encoded extensions
+# aty = The soap (section 5) encoded array type.
+#
+Content.extensions.append('aty')
+
+
+class Encoded(Literal):
+    """
+    A SOAP section (5) encoding marshaller.
+    This marshaller supports rpc/encoded soap styles.
+    """
+    
+    def start(self, content):
+        #
+        # For soap encoded arrays, the 'aty' (array type) information
+        # is extracted and added to the 'content'.  Then, the content.value
+        # is replaced with an object containing an 'item=[]' attribute
+        # containing values that are 'typed' suds objects. 
+        #
+        start = Literal.start(self, content)
+        if start and isinstance(content.value, (list,tuple)):
+            resolved = content.type.resolve()
+            for c in resolved:
+                if hasattr(c[0], 'aty'):
+                    content.aty = (content.tag, c[0].aty)
+                    self.cast(content)
+                    break
+        return start
+    
+    def end(self, parent, content):
+        #
+        # For soap encoded arrays, the soapenc:arrayType attribute is
+        # added with proper type and size information.
+        # Eg: soapenc:arrayType="xs:int[3]"
+        #
+        Literal.end(self, parent, content)
+        if content.aty is None:
+            return
+        tag, aty = content.aty
+        ns0 = ('at0', aty[1])
+        ns1 = ('at1', 'http://schemas.xmlsoap.org/soap/encoding/')
+        array = content.value.item
+        child = parent.getChild(tag)
+        child.addPrefix(ns0[0], ns0[1])
+        child.addPrefix(ns1[0], ns1[1])
+        name = '%s:arrayType' % ns1[0]
+        value = '%s:%s[%d]' % (ns0[0], aty[0], len(array)) 
+        child.set(name, value)
+        
+    def encode(self, node, content):
+        if content.type.any():
+            Typer.auto(node, content.value)
+            return
+        if content.real.any():
+            Typer.auto(node, content.value)
+            return
+        ns = None
+        name = content.real.name
+        if self.xstq:
+            ns = content.real.namespace()
+        Typer.manual(node, name, ns)
+        
+    def cast(self, content):
+        """
+        Cast the I{untyped} list items found in content I{value}.
+        Each items contained in the list is checked for XSD type information.
+        Items (values) that are I{untyped}, are replaced with suds objects and
+        type I{metadata} is added.
+        @param content: The content holding the collection.
+        @type content: L{Content}
+        @return: self
+        @rtype: L{Encoded}
+        """
+        aty = content.aty[1]
+        resolved = content.type.resolve()
+        array = Factory.object(resolved.name)
+        array.item = []
+        query = TypeQuery(aty)
+        ref = query.execute(self.schema)
+        if ref is None:
+            raise TypeNotFound(qref)
+        for x in content.value:
+            if isinstance(x, (list, tuple)):
+                array.item.append(x)
+                continue
+            if isinstance(x, Object):
+                md = x.__metadata__
+                md.sxtype = ref
+                array.item.append(x) 
+                continue
+            if isinstance(x, dict):
+                x = Factory.object(ref.name, x)
+                md = x.__metadata__
+                md.sxtype = ref
+                array.item.append(x) 
+                continue
+            x = Factory.property(ref.name, x)
+            md = x.__metadata__
+            md.sxtype = ref
+            array.item.append(x)
+        content.value = array
+        return self
diff --git a/suds/mx/literal.py b/suds/mx/literal.py
new file mode 100644
index 0000000..937ad8e
--- /dev/null
+++ b/suds/mx/literal.py
@@ -0,0 +1,291 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides literal I{marshaller} classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.mx.core import Core
+from suds.mx.typer import Typer
+from suds.resolver import GraphResolver, Frame
+from suds.sax.element import Element
+from suds.sudsobject import Factory
+
+log = getLogger(__name__)
+
+
+#
+# Add typed extensions
+# type = The expected xsd type
+# real = The 'true' XSD type
+# ancestry = The 'type' ancestry
+#
+Content.extensions.append('type')
+Content.extensions.append('real')
+Content.extensions.append('ancestry')
+
+
+
+class Typed(Core):
+    """
+    A I{typed} marshaller.
+    This marshaller is semi-typed as needed to support both
+    I{document/literal} and I{rpc/literal} soap message styles.
+    @ivar schema: An xsd schema.
+    @type schema: L{xsd.schema.Schema}
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{GraphResolver}
+    """
+
+    def __init__(self, schema, xstq=True):
+        """
+        @param schema: A schema object
+        @type schema: L{xsd.schema.Schema}
+        @param xstq: The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
+            that the I{xsi:type} attribute values should be qualified by namespace.
+        @type xstq: bool
+        """
+        Core.__init__(self)
+        self.schema = schema
+        self.xstq = xstq
+        self.resolver = GraphResolver(self.schema)
+    
+    def reset(self):
+        self.resolver.reset()
+            
+    def start(self, content):
+        #
+        # Start marshalling the 'content' by ensuring that both the
+        # 'content' _and_ the resolver are primed with the XSD type
+        # information.  The 'content' value is both translated and
+        # sorted based on the XSD type.  Only values that are objects
+        # have their attributes sorted.
+        #
+        log.debug('starting content:\n%s', content)
+        if content.type is None:
+            name = content.tag
+            if name.startswith('_'):
+                name = '@'+name[1:]
+            content.type = self.resolver.find(name, content.value)
+            if content.type is None:
+                raise TypeNotFound(content.tag)
+        else:
+            known = None
+            if isinstance(content.value, Object):
+                known = self.resolver.known(content.value)
+                if known is None:
+                    log.debug('object has no type information', content.value)
+                    known = content.type
+            frame = Frame(content.type, resolved=known)
+            self.resolver.push(frame)
+        frame = self.resolver.top()
+        content.real = frame.resolved
+        content.ancestry = frame.ancestry
+        self.translate(content)
+        self.sort(content)
+        if self.skip(content):
+            log.debug('skipping (optional) content:\n%s', content)
+            self.resolver.pop()
+            return False
+        else:
+            return True
+        
+    def suspend(self, content):
+        #
+        # Suspend to process a list content.  Primarily, this
+        # involves popping the 'list' content off the resolver's
+        # stack so the list items can be marshalled.
+        #
+        self.resolver.pop()
+    
+    def resume(self, content):
+        #
+        # Resume processing a list content.  To do this, we
+        # really need to simply push the 'list' content
+        # back onto the resolver stack.
+        #
+        self.resolver.push(Frame(content.type))
+        
+    def end(self, parent, content):
+        #
+        # End processing the content.  Make sure the content
+        # ending matches the top of the resolver stack since for
+        # list processing we play games with the resolver stack.
+        #
+        log.debug('ending content:\n%s', content)
+        current = self.resolver.top().type
+        if current == content.type:
+            self.resolver.pop()
+        else:
+            raise Exception, \
+                'content (end) mismatch: top=(%s) cont=(%s)' % \
+                (current, content)
+    
+    def node(self, content):
+        #
+        # Create an XML node and namespace qualify as defined
+        # by the schema (elementFormDefault).
+        #
+        ns = content.type.namespace()
+        if content.type.form_qualified:
+            node = Element(content.tag, ns=ns)
+            node.addPrefix(ns[0], ns[1])
+        else:
+            node = Element(content.tag)
+        self.encode(node, content)
+        log.debug('created - node:\n%s', node)
+        return node
+    
+    def setnil(self, node, content):
+        #
+        # Set the 'node' nil only if the XSD type
+        # specifies that it is permitted.
+        #
+        if content.type.nillable:
+            node.setnil()
+            
+    def setdefault(self, node, content):
+        #
+        # Set the node to the default value specified
+        # by the XSD type.
+        #
+        default = content.type.default
+        if default is None:
+            pass
+        else:
+            node.setText(default)
+        return default
+    
+    def optional(self, content):
+        if content.type.optional():
+            return True
+        for a in content.ancestry:
+            if a.optional():
+                return True
+        return False
+    
+    def encode(self, node, content):
+        # Add (soap) encoding information only if the resolved
+        # type is derived by extension.  Further, the xsi:type values
+        # is qualified by namespace only if the content (tag) and
+        # referenced type are in different namespaces.
+        if content.type.any():
+            return
+        if not content.real.extension():
+            return
+        if content.type.resolve() == content.real:
+            return
+        ns = None
+        name = content.real.name
+        if self.xstq:
+            ns = content.real.namespace('ns1')
+        Typer.manual(node, name, ns)
+    
+    def skip(self, content):
+        """
+        Get whether to skip this I{content}.
+        Should be skipped when the content is optional
+        and either the value=None or the value is an empty list.
+        @param content: The content to skip.
+        @type content: L{Object}
+        @return: True if content is to be skipped.
+        @rtype: bool
+        """
+        if self.optional(content):
+            v = content.value
+            if v is None:
+                return True
+            if isinstance(v, (list,tuple)) and len(v) == 0:
+                return True
+        return False
+    
+    def optional(self, content):
+        if content.type.optional():
+            return True
+        for a in content.ancestry:
+            if a.optional():
+                return True
+        return False
+    
+    def translate(self, content):
+        """
+        Translate using the XSD type information.
+        Python I{dict} is translated to a suds object.  Most
+        importantly, primative values are translated from python
+        types to XML types using the XSD type.
+        @param content: The content to translate.
+        @type content: L{Object}
+        @return: self
+        @rtype: L{Typed}
+        """
+        v = content.value
+        if v is None:
+            return
+        if isinstance(v, dict):
+            cls = content.real.name
+            content.value = Factory.object(cls, v)
+            md = content.value.__metadata__
+            md.sxtype = content.type
+            return
+        v = content.real.translate(v, False)
+        content.value = v
+        return self
+        
+    def sort(self, content):
+        """
+        Sort suds object attributes based on ordering defined
+        in the XSD type information.
+        @param content: The content to sort.
+        @type content: L{Object}
+        @return: self
+        @rtype: L{Typed}
+        """
+        v = content.value
+        if isinstance(v, Object):
+            md = v.__metadata__
+            md.ordering = self.ordering(content.real)
+        return self
+
+    def ordering(self, type):
+        """
+        Get the attribute ordering defined in the specified
+        XSD type information.
+        @param type: An XSD type object.
+        @type type: SchemaObject
+        @return: An ordered list of attribute names.
+        @rtype: list
+        """
+        result = []
+        for child, ancestry in type.resolve():
+            name = child.name
+            if child.name is None:
+                continue
+            if child.isattr():
+                name = '_%s' % child.name
+            result.append(name)
+        return result
+
+
+class Literal(Typed):
+    """
+    A I{literal} marshaller.
+    This marshaller is semi-typed as needed to support both
+    I{document/literal} and I{rpc/literal} soap message styles.
+    """
+    pass
\ No newline at end of file
diff --git a/suds/mx/typer.py b/suds/mx/typer.py
new file mode 100644
index 0000000..ea88df7
--- /dev/null
+++ b/suds/mx/typer.py
@@ -0,0 +1,123 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides sx typing classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.mx import *
+from suds.sax import Namespace as NS
+from suds.sax.text import Text
+
+log = getLogger(__name__)
+
+
+class Typer:
+    """
+    Provides XML node typing as either automatic or manual.
+    @cvar types:  A dict of class to xs type mapping.
+    @type types: dict
+    """
+
+    types = {
+        int : ('int', NS.xsdns),
+        long : ('long', NS.xsdns),
+        float : ('float', NS.xsdns),
+        str : ('string', NS.xsdns),
+        unicode : ('string', NS.xsdns),
+        Text : ('string', NS.xsdns),
+        bool : ('boolean', NS.xsdns),
+     }
+                
+    @classmethod
+    def auto(cls, node, value=None):
+        """
+        Automatically set the node's xsi:type attribute based on either I{value}'s
+        class or the class of the node's text.  When I{value} is an unmapped class,
+        the default type (xs:any) is set.
+        @param node: An XML node
+        @type node: L{sax.element.Element}
+        @param value: An object that is or would be the node's text.
+        @type value: I{any}
+        @return: The specified node.
+        @rtype: L{sax.element.Element}
+        """
+        if value is None:
+            value = node.getText()
+        if isinstance(value, Object):
+            known = cls.known(value)
+            if known.name is None:
+                return node
+            tm = (known.name, known.namespace())
+        else:
+            tm = cls.types.get(value.__class__, cls.types.get(str))
+        cls.manual(node, *tm)
+        return node
+
+    @classmethod
+    def manual(cls, node, tval, ns=None):
+        """
+        Set the node's xsi:type attribute based on either I{value}'s
+        class or the class of the node's text.  Then adds the referenced
+        prefix(s) to the node's prefix mapping.
+        @param node: An XML node
+        @type node: L{sax.element.Element}
+        @param tval: The name of the schema type.
+        @type tval: str
+        @param ns: The XML namespace of I{tval}.
+        @type ns: (prefix, uri)
+        @return: The specified node.
+        @rtype: L{sax.element.Element}
+        """
+        xta = ':'.join((NS.xsins[0], 'type'))
+        node.addPrefix(NS.xsins[0], NS.xsins[1])
+        if ns is None:
+            node.set(xta, tval)
+        else:
+            ns = cls.genprefix(node, ns)
+            qname = ':'.join((ns[0], tval))
+            node.set(xta, qname)
+            node.addPrefix(ns[0], ns[1]) 
+        return node
+    
+    @classmethod
+    def genprefix(cls, node, ns):
+        """
+        Generate a prefix.
+        @param node: An XML node on which the prefix will be used.
+        @type node: L{sax.element.Element}
+        @param ns: A namespace needing an unique prefix.
+        @type ns: (prefix, uri)
+        @return: The I{ns} with a new prefix.
+        """
+        for n in range(1, 1024):
+            p = 'ns%d' % n
+            u = node.resolvePrefix(p, default=None)
+            if u is None or u == ns[1]:
+                return (p, ns[1])
+        raise Exception('auto prefix, exhausted')
+    
+    @classmethod
+    def known(cls, object):
+        try:
+            md = object.__metadata__
+            known = md.sxtype
+            return known
+        except:
+            pass
+
diff --git a/suds/options.py b/suds/options.py
new file mode 100644
index 0000000..7e5c069
--- /dev/null
+++ b/suds/options.py
@@ -0,0 +1,109 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Suds basic options classes.
+"""
+
+from suds.properties import *
+from suds.wsse import Security
+from suds.xsd.doctor import Doctor
+from suds.transport import Transport
+from suds.cache import Cache, NoCache
+
+
+class TpLinker(AutoLinker):
+    """
+    Transport (auto) linker used to manage linkage between
+    transport objects Properties and those Properties that contain them.
+    """
+    
+    def updated(self, properties, prev, next):
+        if isinstance(prev, Transport):
+            tp = Unskin(prev.options)
+            properties.unlink(tp)
+        if isinstance(next, Transport):
+            tp = Unskin(next.options)
+            properties.link(tp)
+
+
+class Options(Skin):
+    """
+    Options:
+        - B{cache} - The XML document cache.  May be set (None) for no caching.
+                - type: L{Cache}
+                - default: L{NoCache}
+        - B{faults} - Raise faults raised by server,
+            else return tuple from service method invocation as (httpcode, object).
+                - type: I{bool}
+                - default: True
+        - B{service} - The default service name.
+                - type: I{str}
+                - default: None
+        - B{port} - The default service port name, not tcp port.
+                - type: I{str}
+                - default: None
+        - B{location} - This overrides the service port address I{URL} defined 
+            in the WSDL.
+                - type: I{str}
+                - default: None
+        - B{transport} - The message transport.
+                - type: L{Transport}
+                - default: None
+        - B{soapheaders} - The soap headers to be included in the soap message.
+                - type: I{any}
+                - default: None
+        - B{wsse} - The web services I{security} provider object.
+                - type: L{Security}
+                - default: None
+        - B{doctor} - A schema I{doctor} object.
+                - type: L{Doctor}
+                - default: None
+        - B{xstq} - The B{x}ml B{s}chema B{t}ype B{q}ualified flag indicates
+            that the I{xsi:type} attribute values should be qualified by namespace.
+                - type: I{bool}
+                - default: True
+        - B{prefixes} - Elements of the soap message should be qualified (when needed)
+            using XML prefixes as opposed to xmlns="" syntax.
+                - type: I{bool}
+                - default: True
+        - B{retxml} - Flag that causes the I{raw} soap envelope to be returned instead
+            of the python object graph.
+                - type: I{bool}
+                - default: False
+        - B{autoblend} - Flag that ensures that the schema(s) defined within the
+            WSDL import each other.  B{**Experimental**}.
+                - type: I{bool}
+                - default: False
+    """    
+    def __init__(self, **kwargs):
+        domain = __name__
+        definitions = [
+            Definition('cache', Cache, NoCache()),
+            Definition('faults', bool, True),
+            Definition('transport', Transport, None, TpLinker()),
+            Definition('service', (int, basestring), None),
+            Definition('port', (int, basestring), None),
+            Definition('location', basestring, None),
+            Definition('soapheaders', (), ()),
+            Definition('wsse', Security, None),
+            Definition('doctor', Doctor, None),
+            Definition('xstq', bool, True),
+            Definition('prefixes', bool, True),
+            Definition('retxml', bool, False),
+            Definition('autoblend', bool, False),
+        ]
+        Skin.__init__(self, domain, definitions, kwargs)
diff --git a/suds/properties.py b/suds/properties.py
new file mode 100644
index 0000000..50b2593
--- /dev/null
+++ b/suds/properties.py
@@ -0,0 +1,543 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Properties classes.
+"""
+
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+class AutoLinker(object):
+    """
+    Base class, provides interface for I{automatic} link
+    management between a L{Properties} object and the L{Properties}
+    contained within I{values}.
+    """
+    def updated(self, properties, prev, next):
+        """
+        Notification that a values was updated and the linkage
+        between the I{properties} contained with I{prev} need to
+        be relinked to the L{Properties} contained within the
+        I{next} value.
+        """
+        pass
+
+
+class Link(object):
+    """
+    Property link object.
+    @ivar endpoints: A tuple of the (2) endpoints of the link.
+    @type endpoints: tuple(2)
+    """
+    def __init__(self, a, b):
+        """
+        @param a: Property (A) to link.
+        @type a: L{Property}
+        @param b: Property (B) to link.
+        @type b: L{Property}
+        """
+        pA = Endpoint(self, a)
+        pB = Endpoint(self, b)
+        self.endpoints = (pA, pB)
+        self.validate(a, b)
+        a.links.append(pB)
+        b.links.append(pA)
+            
+    def validate(self, pA, pB):
+        """
+        Validate that the two properties may be linked.
+        @param pA: Endpoint (A) to link.
+        @type pA: L{Endpoint}
+        @param pB: Endpoint (B) to link.
+        @type pB: L{Endpoint}
+        @return: self
+        @rtype: L{Link}
+        """
+        if pA in pB.links or \
+           pB in pA.links:
+            raise Exception, 'Already linked'
+        dA = pA.domains()
+        dB = pB.domains()
+        for d in dA:
+            if d in dB:
+                raise Exception, 'Duplicate domain "%s" found' % d
+        for d in dB:
+            if d in dA:
+                raise Exception, 'Duplicate domain "%s" found' % d
+        kA = pA.keys()
+        kB = pB.keys()
+        for k in kA:
+            if k in kB:
+                raise Exception, 'Duplicate key %s found' % k
+        for k in kB:
+            if k in kA:
+                raise Exception, 'Duplicate key %s found' % k
+        return self
+            
+    def teardown(self):
+        """
+        Teardown the link.
+        Removes endpoints from properties I{links} collection.
+        @return: self
+        @rtype: L{Link}
+        """
+        pA, pB = self.endpoints
+        if pA in pB.links:
+            pB.links.remove(pA)
+        if pB in pA.links:
+            pA.links.remove(pB)
+        return self
+
+
+class Endpoint(object):
+    """
+    Link endpoint (wrapper).
+    @ivar link: The associated link.
+    @type link: L{Link}
+    @ivar target: The properties object.
+    @type target: L{Property}
+    """
+    def __init__(self, link, target):
+        self.link = link
+        self.target = target
+        
+    def teardown(self):
+        return self.link.teardown()
+
+    def __eq__(self, rhs):
+        return ( self.target == rhs )
+
+    def __hash__(self):
+        return hash(self.target)
+
+    def __getattr__(self, name):
+        return getattr(self.target, name)
+
+
+class Definition:
+    """
+    Property definition.
+    @ivar name: The property name.
+    @type name: str
+    @ivar classes: The (class) list of permitted values
+    @type classes: tuple
+    @ivar default: The default value.
+    @ivar type: any
+    """
+    def __init__(self, name, classes, default, linker=AutoLinker()):
+        """
+        @param name: The property name.
+        @type name: str
+        @param classes: The (class) list of permitted values
+        @type classes: tuple
+        @param default: The default value.
+        @type default: any
+        """
+        if not isinstance(classes, (list, tuple)):
+            classes = (classes,)
+        self.name = name
+        self.classes = classes
+        self.default = default
+        self.linker = linker
+        
+    def nvl(self, value=None):
+        """
+        Convert the I{value} into the default when I{None}.
+        @param value: The proposed value.
+        @type value: any
+        @return: The I{default} when I{value} is I{None}, else I{value}.
+        @rtype: any
+        """
+        if value is None:
+            return self.default
+        else:
+            return value
+        
+    def validate(self, value):
+        """
+        Validate the I{value} is of the correct class.
+        @param value: The value to validate.
+        @type value: any
+        @raise AttributeError: When I{value} is invalid.
+        """
+        if value is None:
+            return
+        if len(self.classes) and \
+            not isinstance(value, self.classes):
+                msg = '"%s" must be: %s' % (self.name, self.classes)
+                raise AttributeError,msg
+                    
+            
+    def __repr__(self):
+        return '%s: %s' % (self.name, str(self))
+            
+    def __str__(self):
+        s = []
+        if len(self.classes):
+            s.append('classes=%s' % str(self.classes))
+        else:
+            s.append('classes=*')
+        s.append("default=%s" % str(self.default))
+        return ', '.join(s)
+
+
+class Properties:
+    """
+    Represents basic application properties.
+    Provides basic type validation, default values and
+    link/synchronization behavior.
+    @ivar domain: The domain name.
+    @type domain: str
+    @ivar definitions: A table of property definitions.
+    @type definitions: {name: L{Definition}}
+    @ivar links: A list of linked property objects used to create
+        a network of properties.
+    @type links: [L{Property},..]
+    @ivar defined: A dict of property values.
+    @type defined: dict 
+    """
+    def __init__(self, domain, definitions, kwargs):
+        """
+        @param domain: The property domain name.
+        @type domain: str
+        @param definitions: A table of property definitions.
+        @type definitions: {name: L{Definition}}
+        @param kwargs: A list of property name/values to set.
+        @type kwargs: dict  
+        """
+        self.definitions = {}
+        for d in definitions:
+            self.definitions[d.name] = d
+        self.domain = domain
+        self.links = []
+        self.defined = {}
+        self.modified = set()
+        self.prime()
+        self.update(kwargs)
+        
+    def definition(self, name):
+        """
+        Get the definition for the property I{name}.
+        @param name: The property I{name} to find the definition for.
+        @type name: str
+        @return: The property definition
+        @rtype: L{Definition}
+        @raise AttributeError: On not found.
+        """
+        d = self.definitions.get(name)
+        if d is None:
+            raise AttributeError(name)
+        return d
+    
+    def update(self, other):
+        """
+        Update the property values as specified by keyword/value.
+        @param other: An object to update from.
+        @type other: (dict|L{Properties})
+        @return: self
+        @rtype: L{Properties}
+        """
+        if isinstance(other, Properties):
+            other = other.defined
+        for n,v in other.items():
+            self.set(n, v)
+        return self
+    
+    def notset(self, name):
+        """
+        Get whether a property has never been set by I{name}.
+        @param name: A property name.
+        @type name: str
+        @return: True if never been set.
+        @rtype: bool
+        """
+        self.provider(name).__notset(name)
+            
+    def set(self, name, value):
+        """
+        Set the I{value} of a property by I{name}.
+        The value is validated against the definition and set
+        to the default when I{value} is None.
+        @param name: The property name.
+        @type name: str
+        @param value: The new property value.
+        @type value: any
+        @return: self
+        @rtype: L{Properties}
+        """
+        self.provider(name).__set(name, value)
+        return self
+    
+    def unset(self, name):
+        """
+        Unset a property by I{name}.
+        @param name: A property name.
+        @type name: str
+        @return: self
+        @rtype: L{Properties}
+        """
+        self.provider(name).__set(name, None)
+        return self
+            
+    def get(self, name, *df):
+        """
+        Get the value of a property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param df: An optional value to be returned when the value
+            is not set
+        @type df: [1].
+        @return: The stored value, or I{df[0]} if not set.
+        @rtype: any 
+        """
+        return self.provider(name).__get(name, *df)
+    
+    def link(self, other):
+        """
+        Link (associate) this object with anI{other} properties object 
+        to create a network of properties.  Links are bidirectional.
+        @param other: The object to link.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        Link(self, other)
+        return self
+
+    def unlink(self, *others):
+        """
+        Unlink (disassociate) the specified properties object.
+        @param others: The list object to unlink.  Unspecified means unlink all.
+        @type others: [L{Properties},..]
+        @return: self
+        @rtype: L{Properties}
+        """
+        if not len(others):
+            others = self.links[:]
+        for p in self.links[:]:
+            if p in others:
+                p.teardown()
+        return self
+    
+    def provider(self, name, history=None):
+        """
+        Find the provider of the property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: The provider when found.  Otherwise, None (when nested)
+            and I{self} when not nested.
+        @rtype: L{Properties}
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        if name in self.definitions:
+            return self
+        for x in self.links:
+            if x in history:
+                continue
+            provider = x.provider(name, history)
+            if provider is not None:
+                return provider
+        history.remove(self)
+        if len(history):
+            return None
+        return self
+    
+    def keys(self, history=None):
+        """
+        Get the set of I{all} property names.
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: A set of property names.
+        @rtype: list
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        keys = set()
+        keys.update(self.definitions.keys())
+        for x in self.links:
+            if x in history:
+                continue
+            keys.update(x.keys(history))
+        history.remove(self)
+        return keys
+    
+    def domains(self, history=None):
+        """
+        Get the set of I{all} domain names.
+        @param history: A history of nodes checked to prevent
+            circular hunting.
+        @type history: [L{Properties},..]
+        @return: A set of domain names.
+        @rtype: list
+        """
+        if history is None:
+            history = []
+        history.append(self)
+        domains = set()
+        domains.add(self.domain)
+        for x in self.links:
+            if x in history:
+                continue
+            domains.update(x.domains(history))
+        history.remove(self)
+        return domains
+ 
+    def prime(self):
+        """
+        Prime the stored values based on default values
+        found in property definitions.
+        @return: self
+        @rtype: L{Properties}
+        """
+        for d in self.definitions.values():
+            self.defined[d.name] = d.default
+        return self
+    
+    def __notset(self, name):
+        return not (name in self.modified)
+    
+    def __set(self, name, value):
+        d = self.definition(name)
+        d.validate(value)
+        value = d.nvl(value)
+        prev = self.defined[name]
+        self.defined[name] = value
+        self.modified.add(name)
+        d.linker.updated(self, prev, value)
+        
+    def __get(self, name, *df):
+        d = self.definition(name)
+        value = self.defined.get(name)
+        if value == d.default and len(df):
+            value = df[0]
+        return value
+            
+    def str(self, history):
+        s = []
+        s.append('Definitions:')
+        for d in self.definitions.values():
+            s.append('\t%s' % repr(d))
+        s.append('Content:')
+        for d in self.defined.items():
+            s.append('\t%s' % str(d))
+        if self not in history:
+            history.append(self)
+            s.append('Linked:')
+            for x in self.links:
+                s.append(x.str(history))
+            history.remove(self)
+        return '\n'.join(s)
+            
+    def __repr__(self):
+        return str(self)
+            
+    def __str__(self):
+        return self.str([])
+
+
+class Skin(object):
+    """
+    The meta-programming I{skin} around the L{Properties} object.
+    @ivar __pts__: The wrapped object.
+    @type __pts__: L{Properties}.
+    """
+    def __init__(self, domain, definitions, kwargs):
+        self.__pts__ = Properties(domain, definitions, kwargs)
+        
+    def __setattr__(self, name, value):
+        builtin = name.startswith('__') and name.endswith('__')
+        if builtin:
+            self.__dict__[name] = value
+            return
+        self.__pts__.set(name, value)
+        
+    def __getattr__(self, name):
+        return self.__pts__.get(name)
+    
+    def __repr__(self):
+        return str(self)
+    
+    def __str__(self):
+        return str(self.__pts__)
+    
+    
+class Unskin(object):
+    def __new__(self, *args, **kwargs):
+        return args[0].__pts__
+    
+    
+class Inspector:
+    """
+    Wrapper inspector.
+    """
+    def __init__(self, options):
+        self.properties = options.__pts__
+        
+    def get(self, name, *df):
+        """
+        Get the value of a property by I{name}.
+        @param name: The property name.
+        @type name: str
+        @param df: An optional value to be returned when the value
+            is not set
+        @type df: [1].
+        @return: The stored value, or I{df[0]} if not set.
+        @rtype: any 
+        """
+        return self.properties.get(name, *df)
+
+    def update(self, **kwargs):
+        """
+        Update the property values as specified by keyword/value.
+        @param kwargs: A list of property name/values to set.
+        @type kwargs: dict
+        @return: self
+        @rtype: L{Properties}
+        """
+        return self.properties.update(**kwargs)
+
+    def link(self, other):
+        """
+        Link (associate) this object with anI{other} properties object 
+        to create a network of properties.  Links are bidirectional.
+        @param other: The object to link.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        p = other.__pts__
+        return self.properties.link(p)
+    
+    def unlink(self, other):
+        """
+        Unlink (disassociate) the specified properties object.
+        @param other: The object to unlink.
+        @type other: L{Properties}
+        @return: self
+        @rtype: L{Properties}
+        """
+        p = other.__pts__
+        return self.properties.unlink(p)
diff --git a/suds/reader.py b/suds/reader.py
new file mode 100644
index 0000000..84e6822
--- /dev/null
+++ b/suds/reader.py
@@ -0,0 +1,142 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains xml document reader classes.
+"""
+
+
+from suds.sax.parser import Parser
+from suds.transport import Request
+from suds.store import DocumentStore
+from logging import getLogger
+
+
+log = getLogger(__name__)
+
+
+class ObjectId(object):
+    
+    def __init__(self, name, suffix):
+        self.name = name
+        self.suffix = suffix
+
+
+class DocumentReader:
+    """
+    The XML document reader provides an integration
+    between the SAX L{Parser} and the document cache.
+    @cvar suffix: The cache file suffix.
+    @type suffix: str
+    @ivar options: An options object.
+    @type options: I{Options}
+    """
+    
+    suffix = 'pxd'
+    
+    def __init__(self, options):
+        """
+        @param options: An options object.
+        @type options: I{Options}
+        """
+        self.options = options
+    
+    def open(self, url):
+        """
+        Open an XML document at the specified I{url}.
+        First, the document attempted to be retrieved from
+        the I{object cache}.  If not found, it is downloaded and
+        parsed using the SAX parser.  The result is added to the
+        cache for the next open().
+        @param url: A document url.
+        @type url: str.
+        @return: The specified XML document.
+        @rtype: I{Document}
+        """
+        id = ObjectId(url, self.suffix)
+        cache = self.options.cache
+        d = cache.get(id)
+        if d is None:
+            d = self.download(url)
+            cache.put(id, d)
+        return d
+    
+    def download(self, url):
+        """
+        Download the docuemnt.
+        @param url: A document url.
+        @type url: str.
+        @return: A file pointer to the docuemnt.
+        @rtype: file-like
+        """
+        store = DocumentStore()
+        fp = store.open(url)
+        if fp is None:
+            fp = self.options.transport.open(Request(url))
+        sax = Parser()
+        return sax.parse(file=fp)
+
+
+class DefinitionsReader:
+    """
+    The WSDL definitions reader provides an integration
+    between the Definitions and the object cache.
+    @cvar suffix: The cache file suffix.
+    @type suffix: str
+    @ivar options: An options object.
+    @type options: I{Options}
+    @ivar fn: A factory function (constructor) used to
+        create the object not found in the cache.
+    @type fn: I{Constructor}
+    """
+    
+    suffix = 'pw'
+    
+    def __init__(self, options, fn):
+        """
+        @param options: An options object.
+        @type options: I{Options}
+        @param fn: A factory function (constructor) used to
+            create the object not found in the cache.
+        @type fn: I{Constructor}
+        """
+        self.options = options
+        self.fn = fn
+    
+    def open(self, url):
+        """
+        Open a WSDL at the specified I{url}.
+        First, the WSDL attempted to be retrieved from
+        the I{object cache}.  After unpickled from the cache, the
+        I{options} attribute is restored.
+        If not found, it is downloaded and instantiated using the 
+        I{fn} constructor and added to the cache for the next open().
+        @param url: A WSDL url.
+        @type url: str.
+        @return: The WSDL object.
+        @rtype: I{Definitions}
+        """
+        id = ObjectId(url, self.suffix)
+        cache = self.options.cache
+        d = cache.get(id)
+        if d is None:
+            d = self.fn(url, self.options)
+            cache.put(id, d)
+        else:
+            d.options = self.options
+            for imp in d.imports:
+                imp.imported.options = self.options
+        return d
diff --git a/suds/resolver.py b/suds/resolver.py
new file mode 100644
index 0000000..278b5da
--- /dev/null
+++ b/suds/resolver.py
@@ -0,0 +1,496 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{resolver} module provides a collection of classes that
+provide wsdl/xsd named type resolution.
+"""
+
+import re
+from logging import getLogger
+from suds import *
+from suds.sax import splitPrefix, Namespace
+from suds.sudsobject import Object
+from suds.xsd.query import BlindQuery, TypeQuery, qualify
+
+log = getLogger(__name__)
+
+
+class Resolver:
+    """
+    An I{abstract} schema-type resolver.
+    @ivar schema: A schema object.
+    @type schema: L{xsd.schema.Schema}
+    """
+
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        self.schema = schema
+        
+    def find(self, name, resolved=True):
+        """
+        Get the definition object for the schema object by name.
+        @param name: The name of a schema object.
+        @type name: basestring
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        log.debug('searching schema for (%s)', name)
+        qref = qualify(name, self.schema.root, self.schema.tns)
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        if result is None:
+            log.error('(%s) not-found', name)
+            return None
+        log.debug('found (%s) as (%s)', name, Repr(result))
+        if resolved:
+            result = result.resolve()
+        return result
+
+
+class PathResolver(Resolver):
+    """
+    Resolveds the definition object for the schema type located at the specified path.
+    The path may contain (.) dot notation to specify nested types.
+    @ivar wsdl: A wsdl object.
+    @type wsdl: L{wsdl.Definitions}
+    """
+
+    def __init__(self, wsdl, ps='.'):
+        """
+        @param wsdl: A schema object.
+        @type wsdl: L{wsdl.Definitions}
+        @param ps: The path separator character
+        @type ps: char
+        """
+        Resolver.__init__(self, wsdl.schema)
+        self.wsdl = wsdl
+        self.altp = re.compile('({)(.+)(})(.+)')
+        self.splitp = re.compile('({.+})*[^\%s]+' % ps[0])
+
+    def find(self, path, resolved=True):
+        """
+        Get the definition object for the schema type located at the specified path.
+        The path may contain (.) dot notation to specify nested types.
+        Actually, the path separator is usually a (.) but can be redefined
+        during contruction.
+        @param path: A (.) separated path to a schema type.
+        @type path: basestring
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = None
+        parts = self.split(path)
+        try:
+            result = self.root(parts)
+            if len(parts) > 1:
+                result = result.resolve(nobuiltin=True)
+                result = self.branch(result, parts)
+                result = self.leaf(result, parts)
+            if resolved:
+                result = result.resolve(nobuiltin=True)
+        except PathResolver.BadPath:
+            log.error('path: "%s", not-found' % path)
+        return result
+    
+    def root(self, parts):
+        """
+        Find the path root.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @return: The root.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = None
+        name = parts[0]
+        log.debug('searching schema for (%s)', name)
+        qref = self.qualify(parts[0])
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        if result is None:
+            log.error('(%s) not-found', name)
+            raise PathResolver.BadPath(name)
+        else:
+            log.debug('found (%s) as (%s)', name, Repr(result))
+        return result
+    
+    def branch(self, root, parts):
+        """
+        Traverse the path until the leaf is reached.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @param root: The root.
+        @type root: L{xsd.sxbase.SchemaObject}
+        @return: The end of the branch.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        result = root
+        for part in parts[1:-1]:
+            name = splitPrefix(part)[1]
+            log.debug('searching parent (%s) for (%s)', Repr(result), name)
+            result, ancestry = result.get_child(name)
+            if result is None:
+                log.error('(%s) not-found', name)
+                raise PathResolver.BadPath(name)
+            else:
+                result = result.resolve(nobuiltin=True)
+                log.debug('found (%s) as (%s)', name, Repr(result))
+        return result
+    
+    def leaf(self, parent, parts):
+        """
+        Find the leaf.
+        @param parts: A list of path parts.
+        @type parts: [str,..]
+        @param parent: The leaf's parent.
+        @type parent: L{xsd.sxbase.SchemaObject}
+        @return: The leaf.
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = splitPrefix(parts[-1])[1]
+        if name.startswith('@'):
+            result, path = parent.get_attribute(name[1:])
+        else:
+            result, ancestry = parent.get_child(name)
+        if result is None:
+            raise PathResolver.BadPath(name)
+        return result
+    
+    def qualify(self, name):
+        """
+        Qualify the name as either:
+          - plain name
+          - ns prefixed name (eg: ns0:Person)
+          - fully ns qualified name (eg: {http://myns-uri}Person)
+        @param name: The name of an object in the schema.
+        @type name: str
+        @return: A qualifed name.
+        @rtype: qname
+        """
+        m = self.altp.match(name)
+        if m is None:
+            return qualify(name, self.wsdl.root, self.wsdl.tns)
+        else:
+            return (m.group(4), m.group(2))
+        
+    def split(self, s):
+        """
+        Split the string on (.) while preserving any (.) inside the
+        '{}' alternalte syntax for full ns qualification.
+        @param s: A plain or qualifed name.
+        @type s: str
+        @return: A list of the name's parts.
+        @rtype: [str,..]
+        """
+        parts = []
+        b = 0
+        while 1:
+            m = self.splitp.match(s, b)
+            if m is None:
+                break
+            b,e = m.span()
+            parts.append(s[b:e])
+            b = e+1
+        return parts
+    
+    class BadPath(Exception): pass
+
+
+class TreeResolver(Resolver):
+    """
+    The tree resolver is a I{stateful} tree resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    @ivar stack: The context stack.
+    @type stack: list
+    """
+    
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        Resolver.__init__(self, schema)
+        self.stack = Stack()
+        
+    def reset(self):
+        """
+        Reset the resolver's state.
+        """
+        self.stack = Stack()
+            
+    def push(self, x):
+        """
+        Push an I{object} onto the stack.
+        @param x: An object to push.
+        @type x: L{Frame}
+        @return: The pushed frame.
+        @rtype: L{Frame}
+        """
+        if isinstance(x, Frame):
+            frame = x
+        else:
+            frame = Frame(x)
+        self.stack.append(frame)
+        log.debug('push: (%s)\n%s', Repr(frame), Repr(self.stack))
+        return frame
+    
+    def top(self):
+        """
+        Get the I{frame} at the top of the stack.
+        @return: The top I{frame}, else None.
+        @rtype: L{Frame}
+        """
+        if len(self.stack):
+            return self.stack[-1]
+        else:
+            return Frame.Empty()
+        
+    def pop(self):
+        """
+        Pop the frame at the top of the stack.
+        @return: The popped frame, else None.
+        @rtype: L{Frame}
+        """
+        if len(self.stack):      
+            popped = self.stack.pop()
+            log.debug('pop: (%s)\n%s', Repr(popped), Repr(self.stack))
+            return popped
+        else:
+            log.debug('stack empty, not-popped')
+        return None
+    
+    def depth(self):
+        """
+        Get the current stack depth.
+        @return: The current stack depth.
+        @rtype: int
+        """
+        return len(self.stack)
+    
+    def getchild(self, name, parent):
+        """ get a child by name """
+        log.debug('searching parent (%s) for (%s)', Repr(parent), name)
+        if name.startswith('@'):
+            return parent.get_attribute(name[1:])
+        else:
+            return parent.get_child(name)
+
+
+class NodeResolver(TreeResolver):
+    """
+    The node resolver is a I{stateful} XML document resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    """
+    
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        TreeResolver.__init__(self, schema)
+        
+    def find(self, node, resolved=False, push=True):
+        """
+        @param node: An xml node to be resolved.
+        @type node: L{sax.element.Element}
+        @param resolved: A flag indicating that the fully resolved type should be
+            returned.
+        @type resolved: boolean
+        @param push: Indicates that the resolved type should be
+            pushed onto the stack.
+        @type push: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = node.name
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name, node)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        known = self.known(node)
+        if result is None:
+            return result
+        if push:
+            frame = Frame(result, resolved=known, ancestry=ancestry)
+            pushed = self.push(frame)
+        if resolved:
+            result = result.resolve()
+        return result
+    
+    def findattr(self, name, resolved=True):
+        """
+        Find an attribute type definition.
+        @param name: An attribute name.
+        @type name: basestring
+        @param resolved: A flag indicating that the fully resolved type should be
+            returned.
+        @type resolved: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        name = '@%s'%name
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name, node)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        if result is None:
+            return result
+        if resolved:
+            result = result.resolve()
+        return result
+    
+    def query(self, name, node):
+        """ blindly query the schema by name """
+        log.debug('searching schema for (%s)', name)
+        qref = qualify(name, node, node.namespace())
+        query = BlindQuery(qref)
+        result = query.execute(self.schema)
+        return (result, [])
+    
+    def known(self, node):
+        """ resolve type referenced by @xsi:type """
+        ref = node.get('type', Namespace.xsins)
+        if ref is None:
+            return None
+        qref = qualify(ref, node, node.namespace())
+        query = BlindQuery(qref)
+        return query.execute(self.schema)
+        
+
+class GraphResolver(TreeResolver):
+    """
+    The graph resolver is a I{stateful} L{Object} graph resolver
+    used to resolve each node in a tree.  As such, it mirrors
+    the tree structure to ensure that nodes are resolved in
+    context.
+    """
+    
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        TreeResolver.__init__(self, schema)
+        
+    def find(self, name, object, resolved=False, push=True):
+        """
+        @param name: The name of the object to be resolved.
+        @type name: basestring
+        @param object: The name's value.
+        @type object: (any|L{Object}) 
+        @param resolved: A flag indicating that the fully resolved type
+            should be returned.
+        @type resolved: boolean
+        @param push: Indicates that the resolved type should be
+            pushed onto the stack.
+        @type push: boolean
+        @return: The found schema I{type}
+        @rtype: L{xsd.sxbase.SchemaObject}
+        """
+        known = None
+        parent = self.top().resolved
+        if parent is None:
+            result, ancestry = self.query(name)
+        else:
+            result, ancestry = self.getchild(name, parent)
+        if result is None:
+            return None
+        if isinstance(object, Object):
+            known = self.known(object)
+        if push:
+            frame = Frame(result, resolved=known, ancestry=ancestry)
+            pushed = self.push(frame)
+        if resolved:
+            if known is None:
+                result = result.resolve()
+            else:
+                result = known
+        return result
+    
+    def query(self, name):
+        """ blindly query the schema by name """
+        log.debug('searching schema for (%s)', name)
+        schema = self.schema
+        wsdl = self.wsdl()
+        if wsdl is None:
+            qref = qualify(name, schema.root, schema.tns)
+        else:
+            qref = qualify(name, wsdl.root, wsdl.tns)
+        query = BlindQuery(qref)
+        result = query.execute(schema)
+        return (result, [])
+    
+    def wsdl(self):
+        """ get the wsdl """
+        container = self.schema.container
+        if container is None:
+            return None
+        else:
+            return container.wsdl
+    
+    def known(self, object):
+        """ get the type specified in the object's metadata """
+        try:
+            md = object.__metadata__
+            known = md.sxtype
+            return known
+        except:
+            pass
+
+       
+class Frame:
+    def __init__(self, type, resolved=None, ancestry=()):
+        self.type = type
+        if resolved is None:
+            resolved = type.resolve()
+        self.resolved = resolved.resolve()
+        self.ancestry = ancestry
+
+    def __str__(self):
+        return '%s\n%s\n%s' % \
+            (Repr(self.type),
+            Repr(self.resolved),
+            [Repr(t) for t in self.ancestry])
+            
+    class Empty:
+        def __getattr__(self, name):
+            if name == 'ancestry':
+                return ()
+            else:
+                return None
+
+
+class Stack(list):
+    def __repr__(self):
+        result = []
+        for item in self:
+            result.append(repr(item))
+        return '\n'.join(result)
\ No newline at end of file
diff --git a/suds/sax/__init__.py b/suds/sax/__init__.py
new file mode 100644
index 0000000..b4d343e
--- /dev/null
+++ b/suds/sax/__init__.py
@@ -0,0 +1,105 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The sax module contains a collection of classes that provide a
+(D)ocument (O)bject (M)odel representation of an XML document.
+The goal is to provide an easy, intuative interface for managing XML
+documents.  Although, the term, DOM, is used above, this model is
+B{far} better.
+
+XML namespaces in suds are represented using a (2) element tuple
+containing the prefix and the URI.  Eg: I{('tns', 'http://myns')}
+
+ at var encoder: A I{pluggable} XML special character processor used to
+    encode/decode strings.
+ at type encoder: L{Encoder}
+"""
+
+from suds.sax.enc import Encoder
+
+#
+# pluggable XML special character encoder.
+#
+encoder = Encoder()
+
+
+def splitPrefix(name):
+    """
+    Split the name into a tuple (I{prefix}, I{name}).  The first element in
+    the tuple is I{None} when the name does't have a prefix.
+    @param name: A node name containing an optional prefix.
+    @type name: basestring
+    @return: A tuple containing the (2) parts of I{name}
+    @rtype: (I{prefix}, I{name}) 
+    """
+    if isinstance(name, basestring) \
+        and ':' in name:
+            return tuple(name.split(':', 1))
+    else:
+        return (None, name)
+
+   
+class Namespace:
+    """
+    The namespace class represents XML namespaces.
+    """
+
+    default = (None, None)
+    xmlns = ('xml', 'http://www.w3.org/XML/1998/namespace')
+    xsdns = ('xs', 'http://www.w3.org/2001/XMLSchema')
+    xsins = ('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
+    all = (xsdns, xsins)
+    
+    @classmethod
+    def create(cls, p=None, u=None):
+        return (p, u)
+    
+    @classmethod
+    def xsd(cls, ns):
+        try:
+            return cls.w3(ns) and ns[1].endswith('XMLSchema')
+        except:
+            pass
+        return False
+    
+    @classmethod
+    def xsi(cls, ns):
+        try:
+            return cls.w3(ns) and ns[1].endswith('XMLSchema-instance')
+        except:
+            pass
+        return False
+    
+    @classmethod
+    def xs(cls, ns):
+        return ( cls.xsd(ns) or cls.xsi(ns) )
+
+    @classmethod
+    def w3(cls, ns):
+        try:
+            return ns[1].startswith('http://www.w3.org')
+        except:
+            pass
+        return False
+    
+    @classmethod
+    def isns(cls, ns):
+        try:
+            return isinstance(ns, tuple) and len(ns) == len(cls.default)
+        except:
+            pass
+        return False
diff --git a/suds/sax/attribute.py b/suds/sax/attribute.py
new file mode 100644
index 0000000..86dfb11
--- /dev/null
+++ b/suds/sax/attribute.py
@@ -0,0 +1,181 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides XML I{attribute} classes.
+"""
+
+import suds.sax
+from logging import getLogger
+from suds import *
+from suds.sax import *
+from suds.sax.text import Text
+
+log = getLogger(__name__)
+
+class Attribute:
+    """
+    An XML attribute object.
+    @ivar parent: The node containing this attribute
+    @type parent: L{element.Element}
+    @ivar prefix: The I{optional} namespace prefix.
+    @type prefix: basestring
+    @ivar name: The I{unqualified} name of the attribute
+    @type name: basestring
+    @ivar value: The attribute's value
+    @type value: basestring
+    """
+    def __init__(self, name, value=None):
+        """
+        @param name: The attribute's name with I{optional} namespace prefix.
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring 
+        """
+        self.parent = None
+        self.prefix, self.name = splitPrefix(name)
+        self.setValue(value)
+        
+    def clone(self, parent=None):
+        """
+        Clone this object.
+        @param parent: The parent for the clone.
+        @type parent: L{element.Element}
+        @return: A copy of this object assigned to the new parent.
+        @rtype: L{Attribute}
+        """
+        a = Attribute(self.qname(), self.value)
+        a.parent = parent
+        return a
+    
+    def qname(self):
+        """
+        Get the B{fully} qualified name of this attribute
+        @return: The fully qualified name.
+        @rtype: basestring
+        """
+        if self.prefix is None:
+            return self.name
+        else:
+            return ':'.join((self.prefix, self.name))
+        
+    def setValue(self, value):
+        """
+        Set the attributes value
+        @param value: The new value (may be None)
+        @type value: basestring
+        @return: self
+        @rtype: L{Attribute}
+        """
+        if isinstance(value, Text):
+            self.value = value
+        else:
+            self.value = Text(value)
+        return self
+        
+    def getValue(self, default=Text('')):
+        """
+        Get the attributes value with optional default.
+        @param default: An optional value to be return when the
+            attribute's has not been set.
+        @type default: basestring
+        @return: The attribute's value, or I{default}
+        @rtype: L{Text}
+        """
+        if self.hasText():
+            return self.value
+        else:
+            return default
+    
+    def hasText(self):
+        """
+        Get whether the attribute has I{text} and that it is not an empty
+        (zero length) string.
+        @return: True when has I{text}.
+        @rtype: boolean
+        """
+        return ( self.value is not None and len(self.value) )
+        
+    def namespace(self):
+        """
+        Get the attributes namespace.  This may either be the namespace
+        defined by an optional prefix, or its parent's namespace.
+        @return: The attribute's namespace
+        @rtype: (I{prefix}, I{name})
+        """
+        if self.prefix is None:
+            return Namespace.default
+        else:
+            return self.resolvePrefix(self.prefix)
+        
+    def resolvePrefix(self, prefix):
+        """
+        Resolve the specified prefix to a known namespace.
+        @param prefix: A declared prefix
+        @type prefix: basestring
+        @return: The namespace that has been mapped to I{prefix}
+        @rtype: (I{prefix}, I{name})
+        """
+        ns = Namespace.default
+        if self.parent is not None:
+            ns = self.parent.resolvePrefix(prefix)
+        return ns
+    
+    def match(self, name=None, ns=None):
+        """
+        Match by (optional) name and/or (optional) namespace.
+        @param name: The optional attribute tag name.
+        @type name: str
+        @param ns: An optional namespace.
+        @type ns: (I{prefix}, I{name})
+        @return: True if matched.
+        @rtype: boolean
+        """
+        if name is None:
+            byname = True
+        else:
+            byname = ( self.name == name )
+        if ns is None:
+            byns = True
+        else:
+            byns = ( self.namespace()[1] == ns[1] )
+        return ( byname and byns )
+    
+    def __eq__(self, rhs):
+        """ equals operator """
+        return rhs is not None and \
+            isinstance(rhs, Attribute) and \
+            self.prefix == rhs.name and \
+            self.name == rhs.name
+            
+    def __repr__(self):
+        """ get a string representation """
+        return \
+            'attr (prefix=%s, name=%s, value=(%s))' %\
+                (self.prefix, self.name, self.value)
+
+    def __str__(self):
+        """ get an xml string representation """
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        """ get an xml string representation """
+        n = self.qname()
+        if self.hasText():
+            v = self.value.escape()
+        else:
+            v = self.value
+        return u'%s="%s"' % (n, v)
diff --git a/suds/sax/date.py b/suds/sax/date.py
new file mode 100644
index 0000000..f07cf84
--- /dev/null
+++ b/suds/sax/date.py
@@ -0,0 +1,357 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Nathan Van Gheem (vangheem at gmail.com)
+
+"""
+The I{xdate} module provides classes for converstion
+between XML dates and python objects.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.xsd import *
+import time
+import datetime as dt
+import re
+
+log = getLogger(__name__)
+
+
+class Date:
+    """
+    An XML date object.
+    Supported formats:
+        - YYYY-MM-DD
+        - YYYY-MM-DD(z|Z)
+        - YYYY-MM-DD+06:00
+        - YYYY-MM-DD-06:00
+    @ivar date: The object value.
+    @type date: B{datetime}.I{date}
+    """
+    def __init__(self, date):
+        """
+        @param date: The value of the object.
+        @type date: (date|str)
+        @raise ValueError: When I{date} is invalid.
+        """
+        if isinstance(date, dt.date):
+            self.date = date
+            return
+        if isinstance(date, basestring):
+            self.date = self.__parse(date)
+            return
+        raise ValueError, type(date)
+    
+    def year(self):
+        """
+        Get the I{year} component.
+        @return: The year.
+        @rtype: int
+        """
+        return self.date.year
+    
+    def month(self):
+        """
+        Get the I{month} component.
+        @return: The month.
+        @rtype: int
+        """
+        return self.date.month
+    
+    def day(self):
+        """
+        Get the I{day} component.
+        @return: The day.
+        @rtype: int
+        """
+        return self.date.day
+        
+    def __parse(self, s):
+        """
+        Parse the string date.
+        Supported formats:
+            - YYYY-MM-DD
+            - YYYY-MM-DD(z|Z)
+            - YYYY-MM-DD+06:00
+            - YYYY-MM-DD-06:00
+        Although, the TZ is ignored because it's meaningless
+        without the time, right?
+        @param s: A date string.
+        @type s: str
+        @return: A date object.
+        @rtype: I{date}
+        """
+        try:
+            year, month, day = s[:10].split('-', 2)
+            year = int(year)
+            month = int(month)
+            day = int(day)
+            return dt.date(year, month, day)
+        except:
+            log.debug(s, exec_info=True)
+            raise ValueError, 'Invalid format "%s"' % s
+        
+    def __str__(self):
+        return unicode(self)
+    
+    def __unicode__(self):
+        return self.date.isoformat()
+
+
+class Time:
+    """
+    An XML time object.
+    Supported formats:
+        - HH:MI:SS
+        - HH:MI:SS(z|Z)
+        - HH:MI:SS.ms
+        - HH:MI:SS.ms(z|Z)
+        - HH:MI:SS(+|-)06:00
+        - HH:MI:SS.ms(+|-)06:00
+    @ivar date: The object value.
+    @type date: B{datetime}.I{time}
+    """
+    
+    def __init__(self, time, adjusted=True):
+        """
+        @param time: The value of the object.
+        @type time: (time|str)
+        @param adjusted: Adjust for I{local} Timezone.
+        @type adjusted: boolean
+        @raise ValueError: When I{time} is invalid.
+        """
+        if isinstance(time, dt.time):
+            self.time = time
+            return
+        if isinstance(time, basestring):
+            self.time = self.__parse(time)
+            if adjusted:
+                self.__adjust()
+            return
+        raise ValueError, type(time)
+    
+    def hour(self):
+        """
+        Get the I{hour} component.
+        @return: The hour.
+        @rtype: int
+        """
+        return self.time.hour
+    
+    def minute(self):
+        """
+        Get the I{minute} component.
+        @return: The minute.
+        @rtype: int
+        """
+        return self.time.minute
+    
+    def second(self):
+        """
+        Get the I{seconds} component.
+        @return: The seconds.
+        @rtype: int
+        """
+        return self.time.second
+    
+    def microsecond(self):
+        """
+        Get the I{microsecond} component.
+        @return: The microsecond.
+        @rtype: int
+        """
+        return self.time.microsecond
+    
+    def __adjust(self):
+        """
+        Adjust for TZ offset.
+        """
+        if hasattr(self, 'offset'):
+            today = dt.date.today()
+            tz = Timezone()
+            delta = Timezone.adjustment(self.offset)
+            d = dt.datetime.combine(today, self.time)
+            d = ( d + delta )
+            self.time = d.time()
+        
+    def __parse(self, s):
+        """
+        Parse the string date.
+        Patterns:
+            - HH:MI:SS
+            - HH:MI:SS(z|Z)
+            - HH:MI:SS.ms
+            - HH:MI:SS.ms(z|Z)
+            - HH:MI:SS(+|-)06:00
+            - HH:MI:SS.ms(+|-)06:00
+        @param s: A time string.
+        @type s: str
+        @return: A time object.
+        @rtype: B{datetime}.I{time}
+        """
+        try:
+            offset = None
+            part = Timezone.split(s)
+            hour, minute, second = part[0].split(':', 2)
+            hour = int(hour)
+            minute = int(minute)
+            second, ms = self.__second(second)
+            if len(part) == 2:
+                self.offset = self.__offset(part[1])
+            if ms is None:
+                return dt.time(hour, minute, second)
+            else:
+                return dt.time(hour, minute, second, ms)
+        except:
+            log.debug(s, exec_info=True)
+            raise ValueError, 'Invalid format "%s"' % s
+        
+    def __second(self, s):
+        """
+        Parse the seconds and microseconds.
+        The microseconds are truncated to 999999 due to a restriction in
+        the python datetime.datetime object.
+        @param s: A string representation of the seconds.
+        @type s: str
+        @return: Tuple of (sec,ms)
+        @rtype: tuple.
+        """
+        part = s.split('.')
+        if len(part) > 1:
+            return (int(part[0]), int(part[1][:6]))
+        else:
+            return (int(part[0]), None)
+        
+    def __offset(self, s):
+        """
+        Parse the TZ offset.
+        @param s: A string representation of the TZ offset.
+        @type s: str
+        @return: The signed offset in hours.
+        @rtype: str
+        """
+        if len(s) == len('-00:00'):
+            return int(s[:3])
+        if len(s) == 0:
+            return Timezone.local
+        if len(s) == 1:
+            return 0
+        raise Exception()
+
+    def __str__(self):
+        return unicode(self)
+    
+    def __unicode__(self):
+        time = self.time.isoformat()
+        return '%s%+.2d:00' % (time, Timezone.local)
+
+
+class DateTime(Date,Time):
+    """
+    An XML time object.
+    Supported formats:
+        - YYYY-MM-DDB{T}HH:MI:SS
+        - YYYY-MM-DDB{T}HH:MI:SS(z|Z)
+        - YYYY-MM-DDB{T}HH:MI:SS.ms
+        - YYYY-MM-DDB{T}HH:MI:SS.ms(z|Z)
+        - YYYY-MM-DDB{T}HH:MI:SS(+|-)06:00
+        - YYYY-MM-DDB{T}HH:MI:SS.ms(+|-)06:00
+    @ivar datetime: The object value.
+    @type datetime: B{datetime}.I{datedate}
+    """
+    def __init__(self, date):
+        """
+        @param date: The value of the object.
+        @type date: (datetime|str)
+        @raise ValueError: When I{tm} is invalid.
+        """
+        if isinstance(date, dt.datetime):
+            Date.__init__(self, date.date())
+            Time.__init__(self, date.time())
+            self.datetime = \
+                dt.datetime.combine(self.date, self.time)
+            return
+        if isinstance(date, basestring):
+            part = date.split('T')
+            Date.__init__(self, part[0])
+            Time.__init__(self, part[1], 0)
+            self.datetime = \
+                dt.datetime.combine(self.date, self.time)
+            self.__adjust()
+            return
+        raise ValueError, type(date)
+    
+    def __adjust(self):
+        """
+        Adjust for TZ offset.
+        """
+        if not hasattr(self, 'offset'):
+            return
+        tz = Timezone()
+        delta = Timezone.adjustment(self.offset)
+        try:
+            d = ( self.datetime + delta )
+            self.datetime = d
+            self.date = d.date()
+            self.time = d.time()
+        except OverflowError:
+            log.warn('"%s" caused overflow, not-adjusted', self.datetime)
+
+    def __str__(self):
+        return unicode(self)
+    
+    def __unicode__(self):
+        s = []
+        s.append(Date.__unicode__(self))
+        s.append(Time.__unicode__(self))
+        return 'T'.join(s)
+    
+    
+class Timezone:
+    """
+    Timezone object used to do TZ conversions
+    @cvar local: The (A) local TZ offset.
+    @type local: int
+    @cvar patten: The regex patten to match TZ.
+    @type patten: L{re.RegexObject}
+    """
+
+    local = ( 0-time.timezone/60/60 )
+    pattern = re.compile('([zZ])|([\-\+][0-9]{2}:[0-9]{2})')
+    
+    @classmethod
+    def split(cls, s):
+        """
+        Split the TZ from string.
+        @param s: A string containing a timezone
+        @type s: basestring
+        @return: The split parts.
+        @rtype: tuple
+        """
+        m = cls.pattern.search(s)
+        if m is None:
+            return (s,)
+        x = m.start(0)
+        return (s[:x], s[x:])
+    
+    @classmethod
+    def adjustment(cls, offset):
+        """
+        Get the adjustment to the I{local} TZ.
+        @return: The delta between I{offset} and local TZ.
+        @rtype: B{datetime}.I{timedelta}
+        """
+        delta = ( cls.local - offset )
+        return dt.timedelta(hours=delta)
diff --git a/suds/sax/document.py b/suds/sax/document.py
new file mode 100644
index 0000000..c7129cb
--- /dev/null
+++ b/suds/sax/document.py
@@ -0,0 +1,51 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides XML I{document} classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax import *
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+class Document(Element):
+    """ simple document """
+
+    def __init__(self, root=None):
+        Element.__init__(self, 'document')
+        if root is not None:
+            self.append(root)
+        
+    def root(self):
+        if len(self.children) > 0:
+            return self.children[0]
+        else:
+            return None
+        
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        result = '<?xml version="1.0" encoding="UTF-8"?>'
+        root = self.root()
+        if root is not None:
+            result += '\n'
+            result += root.str()
+        return unicode(result)
diff --git a/suds/sax/element.py b/suds/sax/element.py
new file mode 100644
index 0000000..ca44801
--- /dev/null
+++ b/suds/sax/element.py
@@ -0,0 +1,1089 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides XML I{element} classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax import *
+from suds.sax.text import Text
+from suds.sax.attribute import Attribute
+import sys 
+if sys.version_info < (2, 4, 0): 
+    from sets import Set as set 
+    del sys 
+
+log = getLogger(__name__)
+
+class Element:
+    """
+    An XML element object.
+    @ivar parent: The node containing this attribute
+    @type parent: L{Element}
+    @ivar prefix: The I{optional} namespace prefix.
+    @type prefix: basestring
+    @ivar name: The I{unqualified} name of the attribute
+    @type name: basestring
+    @ivar expns: An explicit namespace (xmlns="...").
+    @type expns: (I{prefix}, I{name})
+    @ivar nsprefixes: A mapping of prefixes to namespaces.
+    @type nsprefixes: dict
+    @ivar attributes: A list of XML attributes.
+    @type attributes: [I{Attribute},]
+    @ivar text: The element's I{text} content.
+    @type text: basestring
+    @ivar children: A list of child elements.
+    @type children: [I{Element},]
+    @cvar matcher: A collection of I{lambda} for string matching.
+    @cvar specialprefixes: A dictionary of builtin-special prefixes.
+    """
+
+    matcher = \
+    {
+        'eq': lambda a,b: a == b,
+        'startswith' : lambda a,b: a.startswith(b),
+        'endswith' : lambda a,b: a.endswith(b),
+        'contains' : lambda a,b: b in a 
+    }
+    
+    specialprefixes = { Namespace.xmlns[0] : Namespace.xmlns[1]  }
+    
+    @classmethod
+    def buildPath(self, parent, path):
+        """
+        Build the specifed pat as a/b/c where missing intermediate nodes are built
+        automatically.
+        @param parent: A parent element on which the path is built.
+        @type parent: I{Element}
+        @param path: A simple path separated by (/).
+        @type path: basestring
+        @return: The leaf node of I{path}.
+        @rtype: L{Element}
+        """
+        for tag in path.split('/'):
+            child = parent.getChild(tag)
+            if child is None:
+                child = Element(tag, parent)
+            parent = child
+        return child
+
+    def __init__(self, name, parent=None, ns=None):
+        """
+        @param name: The element's (tag) name.  May cotain a prefix.
+        @type name: basestring
+        @param parent: An optional parent element.
+        @type parent: I{Element}
+        @param ns: An optional namespace
+        @type ns: (I{prefix}, I{name})
+        """
+        
+        self.rename(name)
+        self.expns = None
+        self.nsprefixes = {}
+        self.attributes = []
+        self.text = None
+        if parent is not None:
+            if isinstance(parent, Element):
+                self.parent = parent
+            else:
+                raise Exception('parent (%s) not-valid', parent.__class__.__name__)
+        else:
+            self.parent = None
+        self.children = []
+        self.applyns(ns)
+        
+    def rename(self, name):
+        """
+        Rename the element.
+        @param name: A new name for the element.
+        @type name: basestring 
+        """
+        if name is None:
+            raise Exception('name (%s) not-valid' % name)
+        else:
+            self.prefix, self.name = splitPrefix(name)
+            
+    def setPrefix(self, p, u=None):
+        """
+        Set the element namespace prefix.
+        @param p: A new prefix for the element.
+        @type p: basestring 
+        @param u: A namespace URI to be mapped to the prefix.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        self.prefix = p
+        if p is not None and u is not None:
+            self.addPrefix(p, u)
+        return self
+
+    def qname(self):
+        """
+        Get the B{fully} qualified name of this element
+        @return: The fully qualified name.
+        @rtype: basestring
+        """
+        if self.prefix is None:
+            return self.name
+        else:
+            return '%s:%s' % (self.prefix, self.name)
+        
+    def getRoot(self):
+        """
+        Get the root (top) node of the tree.
+        @return: The I{top} node of this tree.
+        @rtype: I{Element}
+        """
+        if self.parent is None:
+            return self
+        else:
+            return self.parent.getRoot()
+        
+    def clone(self, parent=None):
+        """
+        Deep clone of this element and children.
+        @param parent: An optional parent for the copied fragment.
+        @type parent: I{Element}
+        @return: A deep copy parented by I{parent}
+        @rtype: I{Element}
+        """
+        root = Element(self.qname(), parent, self.namespace())
+        for a in self.attributes:
+            root.append(a.clone(self))
+        for c in self.children:
+            root.append(c.clone(self))
+        for item in self.nsprefixes.items():
+            root.addPrefix(item[0], item[1])
+        return root
+    
+    def detach(self):
+        """
+        Detach from parent.
+        @return: This element removed from its parent's
+            child list and I{parent}=I{None}
+        @rtype: L{Element}
+        """
+        if self.parent is not None:
+            if self in self.parent.children:
+                self.parent.children.remove(self)
+            self.parent = None
+        return self
+        
+    def set(self, name, value):
+        """
+        Set an attribute's value.
+        @param name: The name of the attribute.
+        @type name: basestring
+        @param value: The attribute value.
+        @type value: basestring
+        @see: __setitem__()
+        """
+        attr = self.getAttribute(name)
+        if attr is None:
+            attr = Attribute(name, value)
+            self.append(attr)
+        else:
+            attr.setValue(value)
+            
+    def unset(self, name):
+        """
+        Unset (remove) an attribute.
+        @param name: The attribute name.
+        @type name: str
+        @return: self
+        @rtype: L{Element}
+        """
+        try:
+            attr = self.getAttribute(name)
+            self.attributes.remove(attr)
+        except:
+            pass
+        return self
+            
+            
+    def get(self, name, ns=None, default=None):
+        """
+        Get the value of an attribute by name.
+        @param name: The name of the attribute.
+        @type name: basestring
+        @param ns: The optional attribute's namespace.
+        @type ns: (I{prefix}, I{name})
+        @param default: An optional value to be returned when either
+            the attribute does not exist of has not value.
+        @type default: basestring
+        @return: The attribute's value or I{default}
+        @rtype: basestring
+        @see: __getitem__()
+        """
+        attr = self.getAttribute(name, ns)
+        if attr is None or attr.value is None:
+            return default
+        else:
+            return attr.getValue()   
+
+    def setText(self, value):
+        """
+        Set the element's L{Text} content.
+        @param value: The element's text value.
+        @type value: basestring
+        @return: self
+        @rtype: I{Element}
+        """
+        if isinstance(value, Text):
+            self.text = value
+        else:
+            self.text = Text(value)
+        return self
+        
+    def getText(self, default=None):
+        """
+        Get the element's L{Text} content with optional default
+        @param default: A value to be returned when no text content exists.
+        @type default: basestring
+        @return: The text content, or I{default}
+        @rtype: L{Text}
+        """
+        if self.hasText():
+            return self.text
+        else:
+            return default
+    
+    def trim(self):
+        """
+        Trim leading and trailing whitespace.
+        @return: self
+        @rtype: L{Element}
+        """
+        if self.hasText():
+            self.text = self.text.trim()
+        return self
+    
+    def hasText(self):
+        """
+        Get whether the element has I{text} and that it is not an empty
+        (zero length) string.
+        @return: True when has I{text}.
+        @rtype: boolean
+        """
+        return ( self.text is not None and len(self.text) )
+        
+    def namespace(self):
+        """
+        Get the element's namespace.
+        @return: The element's namespace by resolving the prefix, the explicit 
+            namespace or the inherited namespace.
+        @rtype: (I{prefix}, I{name})
+        """
+        if self.prefix is None:
+            return self.defaultNamespace()
+        else:
+            return self.resolvePrefix(self.prefix)
+        
+    def defaultNamespace(self):
+        """
+        Get the default (unqualified namespace).  
+        This is the expns of the first node (looking up the tree)
+        that has it set.
+        @return: The namespace of a node when not qualified.
+        @rtype: (I{prefix}, I{name})
+        """
+        p = self
+        while p is not None:
+            if p.expns is not None:
+                return (None, p.expns)
+            else:
+                p = p.parent
+        return Namespace.default
+            
+    def append(self, objects):
+        """
+        Append the specified child based on whether it is an
+        element or an attrbuite.
+        @param objects: A (single|collection) of attribute(s) or element(s)
+            to be added as children.
+        @type objects: (L{Element}|L{Attribute})
+        @return: self
+        @rtype: L{Element}
+        """
+        if not isinstance(objects, (list, tuple)):
+            objects = (objects,)
+        for child in objects:
+            if isinstance(child, Element):
+                self.children.append(child)
+                child.parent = self
+                continue
+            if isinstance(child, Attribute):
+                self.attributes.append(child)
+                child.parent = self
+                continue
+            raise Exception('append %s not-valid' % child.__class__.__name__)
+        return self
+    
+    def insert(self, objects, index=0):
+        """
+        Insert an L{Element} content at the specified index.
+        @param objects: A (single|collection) of attribute(s) or element(s)
+            to be added as children.
+        @type objects: (L{Element}|L{Attribute})
+        @param index: The position in the list of children to insert.
+        @type index: int
+        @return: self
+        @rtype: L{Element}
+        """
+        objects = (objects,)
+        for child in objects:
+            if isinstance(child, Element):
+                self.children.insert(index, child)
+                child.parent = self
+            else:
+                raise Exception('append %s not-valid' % child.__class__.__name__)
+        return self
+    
+    def remove(self, child):
+        """
+        Remove the specified child element or attribute.
+        @param child: A child to remove.
+        @type child: L{Element}|L{Attribute}
+        @return: The detached I{child} when I{child} is an element, else None.
+        @rtype: L{Element}|None
+        """
+        if isinstance(child, Element):
+            return child.detach()
+        if isinstance(child, Attribute):
+            self.attributes.remove(child)
+        return None
+            
+    def replaceChild(self, child, content):
+        """
+        Replace I{child} with the specified I{content}.
+        @param child: A child element.
+        @type child: L{Element}
+        @param content: An element or collection of elements.
+        @type content: L{Element} or [L{Element},]
+        """
+        if child not in self.children:
+            raise Exception('child not-found')
+        index = self.children.index(child)
+        self.remove(child)
+        if not isinstance(content, (list, tuple)):
+            content = (content,)
+        for node in content:
+            self.children.insert(index, node.detach())
+            node.parent = self
+            index += 1
+            
+    def getAttribute(self, name, ns=None, default=None):
+        """
+        Get an attribute by name and (optional) namespace
+        @param name: The name of a contained attribute (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace
+        @type ns: (I{prefix}, I{name})
+        @param default: Returned when attribute not-found.
+        @type default: L{Attribute}
+        @return: The requested attribute object.
+        @rtype: L{Attribute}
+        """
+        if ns is None:
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        for a in self.attributes:
+            if a.match(name, ns):
+                return a
+        return default
+
+    def getChild(self, name, ns=None, default=None):
+        """
+        Get a child by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @param default: Returned when child not-found.
+        @type default: L{Element}
+        @return: The requested child, or I{default} when not-found.
+        @rtype: L{Element}
+        """
+        if ns is None:
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        for c in self.children:
+            if c.match(name, ns):
+                return c
+        return default
+    
+    def childAtPath(self, path):
+        """
+        Get a child at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The leaf node at the end of I{path}
+        @rtype: L{Element}
+        """
+        result = None
+        node = self
+        for name in [p for p in path.split('/') if len(p) > 0]:
+            ns = None
+            prefix, name = splitPrefix(name)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            result = node.getChild(name, ns)
+            if result is None:
+                break;
+            else:
+                node = result
+        return result
+
+    def childrenAtPath(self, path):
+        """
+        Get a list of children at I{path} where I{path} is a (/) separated
+        list of element names that are expected to be children.
+        @param path: A (/) separated list of element names.
+        @type path: basestring
+        @return: The collection leaf nodes at the end of I{path}
+        @rtype: [L{Element},...]
+        """
+        parts = [p for p in path.split('/') if len(p) > 0]
+        if len(parts) == 1:
+            result = self.getChildren(path)
+        else:
+            result = self.__childrenAtPath(parts)
+        return result
+        
+    def getChildren(self, name=None, ns=None):
+        """
+        Get a list of children by (optional) name and/or (optional) namespace.
+        @param name: The name of a child element (may contain prefix).
+        @type name: basestring
+        @param ns: An optional namespace used to match the child.
+        @type ns: (I{prefix}, I{name})
+        @return: The list of matching children.
+        @rtype: [L{Element},...]
+        """
+        if ns is None:
+            if name is None:
+                return self.children
+            prefix, name = splitPrefix(name)
+            if prefix is None:
+                ns = None
+            else:
+                ns = self.resolvePrefix(prefix)
+        return [c for c in self.children if c.match(name, ns)]
+    
+    def detachChildren(self):
+        """
+        Detach and return this element's children.
+        @return: The element's children (detached).
+        @rtype: [L{Element},...]
+        """
+        detached = self.children
+        self.children = []
+        for child in detached:
+            child.parent = None
+        return detached
+        
+    def resolvePrefix(self, prefix, default=Namespace.default):
+        """
+        Resolve the specified prefix to a namespace.  The I{nsprefixes} is
+        searched.  If not found, it walks up the tree until either resolved or
+        the top of the tree is reached.  Searching up the tree provides for
+        inherited mappings.
+        @param prefix: A namespace prefix to resolve.
+        @type prefix: basestring
+        @param default: An optional value to be returned when the prefix
+            cannot be resolved.
+        @type default: (I{prefix},I{URI})
+        @return: The namespace that is mapped to I{prefix} in this context.
+        @rtype: (I{prefix},I{URI})
+        """
+        n = self
+        while n is not None:
+            if prefix in n.nsprefixes:
+                return (prefix, n.nsprefixes[prefix])
+            if prefix in self.specialprefixes:
+                return (prefix, self.specialprefixes[prefix])
+            n = n.parent
+        return default
+    
+    def addPrefix(self, p, u):
+        """
+        Add or update a prefix mapping.
+        @param p: A prefix.
+        @type p: basestring
+        @param u: A namespace URI.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        self.nsprefixes[p] = u
+        return self
+ 
+    def updatePrefix(self, p, u):
+        """
+        Update (redefine) a prefix mapping for the branch. 
+        @param p: A prefix.
+        @type p: basestring
+        @param u: A namespace URI.
+        @type u: basestring
+        @return: self
+        @rtype: L{Element}
+        @note: This method traverses down the entire branch!
+        """
+        if p in self.nsprefixes:
+            self.nsprefixes[p] = u
+        for c in self.children:
+            c.updatePrefix(p, u)
+        return self
+            
+    def clearPrefix(self, prefix):
+        """
+        Clear the specified prefix from the prefix mappings.
+        @param prefix: A prefix to clear.
+        @type prefix: basestring
+        @return: self
+        @rtype: L{Element}
+        """
+        if prefix in self.nsprefixes:
+            del self.nsprefixes[prefix]
+        return self
+    
+    def findPrefix(self, uri, default=None):
+        """
+        Find the first prefix that has been mapped to a namespace URI.
+        The local mapping is searched, then it walks up the tree until
+        it reaches the top or finds a match.
+        @param uri: A namespace URI.
+        @type uri: basestring
+        @param default: A default prefix when not found.
+        @type default: basestring
+        @return: A mapped prefix.
+        @rtype: basestring
+        """
+        for item in self.nsprefixes.items():
+            if item[1] == uri:
+                prefix = item[0]
+                return prefix
+        for item in self.specialprefixes.items():
+            if item[1] == uri:
+                prefix = item[0]
+                return prefix      
+        if self.parent is not None:
+            return self.parent.findPrefix(uri, default)
+        else:
+            return default
+
+    def findPrefixes(self, uri, match='eq'):
+        """
+        Find all prefixes that has been mapped to a namespace URI.
+        The local mapping is searched, then it walks up the tree until
+        it reaches the top collecting all matches.
+        @param uri: A namespace URI.
+        @type uri: basestring
+        @param match: A matching function L{Element.matcher}.
+        @type match: basestring
+        @return: A list of mapped prefixes.
+        @rtype: [basestring,...]
+        """
+        result = []
+        for item in self.nsprefixes.items():
+            if self.matcher[match](item[1], uri):
+                prefix = item[0]
+                result.append(prefix)
+        for item in self.specialprefixes.items():
+            if self.matcher[match](item[1], uri):
+                prefix = item[0]
+                result.append(prefix)
+        if self.parent is not None:
+            result += self.parent.findPrefixes(uri, match)
+        return result
+    
+    def promotePrefixes(self):
+        """
+        Push prefix declarations up the tree as far as possible.  Prefix
+        mapping are pushed to its parent unless the parent has the
+        prefix mapped to another URI or the parent has the prefix.
+        This is propagated up the tree until the top is reached.
+        @return: self
+        @rtype: L{Element}
+        """
+        for c in self.children:
+            c.promotePrefixes()
+        if self.parent is None:
+            return
+        for p,u in self.nsprefixes.items():
+            if p in self.parent.nsprefixes:
+                pu = self.parent.nsprefixes[p]
+                if pu == u:
+                    del self.nsprefixes[p]
+                continue
+            if p != self.parent.prefix:
+                self.parent.nsprefixes[p] = u
+                del self.nsprefixes[p]
+        return self
+    
+    def refitPrefixes(self):
+        """
+        Refit namespace qualification by replacing prefixes
+        with explicit namespaces. Also purges prefix mapping table.
+        @return: self
+        @rtype: L{Element}
+        """
+        for c in self.children:
+            c.refitPrefixes()
+        if self.prefix is not None:
+            ns = self.resolvePrefix(self.prefix)
+            if ns[1] is not None:
+                self.expns = ns[1]
+        self.prefix = None
+        self.nsprefixes = {}
+        return self
+                
+    def normalizePrefixes(self):
+        """
+        Normalize the namespace prefixes.
+        This generates unique prefixes for all namespaces.  Then retrofits all
+        prefixes and prefix mappings.  Further, it will retrofix attribute values
+        that have values containing (:).
+        @return: self
+        @rtype: L{Element}
+        """
+        PrefixNormalizer.apply(self)
+        return self
+
+    def isempty(self, content=True):
+        """
+        Get whether the element has no children.
+        @param content: Test content (children & text) only.
+        @type content: boolean
+        @return: True when element has not children.
+        @rtype: boolean
+        """
+        noattrs = not len(self.attributes)
+        nochildren = not len(self.children)
+        notext = ( self.text is None )
+        nocontent = ( nochildren and notext )
+        if content:
+            return nocontent
+        else:
+            return ( nocontent and noattrs )
+            
+            
+    def isnil(self):
+        """
+        Get whether the element is I{nil} as defined by having
+        an attribute in the I{xsi:nil="true"}
+        @return: True if I{nil}, else False
+        @rtype: boolean
+        """
+        nilattr = self.getAttribute('nil', ns=Namespace.xsins)
+        if nilattr is None:
+            return False
+        else:
+            return ( nilattr.getValue().lower() == 'true' )
+        
+    def setnil(self, flag=True):
+        """
+        Set this node to I{nil} as defined by having an
+        attribute I{xsi:nil}=I{flag}.
+        @param flag: A flag inidcating how I{xsi:nil} will be set.
+        @type flag: boolean
+        @return: self
+        @rtype: L{Element}
+        """
+        p, u = Namespace.xsins
+        name  = ':'.join((p, 'nil'))
+        self.set(name, str(flag).lower())
+        self.addPrefix(p, u)
+        if flag:
+            self.text = None
+        return self
+            
+    def applyns(self, ns):
+        """
+        Apply the namespace to this node.  If the prefix is I{None} then
+        this element's explicit namespace I{expns} is set to the
+        URI defined by I{ns}.  Otherwise, the I{ns} is simply mapped.
+        @param ns: A namespace.
+        @type ns: (I{prefix},I{URI})
+        """
+        if ns is None:
+            return
+        if not isinstance(ns, (tuple,list)):
+            raise Exception('namespace must be tuple')
+        if ns[0] is None:
+            self.expns = ns[1]
+        else:
+            self.prefix = ns[0]
+            self.nsprefixes[ns[0]] = ns[1]
+            
+    def str(self, indent=0):
+        """
+        Get a string representation of this XML fragment.
+        @param indent: The indent to be used in formatting the output.
+        @type indent: int
+        @return: A I{pretty} string.
+        @rtype: basestring
+        """
+        tab = '%*s'%(indent*3,'')
+        result = []
+        result.append('%s<%s' % (tab, self.qname()))
+        result.append(self.nsdeclarations())
+        for a in [unicode(a) for a in self.attributes]:
+            result.append(' %s' % a)
+        if self.isempty():
+            result.append('/>')
+            return ''.join(result)
+        result.append('>')
+        if self.hasText():
+            result.append(self.text.escape())
+        for c in self.children:
+            result.append('\n')
+            result.append(c.str(indent+1))
+        if len(self.children):
+            result.append('\n%s' % tab)
+        result.append('</%s>' % self.qname())
+        result = ''.join(result)
+        return result
+
+    def nsdeclarations(self):
+        """
+        Get a string representation for all namespace declarations
+        as xmlns="" and xmlns:p="".
+        @return: A separated list of declarations.
+        @rtype: basestring
+        """
+        s = []
+        myns = (None, self.expns)
+        if self.parent is None:
+            pns = Namespace.default
+        else:
+            pns = (None, self.parent.expns)
+        if myns[1] != pns[1]:
+            if self.expns is not None:
+                d = ' xmlns="%s"' % self.expns
+                s.append(d)
+        for item in self.nsprefixes.items():
+            (p,u) = item
+            if self.parent is not None:
+                ns = self.parent.resolvePrefix(p)
+                if ns[1] == u: continue
+            d = ' xmlns:%s="%s"' % (p, u)
+            s.append(d)
+        return ''.join(s)
+    
+    def match(self, name=None, ns=None):
+        """
+        Match by (optional) name and/or (optional) namespace.
+        @param name: The optional element tag name.
+        @type name: str
+        @param ns: An optional namespace.
+        @type ns: (I{prefix}, I{name})
+        @return: True if matched.
+        @rtype: boolean
+        """
+        if name is None:
+            byname = True
+        else:
+            byname = ( self.name == name )
+        if ns is None:
+            byns = True
+        else:
+            byns = ( self.namespace()[1] == ns[1] )
+        return ( byname and byns )
+    
+    def branch(self):
+        """
+        Get a flattened representation of the branch.
+        @return: A flat list of nodes.
+        @rtype: [L{Element},..]
+        """
+        branch = [self]
+        for c in self.children:
+            branch += c.branch()
+        return branch
+    
+    def ancestors(self):
+        """
+        Get a list of ancestors.
+        @return: A list of ancestors.
+        @rtype: [L{Element},..]
+        """
+        ancestors = []
+        p = self.parent
+        while p is not None:
+            ancestors.append(p)
+            p = p.parent
+        return ancestors
+    
+    def walk(self, visitor):
+        """
+        Walk the branch and call the visitor function
+        on each node.
+        @param visitor: A function.
+        @return: self
+        @rtype: L{Element}
+        """
+        visitor(self)
+        for c in self.children:
+            c.walk(visitor)
+        return self
+    
+    def prune(self):
+        """
+        Prune the branch of empty nodes.
+        """
+        pruned = []
+        for c in self.children:
+            c.prune()
+            if c.isempty(False):
+                pruned.append(c)
+        for p in pruned:
+            self.children.remove(p)
+                
+            
+    def __childrenAtPath(self, parts):
+        result = []
+        node = self
+        last = len(parts)-1
+        ancestors = parts[:last]
+        leaf = parts[last]
+        for name in ancestors:
+            ns = None
+            prefix, name = splitPrefix(name)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            child = node.getChild(name, ns)
+            if child is None:
+                break
+            else:
+                node = child
+        if child is not None:
+            ns = None
+            prefix, leaf = splitPrefix(leaf)
+            if prefix is not None:
+                ns = node.resolvePrefix(prefix)
+            result = child.getChildren(leaf)
+        return result
+    
+    def __len__(self):
+        return len(self.children)
+                
+    def __getitem__(self, index):
+        if isinstance(index, basestring):
+            return self.get(index)
+        else:
+            if index < len(self.children):
+                return self.children[index]
+            else:
+                return None
+        
+    def __setitem__(self, index, value):
+        if isinstance(index, basestring):
+            self.set(index, value)
+        else:
+            if index < len(self.children) and \
+                isinstance(value, Element):
+                self.children.insert(index, value)
+
+    def __eq__(self, rhs):
+        return  rhs is not None and \
+            isinstance(rhs, Element) and \
+            self.name == rhs.name and \
+            self.namespace()[1] == rhs.namespace()[1]
+        
+    def __repr__(self):
+        return \
+            'Element (prefix=%s, name=%s)' % (self.prefix, self.name)
+    
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        return self.str()
+
+
+class PrefixNormalizer:
+    """
+    The prefix normalizer provides namespace prefix normalization.
+    @ivar node: A node to normalize.
+    @type node: L{Element}
+    @ivar branch: The nodes flattened branch.
+    @type branch: [L{Element},..]
+    @ivar namespaces: A unique list of namespaces (URI).
+    @type namespaces: [str,]
+    @ivar prefixes: A reverse dict of prefixes.
+    @type prefixes: {u, p}
+    """
+    
+    @classmethod
+    def apply(cls, node):
+        """
+        Normalize the specified node.
+        @param node: A node to normalize.
+        @type node: L{Element}
+        @return: The normalized node.
+        @rtype: L{Element}
+        """
+        pn = PrefixNormalizer(node)
+        return pn.refit()
+    
+    def __init__(self, node):
+        """
+        @param node: A node to normalize.
+        @type node: L{Element}
+        """
+        self.node = node
+        self.branch = node.branch()
+        self.namespaces = self.getNamespaces()
+        self.prefixes = self.genPrefixes()
+        
+    def getNamespaces(self):
+        """
+        Get the I{unique} set of namespaces referenced in the branch.
+        @return: A set of namespaces.
+        @rtype: set
+        """
+        s = set()
+        for n in self.branch + self.node.ancestors():
+            if self.permit(n.expns):
+                s.add(n.expns)
+            s = s.union(self.pset(n))
+        return s
+    
+    def pset(self, n):
+        """
+        Convert the nodes nsprefixes into a set.
+        @param n: A node.
+        @type n: L{Element}
+        @return: A set of namespaces.
+        @rtype: set
+        """
+        s = set()
+        for ns in n.nsprefixes.items():
+            if self.permit(ns):
+                s.add(ns[1])
+        return s
+            
+    def genPrefixes(self):
+        """
+        Generate a I{reverse} mapping of unique prefixes for all namespaces.
+        @return: A referse dict of prefixes.
+        @rtype: {u, p}
+        """
+        prefixes = {}
+        n = 0
+        for u in self.namespaces:
+            p = 'ns%d' % n
+            prefixes[u] = p
+            n += 1
+        return prefixes
+    
+    def refit(self):
+        """
+        Refit (normalize) the prefixes in the node.
+        """
+        self.refitNodes()
+        self.refitMappings()
+    
+    def refitNodes(self):
+        """
+        Refit (normalize) all of the nodes in the branch.
+        """
+        for n in self.branch:
+            if n.prefix is not None:
+                ns = n.namespace()
+                if self.permit(ns):
+                    n.prefix = self.prefixes[ns[1]]
+            self.refitAttrs(n)
+                
+    def refitAttrs(self, n):
+        """
+        Refit (normalize) all of the attributes in the node.
+        @param n: A node.
+        @type n: L{Element}
+        """
+        for a in n.attributes:
+            self.refitAddr(a)
+    
+    def refitAddr(self, a):
+        """
+        Refit (normalize) the attribute.
+        @param a: An attribute.
+        @type a: L{Attribute}
+        """
+        if a.prefix is not None:
+            ns = a.namespace()
+            if self.permit(ns):
+                a.prefix = self.prefixes[ns[1]]
+        self.refitValue(a)
+    
+    def refitValue(self, a):
+        """
+        Refit (normalize) the attribute's value.
+        @param a: An attribute.
+        @type a: L{Attribute}
+        """
+        p,name = splitPrefix(a.getValue())
+        if p is None: return
+        ns = a.resolvePrefix(p)
+        if self.permit(ns):
+            u = ns[1]
+            p = self.prefixes[u]
+            a.setValue(':'.join((p, name)))
+            
+    def refitMappings(self):
+        """
+        Refit (normalize) all of the nsprefix mappings.
+        """
+        for n in self.branch:
+            n.nsprefixes = {}
+        n = self.node
+        for u, p in self.prefixes.items():
+            n.addPrefix(p, u)
+            
+    def permit(self, ns):
+        """
+        Get whether the I{ns} is to be normalized.
+        @param ns: A namespace.
+        @type ns: (p,u)
+        @return: True if to be included.
+        @rtype: boolean
+        """
+        return not self.skip(ns)
+            
+    def skip(self, ns):
+        """
+        Get whether the I{ns} is to B{not} be normalized.
+        @param ns: A namespace.
+        @type ns: (p,u)
+        @return: True if to be skipped.
+        @rtype: boolean
+        """
+        return ns is None or \
+            ( ns == Namespace.default ) or \
+            ( ns == Namespace.xsdns ) or \
+            ( ns == Namespace.xsins) or \
+            ( ns == Namespace.xmlns )
\ No newline at end of file
diff --git a/suds/sax/enc.py b/suds/sax/enc.py
new file mode 100644
index 0000000..efc7274
--- /dev/null
+++ b/suds/sax/enc.py
@@ -0,0 +1,79 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides XML I{special character} encoder classes.
+"""
+
+import re
+
+class Encoder:
+    """
+    An XML special character encoder/decoder.
+    @cvar encodings: A mapping of special characters encoding.
+    @type encodings: [(str,str)]
+    @cvar decodings: A mapping of special characters decoding.
+    @type decodings: [(str,str)]
+    @cvar special: A list of special characters
+    @type special: [char]
+    """
+    
+    encodings = \
+        (( '&(?!(amp|lt|gt|quot|apos);)', '&' ),( '<', '<' ),( '>', '>' ),( '"', '"' ),("'", ''' ))
+    decodings = \
+        (( '<', '<' ),( '>', '>' ),( '"', '"' ),( ''', "'" ),( '&', '&' ))
+    special = \
+        ('&', '<', '>', '"', "'")
+    
+    def needsEncoding(self, s):
+        """
+        Get whether string I{s} contains special characters.
+        @param s: A string to check.
+        @type s: str
+        @return: True if needs encoding.
+        @rtype: boolean
+        """
+        if isinstance(s, basestring):
+            for c in self.special:
+                if c in s:
+                    return True
+        return False
+    
+    def encode(self, s):
+        """
+        Encode special characters found in string I{s}.
+        @param s: A string to encode.
+        @type s: str
+        @return: The encoded string.
+        @rtype: str
+        """
+        if isinstance(s, basestring) and self.needsEncoding(s):
+            for x in self.encodings:
+                s = re.sub(x[0], x[1], s)
+        return s
+    
+    def decode(self, s):
+        """
+        Decode special characters encodings found in string I{s}.
+        @param s: A string to decode.
+        @type s: str
+        @return: The decoded string.
+        @rtype: str
+        """
+        if isinstance(s, basestring) and '&' in s:
+            for x in self.decodings:
+                s = s.replace(x[0], x[1])
+        return s
diff --git a/suds/sax/parser.py b/suds/sax/parser.py
new file mode 100644
index 0000000..69f871b
--- /dev/null
+++ b/suds/sax/parser.py
@@ -0,0 +1,139 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The sax module contains a collection of classes that provide a
+(D)ocument (O)bject (M)odel representation of an XML document.
+The goal is to provide an easy, intuative interface for managing XML
+documents.  Although, the term, DOM, is used above, this model is
+B{far} better.
+
+XML namespaces in suds are represented using a (2) element tuple
+containing the prefix and the URI.  Eg: I{('tns', 'http://myns')}
+
+"""
+
+from logging import getLogger
+import suds.metrics
+from suds import *
+from suds.sax import *
+from suds.sax.document import Document
+from suds.sax.element import Element
+from suds.sax.text import Text
+from suds.sax.attribute import Attribute
+from xml.sax import make_parser, InputSource, ContentHandler
+from xml.sax.handler import feature_external_ges
+from cStringIO import StringIO
+
+log = getLogger(__name__)
+
+
+class Handler(ContentHandler):
+    """ sax hanlder """
+    
+    def __init__(self):
+        self.nodes = [Document()]
+ 
+    def startElement(self, name, attrs):
+        top = self.top()
+        node = Element(unicode(name), parent=top)
+        for a in attrs.getNames():
+            n = unicode(a)
+            v = unicode(attrs.getValue(a))
+            attribute = Attribute(n,v)
+            if self.mapPrefix(node, attribute):
+                continue
+            node.append(attribute)
+        node.charbuffer = []
+        top.append(node)
+        self.push(node)
+        
+    def mapPrefix(self, node, attribute):
+        skip = False
+        if attribute.name == 'xmlns':
+            if len(attribute.value):
+                node.expns = unicode(attribute.value)
+            skip = True
+        elif attribute.prefix == 'xmlns':
+            prefix = attribute.name
+            node.nsprefixes[prefix] = unicode(attribute.value)
+            skip = True
+        return skip
+ 
+    def endElement(self, name):
+        name = unicode(name)
+        current = self.top()
+        if len(current.charbuffer):
+            current.text = Text(u''.join(current.charbuffer))
+        del current.charbuffer
+        if len(current):
+            current.trim()
+        currentqname = current.qname()
+        if name == currentqname:
+            self.pop()
+        else:
+            raise Exception('malformed document')
+ 
+    def characters(self, content):
+        text = unicode(content)
+        node = self.top()
+        node.charbuffer.append(text)
+
+    def push(self, node):
+        self.nodes.append(node)
+        return node
+
+    def pop(self):
+        return self.nodes.pop()
+ 
+    def top(self):
+        return self.nodes[len(self.nodes)-1]
+
+
+class Parser:
+    """ SAX Parser """
+    
+    @classmethod
+    def saxparser(cls):
+        p = make_parser()
+        p.setFeature(feature_external_ges, 0)
+        h = Handler()
+        p.setContentHandler(h)
+        return (p, h)
+        
+    def parse(self, file=None, string=None):
+        """
+        SAX parse XML text.
+        @param file: Parse a python I{file-like} object.
+        @type file: I{file-like} object.
+        @param string: Parse string XML.
+        @type string: str
+        """
+        timer = metrics.Timer()
+        timer.start()
+        sax, handler = self.saxparser()
+        if file is not None:
+            sax.parse(file)
+            timer.stop()
+            metrics.log.debug('sax (%s) duration: %s', file, timer)
+            return handler.nodes[0]
+        if string is not None:
+            source = InputSource(None)
+            source.setByteStream(StringIO(string))
+            sax.parse(source)
+            timer.stop()
+            metrics.log.debug('%s\nsax duration: %s', string, timer)
+            return handler.nodes[0]
\ No newline at end of file
diff --git a/suds/sax/text.py b/suds/sax/text.py
new file mode 100644
index 0000000..0d58ee8
--- /dev/null
+++ b/suds/sax/text.py
@@ -0,0 +1,116 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains XML text classes.
+"""
+
+from suds import *
+from suds.sax import *
+
+
+class Text(unicode):
+    """
+    An XML text object used to represent text content.
+    @ivar lang: The (optional) language flag.
+    @type lang: bool
+    @ivar escaped: The (optional) XML special character escaped flag.
+    @type escaped: bool
+    """
+    __slots__ = ('lang', 'escaped',)
+    
+    @classmethod
+    def __valid(cls, *args):
+        return ( len(args) and args[0] is not None )
+    
+    def __new__(cls, *args, **kwargs):
+        if cls.__valid(*args):
+            lang = kwargs.pop('lang', None)
+            escaped = kwargs.pop('escaped', False)
+            result = super(Text, cls).__new__(cls, *args, **kwargs)
+            result.lang = lang
+            result.escaped = escaped
+        else:
+            result = None
+        return result
+    
+    def escape(self):
+        """
+        Encode (escape) special XML characters.
+        @return: The text with XML special characters escaped.
+        @rtype: L{Text}
+        """
+        if not self.escaped:
+            post = sax.encoder.encode(self)
+            escaped = ( post != self )
+            return Text(post, lang=self.lang, escaped=escaped)
+        return self
+    
+    def unescape(self):
+        """
+        Decode (unescape) special XML characters.
+        @return: The text with escaped XML special characters decoded.
+        @rtype: L{Text}
+        """
+        if self.escaped:
+            post = sax.encoder.decode(self)
+            return Text(post, lang=self.lang)
+        return self
+    
+    def trim(self):
+        post = self.strip()
+        return Text(post, lang=self.lang, escaped=self.escaped)
+    
+    def __add__(self, other):
+        joined = u''.join((self, other))
+        result = Text(joined, lang=self.lang, escaped=self.escaped)
+        if isinstance(other, Text):
+            result.escaped = ( self.escaped or other.escaped )
+        return result
+    
+    def __repr__(self):
+        s = [self]
+        if self.lang is not None:
+            s.append(' [%s]' % self.lang)
+        if self.escaped:
+            s.append(' <escaped>')
+        return ''.join(s)
+    
+    def __getstate__(self):
+        state = {}
+        for k in self.__slots__:
+            state[k] = getattr(self, k)
+        return state
+    
+    def __setstate__(self, state):
+        for k in self.__slots__:
+            setattr(self, k, state[k])
+    
+    
+class Raw(Text):
+    """
+    Raw text which is not XML escaped.
+    This may include I{string} XML.
+    """
+    def escape(self):
+        return self
+    
+    def unescape(self):
+        return self
+    
+    def __add__(self, other):
+        joined = u''.join((self, other))
+        return Raw(joined, lang=self.lang)
diff --git a/suds/servicedefinition.py b/suds/servicedefinition.py
new file mode 100644
index 0000000..81b5a0d
--- /dev/null
+++ b/suds/servicedefinition.py
@@ -0,0 +1,248 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{service definition} provides a textual representation of a service.
+"""
+
+from logging import getLogger
+from suds import *
+import suds.metrics as metrics
+from suds.sax import Namespace
+
+log = getLogger(__name__)
+
+class ServiceDefinition:
+    """
+    A service definition provides an object used to generate a textual description
+    of a service.
+    @ivar wsdl: A wsdl.
+    @type wsdl: L{wsdl.Definitions}
+    @ivar service: The service object.
+    @type service: L{suds.wsdl.Service}
+    @ivar ports: A list of port-tuple: (port, [(method-name, pdef)])
+    @type ports: [port-tuple,..]
+    @ivar prefixes: A list of remapped prefixes.
+    @type prefixes: [(prefix,uri),..]
+    @ivar types: A list of type definitions
+    @type types: [I{Type},..] 
+    """
+    
+    def __init__(self, wsdl, service):
+        """
+        @param wsdl: A wsdl object
+        @type wsdl: L{Definitions}
+        @param service: A service B{name}.
+        @type service: str
+        """
+        self.wsdl = wsdl
+        self.service = service
+        self.ports = []
+        self.params = []
+        self.types = []
+        self.prefixes = []
+        self.addports()
+        self.paramtypes()
+        self.publictypes()
+        self.getprefixes()
+        self.pushprefixes()
+    
+    def pushprefixes(self):
+        """
+        Add our prefixes to the wsdl so that when users invoke methods
+        and reference the prefixes, the will resolve properly.
+        """
+        for ns in self.prefixes:
+            self.wsdl.root.addPrefix(ns[0], ns[1])
+
+    def addports(self):
+        """
+        Look through the list of service ports and construct a list of tuples where
+        each tuple is used to describe a port and it's list of methods as:
+        (port, [method]).  Each method is tuple: (name, [pdef,..] where each pdef is
+        a tuple: (param-name, type).
+        """
+        timer = metrics.Timer()
+        timer.start()
+        for port in self.service.ports:
+            p = self.findport(port)
+            for op in port.binding.operations.values():
+                m = p[0].method(op.name)
+                binding = m.binding.input
+                method = (m.name, binding.param_defs(m))
+                p[1].append(method)
+                metrics.log.debug("method '%s' created: %s", m.name, timer)
+            p[1].sort()
+        timer.stop()
+            
+    def findport(self, port):
+        """
+        Find and return a port tuple for the specified port.
+        Created and added when not found.
+        @param port: A port.
+        @type port: I{service.Port}
+        @return: A port tuple.
+        @rtype: (port, [method])
+        """
+        for p in self.ports:
+            if p[0] == p: return p
+        p = (port, [])
+        self.ports.append(p)
+        return p
+            
+    def getprefixes(self):
+        """
+        Add prefixes foreach namespace referenced by parameter types.
+        """
+        namespaces = []
+        for l in (self.params, self.types):
+            for t,r in l:
+                ns = r.namespace()
+                if ns[1] is None: continue
+                if ns[1] in namespaces: continue
+                if Namespace.xs(ns) or Namespace.xsd(ns):
+                    continue
+                namespaces.append(ns[1])
+                if t == r: continue
+                ns = t.namespace()
+                if ns[1] is None: continue
+                if ns[1] in namespaces: continue
+                namespaces.append(ns[1])
+        i = 0
+        namespaces.sort()
+        for u in namespaces:
+            p = self.nextprefix()
+            ns = (p, u)
+            self.prefixes.append(ns)
+            
+    def paramtypes(self):
+        """ get all parameter types """
+        for m in [p[1] for p in self.ports]:
+            for p in [p[1] for p in m]:
+                for pd in p:
+                    if pd[1] in self.params: continue
+                    item = (pd[1], pd[1].resolve())
+                    self.params.append(item)
+                    
+    def publictypes(self):
+        """ get all public types """
+        for t in self.wsdl.schema.types.values():
+            if t in self.params: continue
+            if t in self.types: continue
+            item = (t, t)
+            self.types.append(item)
+        tc = lambda x,y: cmp(x[0].name, y[0].name)
+        self.types.sort(cmp=tc)
+        
+    def nextprefix(self):
+        """
+        Get the next available prefix.  This means a prefix starting with 'ns' with
+        a number appended as (ns0, ns1, ..) that is not already defined on the
+        wsdl document.
+        """
+        used = [ns[0] for ns in self.prefixes]
+        used += [ns[0] for ns in self.wsdl.root.nsprefixes.items()]
+        for n in range(0,1024):
+            p = 'ns%d'%n
+            if p not in used:
+                return p
+        raise Exception('prefixes exhausted')
+    
+    def getprefix(self, u):
+        """
+        Get the prefix for the specified namespace (uri)
+        @param u: A namespace uri.
+        @type u: str
+        @return: The namspace.
+        @rtype: (prefix, uri).
+        """
+        for ns in Namespace.all:
+            if u == ns[1]: return ns[0]
+        for ns in self.prefixes:
+            if u == ns[1]: return ns[0]
+        raise Exception('ns (%s) not mapped'  % u)
+    
+    def xlate(self, type):
+        """
+        Get a (namespace) translated I{qualified} name for specified type.
+        @param type: A schema type.
+        @type type: I{suds.xsd.sxbasic.SchemaObject}
+        @return: A translated I{qualified} name.
+        @rtype: str
+        """
+        resolved = type.resolve()
+        name = resolved.name
+        if type.unbounded():
+            name += '[]'
+        ns = resolved.namespace()
+        if ns[1] == self.wsdl.tns[1]:
+            return name
+        prefix = self.getprefix(ns[1])
+        return ':'.join((prefix, name))
+        
+    def description(self):
+        """
+        Get a textual description of the service for which this object represents.
+        @return: A textual description.
+        @rtype: str
+        """
+        s = []
+        indent = (lambda n :  '\n%*s'%(n*3,' '))
+        s.append('Service ( %s ) tns="%s"' % (self.service.name, self.wsdl.tns[1]))
+        s.append(indent(1))
+        s.append('Prefixes (%d)' % len(self.prefixes))
+        for p in self.prefixes:
+            s.append(indent(2))
+            s.append('%s = "%s"' % p)
+        s.append(indent(1))
+        s.append('Ports (%d):' % len(self.ports))
+        for p in self.ports:
+            s.append(indent(2))
+            s.append('(%s)' % p[0].name)
+            s.append(indent(3))
+            s.append('Methods (%d):' % len(p[1]))
+            for m in p[1]:
+                sig = []
+                s.append(indent(4))
+                sig.append(m[0])
+                sig.append('(')
+                for p in m[1]:
+                    sig.append(self.xlate(p[1]))
+                    sig.append(' ')
+                    sig.append(p[0])
+                    sig.append(', ')
+                sig.append(')')
+                try:
+                    s.append(''.join(sig))
+                except:
+                    pass
+            s.append(indent(3))
+            s.append('Types (%d):' % len(self.types))
+            for t in self.types:
+                s.append(indent(4))
+                s.append(self.xlate(t[0]))
+        s.append('\n\n')
+        return ''.join(s)
+    
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+        
+    def __unicode__(self):
+        try:
+            return self.description()
+        except Exception, e:
+            log.exception(e)
+        return tostr(e)
\ No newline at end of file
diff --git a/suds/serviceproxy.py b/suds/serviceproxy.py
new file mode 100644
index 0000000..6e71050
--- /dev/null
+++ b/suds/serviceproxy.py
@@ -0,0 +1,86 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The service proxy provides access to web services.
+
+Replaced by: L{client.Client}
+"""
+
+from logging import getLogger
+from suds import *
+from suds.client import Client
+
+log = getLogger(__name__)
+
+
+class ServiceProxy(object):
+    
+    """ 
+    A lightweight soap based web service proxy.
+    @ivar __client__: A client.  
+        Everything is delegated to the 2nd generation API.
+    @type __client__: L{Client}
+    @note:  Deprecated, replaced by L{Client}.
+    """
+
+    def __init__(self, url, **kwargs):
+        """
+        @param url: The URL for the WSDL.
+        @type url: str
+        @param kwargs: keyword arguments.
+        @keyword faults: Raise faults raised by server (default:True),
+                else return tuple from service method invocation as (http code, object).
+        @type faults: boolean
+        @keyword proxy: An http proxy to be specified on requests (default:{}).
+                           The proxy is defined as {protocol:proxy,}
+        @type proxy: dict
+        """
+        client = Client(url, **kwargs)
+        self.__client__ = client
+    
+    def get_instance(self, name):
+        """
+        Get an instance of a WSDL type by name
+        @param name: The name of a type defined in the WSDL.
+        @type name: str
+        @return: An instance on success, else None
+        @rtype: L{sudsobject.Object}
+        """
+        return self.__client__.factory.create(name)
+    
+    def get_enum(self, name):
+        """
+        Get an instance of an enumeration defined in the WSDL by name.
+        @param name: The name of a enumeration defined in the WSDL.
+        @type name: str
+        @return: An instance on success, else None
+        @rtype: L{sudsobject.Object}
+        """
+        return self.__client__.factory.create(name)
+ 
+    def __str__(self):
+        return str(self.__client__)
+        
+    def __unicode__(self):
+        return unicode(self.__client__)
+    
+    def __getattr__(self, name):
+        builtin =  name.startswith('__') and name.endswith('__')
+        if builtin:
+            return self.__dict__[name]
+        else:
+            return getattr(self.__client__.service, name)
\ No newline at end of file
diff --git a/suds/soaparray.py b/suds/soaparray.py
new file mode 100644
index 0000000..c7b85e5
--- /dev/null
+++ b/suds/soaparray.py
@@ -0,0 +1,69 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{soaparray} module provides XSD extensions for handling
+soap (section 5) encoded arrays.
+"""
+
+from suds import *
+from logging import getLogger
+from suds.xsd.sxbasic import Factory as SXFactory
+from suds.xsd.sxbasic import Attribute as SXAttribute
+
+
+class Attribute(SXAttribute):
+    """
+    Represents an XSD <attribute/> that handles special
+    attributes that are extensions for WSDLs.
+    @ivar aty: Array type information.
+    @type aty: The value of wsdl:arrayType.
+    """
+
+    def __init__(self, schema, root, aty):
+        """
+        @param aty: Array type information.
+        @type aty: The value of wsdl:arrayType.
+        """
+        SXAttribute.__init__(self, schema, root)
+        self.aty = aty[:-2]
+        
+    def autoqualified(self):
+        aqs = SXAttribute.autoqualified(self)
+        aqs.append('aty')
+        return aqs
+    
+    def description(self):
+        d = SXAttribute.description(self)
+        d = d+('aty',)
+        return d
+
+#
+# Builder function, only builds Attribute when arrayType
+# attribute is defined on root.
+#
+def __fn(x, y):
+    ns = (None, "http://schemas.xmlsoap.org/wsdl/")
+    aty = y.get('arrayType', ns=ns)
+    if aty is None:
+        return SXAttribute(x, y)
+    else:
+        return Attribute(x, y, aty)
+
+#
+# Remap <xs:attrbute/> tags to __fn() builder.
+#
+SXFactory.maptag('attribute', __fn)
\ No newline at end of file
diff --git a/suds/store.py b/suds/store.py
new file mode 100644
index 0000000..85e0943
--- /dev/null
+++ b/suds/store.py
@@ -0,0 +1,594 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains XML text for documents to be distributed
+with the suds lib.  Also, contains classes for accessing
+these documents.
+"""
+
+from StringIO import StringIO
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+#
+# Soap section 5 encoding schema.
+#
+encoding = \
+"""<?xml version="1.0" encoding="UTF-8"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="http://schemas.xmlsoap.org/soap/encoding/" targetNamespace="http://schemas.xmlsoap.org/soap/encoding/">
+        
+ <xs:attribute name="root">
+   <xs:annotation>
+     <xs:documentation>
+       'root' can be used to distinguish serialization roots from other
+       elements that are present in a serialization but are not roots of
+       a serialized value graph 
+     </xs:documentation>
+   </xs:annotation>
+   <xs:simpleType>
+     <xs:restriction base="xs:boolean">
+       <xs:pattern value="0|1"/>
+     </xs:restriction>
+   </xs:simpleType>
+ </xs:attribute>
+
+  <xs:attributeGroup name="commonAttributes">
+    <xs:annotation>
+      <xs:documentation>
+        Attributes common to all elements that function as accessors or 
+        represent independent (multi-ref) values.  The href attribute is
+        intended to be used in a manner like CONREF.  That is, the element
+        content should be empty iff the href attribute appears
+      </xs:documentation>
+    </xs:annotation>
+    <xs:attribute name="id" type="xs:ID"/>
+    <xs:attribute name="href" type="xs:anyURI"/>
+    <xs:anyAttribute namespace="##other" processContents="lax"/>
+  </xs:attributeGroup>
+
+  <!-- Global Attributes.  The following attributes are intended to be usable via qualified attribute names on any complex type referencing them. -->
+       
+  <!-- Array attributes. Needed to give the type and dimensions of an array's contents, and the offset for partially-transmitted arrays. -->
+   
+  <xs:simpleType name="arrayCoordinate">
+    <xs:restriction base="xs:string"/>
+  </xs:simpleType>
+          
+  <xs:attribute name="arrayType" type="xs:string"/>
+  <xs:attribute name="offset" type="tns:arrayCoordinate"/>
+  
+  <xs:attributeGroup name="arrayAttributes">
+    <xs:attribute ref="tns:arrayType"/>
+    <xs:attribute ref="tns:offset"/>
+  </xs:attributeGroup>    
+  
+  <xs:attribute name="position" type="tns:arrayCoordinate"/> 
+  
+  <xs:attributeGroup name="arrayMemberAttributes">
+    <xs:attribute ref="tns:position"/>
+  </xs:attributeGroup>    
+
+  <xs:group name="Array">
+    <xs:sequence>
+      <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+    </xs:sequence>
+  </xs:group>
+
+  <xs:element name="Array" type="tns:Array"/>
+  <xs:complexType name="Array">
+    <xs:annotation>
+      <xs:documentation>
+       'Array' is a complex type for accessors identified by position 
+      </xs:documentation>
+    </xs:annotation>
+    <xs:group ref="tns:Array" minOccurs="0"/>
+    <xs:attributeGroup ref="tns:arrayAttributes"/>
+    <xs:attributeGroup ref="tns:commonAttributes"/>
+  </xs:complexType> 
+
+  <!-- 'Struct' is a complex type for accessors identified by name. 
+       Constraint: No element may be have the same name as any other,
+       nor may any element have a maxOccurs > 1. -->
+   
+  <xs:element name="Struct" type="tns:Struct"/>
+
+  <xs:group name="Struct">
+    <xs:sequence>
+      <xs:any namespace="##any" minOccurs="0" maxOccurs="unbounded" processContents="lax"/>
+    </xs:sequence>
+  </xs:group>
+
+  <xs:complexType name="Struct">
+    <xs:group ref="tns:Struct" minOccurs="0"/>
+    <xs:attributeGroup ref="tns:commonAttributes"/>
+  </xs:complexType> 
+
+  <!-- 'Base64' can be used to serialize binary data using base64 encoding
+       as defined in RFC2045 but without the MIME line length limitation. -->
+
+  <xs:simpleType name="base64">
+    <xs:restriction base="xs:base64Binary"/>
+  </xs:simpleType>
+
+ <!-- Element declarations corresponding to each of the simple types in the 
+      XML Schemas Specification. -->
+
+  <xs:element name="duration" type="tns:duration"/>
+  <xs:complexType name="duration">
+    <xs:simpleContent>
+      <xs:extension base="xs:duration">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="dateTime" type="tns:dateTime"/>
+  <xs:complexType name="dateTime">
+    <xs:simpleContent>
+      <xs:extension base="xs:dateTime">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+
+
+  <xs:element name="NOTATION" type="tns:NOTATION"/>
+  <xs:complexType name="NOTATION">
+    <xs:simpleContent>
+      <xs:extension base="xs:QName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  
+
+  <xs:element name="time" type="tns:time"/>
+  <xs:complexType name="time">
+    <xs:simpleContent>
+      <xs:extension base="xs:time">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="date" type="tns:date"/>
+  <xs:complexType name="date">
+    <xs:simpleContent>
+      <xs:extension base="xs:date">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gYearMonth" type="tns:gYearMonth"/>
+  <xs:complexType name="gYearMonth">
+    <xs:simpleContent>
+      <xs:extension base="xs:gYearMonth">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gYear" type="tns:gYear"/>
+  <xs:complexType name="gYear">
+    <xs:simpleContent>
+      <xs:extension base="xs:gYear">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gMonthDay" type="tns:gMonthDay"/>
+  <xs:complexType name="gMonthDay">
+    <xs:simpleContent>
+      <xs:extension base="xs:gMonthDay">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gDay" type="tns:gDay"/>
+  <xs:complexType name="gDay">
+    <xs:simpleContent>
+      <xs:extension base="xs:gDay">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="gMonth" type="tns:gMonth"/>
+  <xs:complexType name="gMonth">
+    <xs:simpleContent>
+      <xs:extension base="xs:gMonth">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+  
+  <xs:element name="boolean" type="tns:boolean"/>
+  <xs:complexType name="boolean">
+    <xs:simpleContent>
+      <xs:extension base="xs:boolean">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="base64Binary" type="tns:base64Binary"/>
+  <xs:complexType name="base64Binary">
+    <xs:simpleContent>
+      <xs:extension base="xs:base64Binary">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="hexBinary" type="tns:hexBinary"/>
+  <xs:complexType name="hexBinary">
+    <xs:simpleContent>
+     <xs:extension base="xs:hexBinary">
+       <xs:attributeGroup ref="tns:commonAttributes"/>
+     </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="float" type="tns:float"/>
+  <xs:complexType name="float">
+    <xs:simpleContent>
+      <xs:extension base="xs:float">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="double" type="tns:double"/>
+  <xs:complexType name="double">
+    <xs:simpleContent>
+      <xs:extension base="xs:double">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="anyURI" type="tns:anyURI"/>
+  <xs:complexType name="anyURI">
+    <xs:simpleContent>
+      <xs:extension base="xs:anyURI">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="QName" type="tns:QName"/>
+  <xs:complexType name="QName">
+    <xs:simpleContent>
+      <xs:extension base="xs:QName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  
+  <xs:element name="string" type="tns:string"/>
+  <xs:complexType name="string">
+    <xs:simpleContent>
+      <xs:extension base="xs:string">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="normalizedString" type="tns:normalizedString"/>
+  <xs:complexType name="normalizedString">
+    <xs:simpleContent>
+      <xs:extension base="xs:normalizedString">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="token" type="tns:token"/>
+  <xs:complexType name="token">
+    <xs:simpleContent>
+      <xs:extension base="xs:token">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="language" type="tns:language"/>
+  <xs:complexType name="language">
+    <xs:simpleContent>
+      <xs:extension base="xs:language">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="Name" type="tns:Name"/>
+  <xs:complexType name="Name">
+    <xs:simpleContent>
+      <xs:extension base="xs:Name">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NMTOKEN" type="tns:NMTOKEN"/>
+  <xs:complexType name="NMTOKEN">
+    <xs:simpleContent>
+      <xs:extension base="xs:NMTOKEN">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NCName" type="tns:NCName"/>
+  <xs:complexType name="NCName">
+    <xs:simpleContent>
+      <xs:extension base="xs:NCName">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="NMTOKENS" type="tns:NMTOKENS"/>
+  <xs:complexType name="NMTOKENS">
+    <xs:simpleContent>
+      <xs:extension base="xs:NMTOKENS">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ID" type="tns:ID"/>
+  <xs:complexType name="ID">
+    <xs:simpleContent>
+      <xs:extension base="xs:ID">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="IDREF" type="tns:IDREF"/>
+  <xs:complexType name="IDREF">
+    <xs:simpleContent>
+      <xs:extension base="xs:IDREF">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ENTITY" type="tns:ENTITY"/>
+  <xs:complexType name="ENTITY">
+    <xs:simpleContent>
+      <xs:extension base="xs:ENTITY">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="IDREFS" type="tns:IDREFS"/>
+  <xs:complexType name="IDREFS">
+    <xs:simpleContent>
+      <xs:extension base="xs:IDREFS">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="ENTITIES" type="tns:ENTITIES"/>
+  <xs:complexType name="ENTITIES">
+    <xs:simpleContent>
+      <xs:extension base="xs:ENTITIES">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="decimal" type="tns:decimal"/>
+  <xs:complexType name="decimal">
+    <xs:simpleContent>
+      <xs:extension base="xs:decimal">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="integer" type="tns:integer"/>
+  <xs:complexType name="integer">
+    <xs:simpleContent>
+      <xs:extension base="xs:integer">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="nonPositiveInteger" type="tns:nonPositiveInteger"/>
+  <xs:complexType name="nonPositiveInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:nonPositiveInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="negativeInteger" type="tns:negativeInteger"/>
+  <xs:complexType name="negativeInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:negativeInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="long" type="tns:long"/>
+  <xs:complexType name="long">
+    <xs:simpleContent>
+      <xs:extension base="xs:long">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="int" type="tns:int"/>
+  <xs:complexType name="int">
+    <xs:simpleContent>
+      <xs:extension base="xs:int">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="short" type="tns:short"/>
+  <xs:complexType name="short">
+    <xs:simpleContent>
+      <xs:extension base="xs:short">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="byte" type="tns:byte"/>
+  <xs:complexType name="byte">
+    <xs:simpleContent>
+      <xs:extension base="xs:byte">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="nonNegativeInteger" type="tns:nonNegativeInteger"/>
+  <xs:complexType name="nonNegativeInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:nonNegativeInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedLong" type="tns:unsignedLong"/>
+  <xs:complexType name="unsignedLong">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedLong">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedInt" type="tns:unsignedInt"/>
+  <xs:complexType name="unsignedInt">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedInt">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedShort" type="tns:unsignedShort"/>
+  <xs:complexType name="unsignedShort">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedShort">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="unsignedByte" type="tns:unsignedByte"/>
+  <xs:complexType name="unsignedByte">
+    <xs:simpleContent>
+      <xs:extension base="xs:unsignedByte">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="positiveInteger" type="tns:positiveInteger"/>
+  <xs:complexType name="positiveInteger">
+    <xs:simpleContent>
+      <xs:extension base="xs:positiveInteger">
+        <xs:attributeGroup ref="tns:commonAttributes"/>
+      </xs:extension>
+    </xs:simpleContent>
+  </xs:complexType>
+
+  <xs:element name="anyType"/>
+</xs:schema>
+"""
+
+
+class DocumentStore:
+    """
+    The I{suds} document store provides a local repository
+    for xml documnts.
+    @cvar protocol: The URL protocol for the store.
+    @type protocol: str
+    @cvar store: The mapping of URL location to documents.
+    @type store: dict
+    """
+    
+    protocol = 'suds'
+    
+    store = {
+        'schemas.xmlsoap.org/soap/encoding/' : encoding
+    }
+    
+    def open(self, url):
+        """
+        Open a document at the specified url.
+        @param url: A document URL.
+        @type url: str
+        @return: A file pointer to the document.
+        @rtype: StringIO
+        """
+        protocol, location = self.split(url)
+        if protocol == self.protocol:
+            return self.find(location)
+        else:
+            return None
+        
+    def find(self, location):
+        """
+        Find the specified location in the store.
+        @param location: The I{location} part of a URL.
+        @type location: str
+        @return: An input stream to the document.
+        @rtype: StringIO
+        """
+        try:
+            content = self.store[location]
+            return StringIO(content)
+        except:
+            reason = 'location "%s" not in document store' % location
+            raise Exception, reason
+        
+    def split(self, url):
+        """
+        Split the url into I{protocol} and I{location}
+        @param url: A URL.
+        @param url: str
+        @return: (I{url}, I{location})
+        @rtype: tuple
+        """
+        parts = url.split('://', 1)
+        if len(parts) == 2:
+            return parts
+        else:
+            return (None, url)
\ No newline at end of file
diff --git a/suds/sudsobject.py b/suds/sudsobject.py
new file mode 100644
index 0000000..1f6168d
--- /dev/null
+++ b/suds/sudsobject.py
@@ -0,0 +1,390 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{sudsobject} module provides a collection of suds objects
+that are primarily used for the highly dynamic interactions with
+wsdl/xsd defined types.
+"""
+
+from logging import getLogger
+from suds import *
+from new import classobj
+
+log = getLogger(__name__)
+
+
+def items(sobject):
+    """
+    Extract the I{items} from a suds object much like the
+    items() method works on I{dict}.
+    @param sobject: A suds object
+    @type sobject: L{Object}
+    @return: A list of items contained in I{sobject}.
+    @rtype: [(key, value),...]
+    """
+    for item in sobject:
+        yield item
+
+
+def asdict(sobject):
+    """
+    Convert a sudsobject into a dictionary.
+    @param sobject: A suds object
+    @type sobject: L{Object}
+    @return: A python dictionary containing the
+        items contained in I{sobject}.
+    @rtype: dict
+    """
+    return dict(items(sobject))
+
+def merge(a, b):
+    """
+    Merge all attributes and metadata from I{a} to I{b}.
+    @param a: A I{source} object
+    @type a: L{Object}
+    @param b: A I{destination} object
+    @type b: L{Object}
+    """
+    for item in a:
+        setattr(b, item[0], item[1])
+        b.__metadata__ = b.__metadata__
+    return b
+
+def footprint(sobject):
+    """
+    Get the I{virtual footprint} of the object.
+    This is really a count of the attributes in the branch with a significant value.
+    @param sobject: A suds object.
+    @type sobject: L{Object}
+    @return: The branch footprint.
+    @rtype: int
+    """
+    n = 0
+    for a in sobject.__keylist__:
+        v = getattr(sobject, a)
+        if v is None: continue
+        if isinstance(v, Object):
+            n += footprint(v)
+            continue
+        if hasattr(v, '__len__'):
+            if len(v): n += 1
+            continue
+        n +=1
+    return n
+
+    
+class Factory:
+    
+    cache = {}
+    
+    @classmethod
+    def subclass(cls, name, bases, dict={}):
+        if not isinstance(bases, tuple):
+            bases = (bases,)
+        name = name.encode('utf-8')
+        key = '.'.join((name, str(bases)))
+        subclass = cls.cache.get(key)
+        if subclass is None:
+            subclass = classobj(name, bases, dict)
+            cls.cache[key] = subclass
+        return subclass
+    
+    @classmethod
+    def object(cls, classname=None, dict={}):
+        if classname is not None:
+            subclass = cls.subclass(classname, Object)
+            inst = subclass()
+        else:
+            inst = Object()
+        for a in dict.items():
+            setattr(inst, a[0], a[1])
+        return inst
+    
+    @classmethod
+    def metadata(cls):
+        return Metadata()
+    
+    @classmethod
+    def property(cls, name, value=None):
+        subclass = cls.subclass(name, Property)
+        return subclass(value)
+
+
+class Object:
+
+    def __init__(self):
+        self.__keylist__ = []
+        self.__printer__ = Printer()
+        self.__metadata__ = Metadata()
+
+    def __setattr__(self, name, value):
+        builtin =  name.startswith('__') and name.endswith('__')
+        if not builtin and \
+            name not in self.__keylist__:
+            self.__keylist__.append(name)
+        self.__dict__[name] = value
+        
+    def __delattr__(self, name):
+        try:
+            del self.__dict__[name]
+            builtin =  name.startswith('__') and name.endswith('__')
+            if not builtin:
+                self.__keylist__.remove(name)
+        except:
+            cls = self.__class__.__name__
+            raise AttributeError, "%s has no attribute '%s'" % (cls, name)
+
+    def __getitem__(self, name):
+        if isinstance(name, int):
+            name = self.__keylist__[int(name)]
+        return getattr(self, name)
+    
+    def __setitem__(self, name, value):
+        setattr(self, name, value)
+        
+    def __iter__(self):
+        return Iter(self)
+
+    def __len__(self):
+        return len(self.__keylist__)
+    
+    def __contains__(self, name):
+        return name in self.__keylist__
+    
+    def __repr__(self):
+        return str(self)
+
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        return self.__printer__.tostr(self)
+
+
+class Iter:
+
+    def __init__(self, sobject):
+        self.sobject = sobject
+        self.keylist = self.__keylist(sobject)
+        self.index = 0
+
+    def next(self):
+        keylist = self.keylist
+        nkeys = len(self.keylist)
+        while self.index < nkeys:
+            k = keylist[self.index]
+            self.index += 1
+            if hasattr(self.sobject, k):
+                v = getattr(self.sobject, k)
+                return (k, v)
+        raise StopIteration()
+    
+    def __keylist(self, sobject):
+        keylist = sobject.__keylist__
+        try:
+            keyset = set(keylist)
+            ordering = sobject.__metadata__.ordering
+            ordered = set(ordering)
+            if not ordered.issuperset(keyset):
+                log.debug(
+                    '%s must be superset of %s, ordering ignored',
+                    keylist, 
+                    ordering)
+                raise KeyError()
+            return ordering
+        except:
+            return keylist
+        
+    def __iter__(self):
+        return self
+
+
+class Metadata(Object):
+    def __init__(self):
+        self.__keylist__ = []
+        self.__printer__ = Printer()
+
+
+class Facade(Object):
+    def __init__(self, name):
+        Object.__init__(self)
+        md = self.__metadata__
+        md.facade = name
+
+       
+class Property(Object):
+
+    def __init__(self, value):
+        Object.__init__(self)
+        self.value = value
+        
+    def items(self):
+        for item in self:
+            if item[0] != 'value':
+                yield item
+        
+    def get(self):
+        return self.value
+    
+    def set(self, value):
+        self.value = value
+        return self
+
+
+class Printer:
+    """ 
+    Pretty printing of a Object object.
+    """
+    
+    @classmethod
+    def indent(cls, n): return '%*s'%(n*3,' ')
+
+    def tostr(self, object, indent=-2):
+        """ get s string representation of object """
+        history = []
+        return self.process(object, history, indent)
+    
+    def process(self, object, h, n=0, nl=False):
+        """ print object using the specified indent (n) and newline (nl). """
+        if object is None:
+            return 'None'
+        if isinstance(object, Object):
+            if len(object) == 0:
+                return '<empty>'
+            else:
+                return self.print_object(object, h, n+2, nl)
+        if isinstance(object, dict):
+            if len(object) == 0:
+                return '<empty>'
+            else:
+                return self.print_dictionary(object, h, n+2, nl)
+        if isinstance(object, (list,tuple)):
+            if len(object) == 0:
+                return '<empty>'
+            else:
+                return self.print_collection(object, h, n+2)
+        if isinstance(object, basestring):
+            return '"%s"' % tostr(object)
+        return '%s' % tostr(object)
+    
+    def print_object(self, d, h, n, nl=False):
+        """ print complex using the specified indent (n) and newline (nl). """
+        s = []
+        cls = d.__class__
+        md = d.__metadata__
+        if d in h:
+            s.append('(')
+            s.append(cls.__name__)
+            s.append(')')
+            s.append('...')
+            return ''.join(s)
+        h.append(d)
+        if nl:
+            s.append('\n')
+            s.append(self.indent(n))
+        if cls != Object:
+            s.append('(')
+            if isinstance(d, Facade):
+                s.append(md.facade)
+            else:
+                s.append(cls.__name__)
+            s.append(')')
+        s.append('{')
+        for item in d:
+            if self.exclude(d, item):
+                continue
+            item = self.unwrap(d, item)
+            s.append('\n')
+            s.append(self.indent(n+1))
+            if isinstance(item[1], (list,tuple)):            
+                s.append(item[0])
+                s.append('[]')
+            else:
+                s.append(item[0])
+            s.append(' = ')
+            s.append(self.process(item[1], h, n, True))
+        s.append('\n')
+        s.append(self.indent(n))
+        s.append('}')
+        h.pop()
+        return ''.join(s)
+    
+    def print_dictionary(self, d, h, n, nl=False):
+        """ print complex using the specified indent (n) and newline (nl). """
+        if d in h: return '{}...'
+        h.append(d)
+        s = []
+        if nl:
+            s.append('\n')
+            s.append(self.indent(n))
+        s.append('{')
+        for item in d.items():
+            s.append('\n')
+            s.append(self.indent(n+1))
+            if isinstance(item[1], (list,tuple)):            
+                s.append(tostr(item[0]))
+                s.append('[]')
+            else:
+                s.append(tostr(item[0]))
+            s.append(' = ')
+            s.append(self.process(item[1], h, n, True))
+        s.append('\n')
+        s.append(self.indent(n))
+        s.append('}')
+        h.pop()
+        return ''.join(s)
+
+    def print_collection(self, c, h, n):
+        """ print collection using the specified indent (n) and newline (nl). """
+        if c in h: return '[]...'
+        h.append(c)
+        s = []
+        for item in c:
+            s.append('\n')
+            s.append(self.indent(n))
+            s.append(self.process(item, h, n-2))
+            s.append(',')
+        h.pop()
+        return ''.join(s)
+    
+    def unwrap(self, d, item):
+        """ translate (unwrap) using an optional wrapper function """
+        nopt = ( lambda x: x )
+        try:
+            md = d.__metadata__
+            pmd = getattr(md, '__print__', None)
+            if pmd is None:
+                return item
+            wrappers = getattr(pmd, 'wrappers', {})
+            fn = wrappers.get(item[0], nopt)
+            return (item[0], fn(item[1]))
+        except:
+            pass
+        return item
+    
+    def exclude(self, d, item):
+        """ check metadata for excluded items """
+        try:
+            md = d.__metadata__
+            pmd = getattr(md, '__print__', None)
+            if pmd is None:
+                return False
+            excludes = getattr(pmd, 'excludes', [])
+            return ( item[0] in excludes ) 
+        except:
+            pass
+        return False
\ No newline at end of file
diff --git a/suds/transport/__init__.py b/suds/transport/__init__.py
new file mode 100644
index 0000000..e1e00d7
--- /dev/null
+++ b/suds/transport/__init__.py
@@ -0,0 +1,130 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains transport interface (classes).
+"""
+
+
+class TransportError(Exception):
+    def __init__(self, reason, httpcode, fp=None):
+        Exception.__init__(self, reason)
+        self.httpcode = httpcode
+        self.fp = fp
+
+class Request:
+    """
+    A transport request
+    @ivar url: The url for the request.
+    @type url: str
+    @ivar message: The message to be sent in a POST request.
+    @type message: str
+    @ivar headers: The http headers to be used for the request.
+    @type headers: dict
+    """
+
+    def __init__(self, url, message=None):
+        """
+        @param url: The url for the request.
+        @type url: str
+        @param message: The (optional) message to be send in the request.
+        @type message: str
+        """
+        self.url = url
+        self.headers = {}
+        self.message = message
+        
+    def __str__(self):
+        s = []
+        s.append('URL:%s' % self.url)
+        s.append('HEADERS: %s' % self.headers)
+        s.append('MESSAGE:')
+        s.append(self.message)
+        return '\n'.join(s)
+
+
+class Reply:
+    """
+    A transport reply
+    @ivar code: The http code returned.
+    @type code: int
+    @ivar message: The message to be sent in a POST request.
+    @type message: str
+    @ivar headers: The http headers to be used for the request.
+    @type headers: dict
+    """
+
+    def __init__(self, code, headers, message):
+        """
+        @param code: The http code returned.
+        @type code: int
+        @param headers: The http returned headers.
+        @type headers: dict
+        @param message: The (optional) reply message received.
+        @type message: str
+        """
+        self.code = code
+        self.headers = headers
+        self.message = message
+        
+    def __str__(self):
+        s = []
+        s.append('CODE: %s' % self.code)
+        s.append('HEADERS: %s' % self.headers)
+        s.append('MESSAGE:')
+        s.append(self.message)
+        return '\n'.join(s)
+
+
+class Transport:
+    """
+    The transport I{interface}.
+    """
+    
+    def __init__(self):
+        """
+        Constructor.
+        """
+        from suds.transport.options import Options
+        self.options = Options()
+        del Options
+    
+    def open(self, request):
+        """
+        Open the url in the specified request.
+        @param request: A transport request.
+        @type request: L{Request}
+        @return: An input stream.
+        @rtype: stream
+        @raise TransportError: On all transport errors.
+        """
+        raise Exception('not-implemented')
+    
+    def send(self, request):
+        """
+        Send soap message.  Implementations are expected to handle:
+            - proxies
+            - I{http} headers
+            - cookies
+            - sending message
+            - brokering exceptions into L{TransportError}
+        @param request: A transport request.
+        @type request: L{Request}
+        @return: The reply
+        @rtype: L{Reply}
+        @raise TransportError: On all transport errors.
+        """
+        raise Exception('not-implemented')
diff --git a/suds/transport/http.py b/suds/transport/http.py
new file mode 100644
index 0000000..6d85b09
--- /dev/null
+++ b/suds/transport/http.py
@@ -0,0 +1,187 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains classes for basic HTTP transport implementations.
+"""
+
+import urllib2 as u2
+import base64
+import socket
+from suds.transport import *
+from suds.properties import Unskin
+from urlparse import urlparse
+from cookielib import CookieJar
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+class HttpTransport(Transport):
+    """
+    HTTP transport using urllib2.  Provided basic http transport
+    that provides for cookies, proxies but no authentication.
+    """
+    
+    def __init__(self, **kwargs):
+        """
+        @param kwargs: Keyword arguments.
+            - B{proxy} - An http proxy to be specified on requests.
+                 The proxy is defined as {protocol:proxy,}
+                    - type: I{dict}
+                    - default: {}
+            - B{timeout} - Set the url open timeout (seconds).
+                    - type: I{float}
+                    - default: 90
+        """
+        Transport.__init__(self)
+        Unskin(self.options).update(kwargs)
+        self.cookiejar = CookieJar()
+        self.proxy = {}
+        self.urlopener = None
+        
+    def open(self, request):
+        try:
+            url = request.url
+            log.debug('opening (%s)', url)
+            u2request = u2.Request(url)
+            self.proxy = self.options.proxy
+            return self.u2open(u2request)
+        except u2.HTTPError, e:
+            raise TransportError(str(e), e.code, e.fp)
+
+    def send(self, request):
+        result = None
+        url = request.url
+        msg = request.message
+        headers = request.headers
+        try:
+            u2request = u2.Request(url, msg, headers)
+            self.addcookies(u2request)
+            self.proxy = self.options.proxy
+            request.headers.update(u2request.headers)
+            log.debug('sending:\n%s', request)
+            fp = self.u2open(u2request)
+            self.getcookies(fp, u2request)
+            result = Reply(200, fp.headers.dict, fp.read())
+            log.debug('received:\n%s', result)
+        except u2.HTTPError, e:
+            if e.code in (202,204):
+                result = None
+            else:
+                raise TransportError(e.msg, e.code, e.fp)
+        return result
+
+    def addcookies(self, u2request):
+        """
+        Add cookies in the cookiejar to the request.
+        @param u2request: A urllib2 request.
+        @rtype: u2request: urllib2.Requet.
+        """
+        self.cookiejar.add_cookie_header(u2request)
+        
+    def getcookies(self, fp, u2request):
+        """
+        Add cookies in the request to the cookiejar.
+        @param u2request: A urllib2 request.
+        @rtype: u2request: urllib2.Requet.
+        """
+        self.cookiejar.extract_cookies(fp, u2request)
+        
+    def u2open(self, u2request):
+        """
+        Open a connection.
+        @param u2request: A urllib2 request.
+        @type u2request: urllib2.Requet.
+        @return: The opened file-like urllib2 object.
+        @rtype: fp
+        """
+        tm = self.options.timeout
+        url = self.u2opener()
+        if self.u2ver() < 2.6:
+            socket.setdefaulttimeout(tm)
+            return url.open(u2request)
+        else:
+            return url.open(u2request, timeout=tm)
+            
+    def u2opener(self):
+        """
+        Create a urllib opener.
+        @return: An opener.
+        @rtype: I{OpenerDirector}
+        """
+        if self.urlopener is None:
+            return u2.build_opener(*self.u2handlers())
+        else:
+            return self.urlopener
+        
+    def u2handlers(self):
+        """
+        Get a collection of urllib handlers.
+        @return: A list of handlers to be installed in the opener.
+        @rtype: [Handler,...]
+        """
+        handlers = []
+        handlers.append(u2.ProxyHandler(self.proxy))
+        return handlers
+            
+    def u2ver(self):
+        """
+        Get the major/minor version of the urllib2 lib.
+        @return: The urllib2 version.
+        @rtype: float
+        """
+        try:
+            part = u2.__version__.split('.', 1)
+            n = float('.'.join(part))
+            return n
+        except Exception, e:
+            log.exception(e)
+            return 0
+        
+    def __deepcopy__(self, memo={}):
+        clone = self.__class__()
+        p = Unskin(self.options)
+        cp = Unskin(clone.options)
+        cp.update(p)
+        return clone
+
+
+class HttpAuthenticated(HttpTransport):
+    """
+    Provides basic http authentication for servers that don't follow
+    the specified challenge / response model.  This implementation
+    appends the I{Authorization} http header with base64 encoded
+    credentials on every http request.
+    """
+    
+    def open(self, request):
+        self.addcredentials(request)
+        return HttpTransport.open(self, request)
+    
+    def send(self, request):
+        self.addcredentials(request)
+        return HttpTransport.send(self, request)
+    
+    def addcredentials(self, request):
+        credentials = self.credentials()
+        if not (None in credentials):
+            encoded = base64.encodestring(':'.join(credentials))
+            basic = 'Basic %s' % encoded[:-1]
+            request.headers['Authorization'] = basic
+                 
+    def credentials(self):
+        return (self.options.username, self.options.password)
\ No newline at end of file
diff --git a/suds/transport/https.py b/suds/transport/https.py
new file mode 100644
index 0000000..ed23fd5
--- /dev/null
+++ b/suds/transport/https.py
@@ -0,0 +1,98 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains classes for basic HTTP (authenticated) transport implementations.
+"""
+
+import urllib2 as u2
+from suds.transport import *
+from suds.transport.http import HttpTransport
+from logging import getLogger
+
+log = getLogger(__name__)
+
+
+class HttpAuthenticated(HttpTransport):
+    """
+    Provides basic http authentication that follows the RFC-2617 specification.
+    As defined by specifications, credentials are provided to the server
+    upon request (HTTP/1.0 401 Authorization Required) by the server only.
+    @ivar pm: The password manager.
+    @ivar handler: The authentication handler.
+    """
+    
+    def __init__(self, **kwargs):
+        """
+        @param kwargs: Keyword arguments.
+            - B{proxy} - An http proxy to be specified on requests.
+                 The proxy is defined as {protocol:proxy,}
+                    - type: I{dict}
+                    - default: {}
+            - B{timeout} - Set the url open timeout (seconds).
+                    - type: I{float}
+                    - default: 90
+            - B{username} - The username used for http authentication.
+                    - type: I{str}
+                    - default: None
+            - B{password} - The password used for http authentication.
+                    - type: I{str}
+                    - default: None
+        """
+        HttpTransport.__init__(self, **kwargs)
+        self.pm = u2.HTTPPasswordMgrWithDefaultRealm()
+        
+    def open(self, request):
+        self.addcredentials(request)
+        return  HttpTransport.open(self, request)
+    
+    def send(self, request):
+        self.addcredentials(request)
+        return  HttpTransport.send(self, request)
+    
+    def addcredentials(self, request):
+        credentials = self.credentials()
+        if not (None in credentials):
+            u = credentials[0]
+            p = credentials[1]
+            self.pm.add_password(None, request.url, u, p)
+    
+    def credentials(self):
+        return (self.options.username, self.options.password)
+    
+    def u2handlers(self):
+            handlers = HttpTransport.u2handlers(self)
+            handlers.append(u2.HTTPBasicAuthHandler(self.pm))
+            return handlers
+    
+    
+class WindowsHttpAuthenticated(HttpAuthenticated):
+    """
+    Provides Windows (NTLM) http authentication.
+    @ivar pm: The password manager.
+    @ivar handler: The authentication handler.
+    @author: Christopher Bess
+    """
+        
+    def u2handlers(self):
+        # try to import ntlm support  
+        try:
+            from ntlm import HTTPNtlmAuthHandler
+        except ImportError:
+            raise Exception("Cannot import python-ntlm module")
+        handlers = HttpTransport.u2handlers(self)
+        handlers.append(HTTPNtlmAuthHandler.HTTPNtlmAuthHandler(self.pm))
+        return handlers
diff --git a/suds/transport/options.py b/suds/transport/options.py
new file mode 100644
index 0000000..8b0d194
--- /dev/null
+++ b/suds/transport/options.py
@@ -0,0 +1,57 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Contains classes for transport options.
+"""
+
+
+from suds.transport import *
+from suds.properties import *
+
+   
+class Options(Skin):
+    """
+    Options:
+        - B{proxy} - An http proxy to be specified on requests.
+             The proxy is defined as {protocol:proxy,}
+                - type: I{dict}
+                - default: {}
+        - B{timeout} - Set the url open timeout (seconds).
+                - type: I{float}
+                - default: 90
+        - B{headers} - Extra HTTP headers.
+                - type: I{dict}
+                    - I{str} B{http} - The I{http} protocol proxy URL.
+                    - I{str} B{https} - The I{https} protocol proxy URL.
+                - default: {}
+        - B{username} - The username used for http authentication.
+                - type: I{str}
+                - default: None
+        - B{password} - The password used for http authentication.
+                - type: I{str}
+                - default: None
+    """    
+    def __init__(self, **kwargs):
+        domain = __name__
+        definitions = [
+            Definition('proxy', dict, {}),
+            Definition('timeout', (int,float), 90),
+            Definition('headers', dict, {}),
+            Definition('username', basestring, None),
+            Definition('password', basestring, None),
+        ]
+        Skin.__init__(self, domain, definitions, kwargs)
\ No newline at end of file
diff --git a/suds/umx/__init__.py b/suds/umx/__init__.py
new file mode 100644
index 0000000..9d06b40
--- /dev/null
+++ b/suds/umx/__init__.py
@@ -0,0 +1,56 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides modules containing classes to support
+unmarshalling (XML).
+"""
+
+from suds.sudsobject import Object
+
+
+
+class Content(Object):
+    """
+    @ivar node: The content source node.
+    @type node: L{sax.element.Element}
+    @ivar data: The (optional) content data.
+    @type data: L{Object}
+    @ivar text: The (optional) content (xml) text.
+    @type text: basestring
+    """
+    
+    extensions = []
+
+    def __init__(self, node, **kwargs):
+        Object.__init__(self)
+        self.node = node
+        self.data = None
+        self.text = None
+        for k,v in kwargs.items():
+            setattr(self, k, v)
+
+    def __getattr__(self, name):
+        if name not in self.__dict__:
+            if name in self.extensions:
+                v = None
+                setattr(self, name, v)
+            else:
+                raise AttributeError, \
+                    'Content has no attribute %s' % name
+        else:
+            v = self.__dict__[name]
+        return v
\ No newline at end of file
diff --git a/suds/umx/attrlist.py b/suds/umx/attrlist.py
new file mode 100644
index 0000000..3694327
--- /dev/null
+++ b/suds/umx/attrlist.py
@@ -0,0 +1,88 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides filtered attribute list classes.
+"""
+
+from suds import *
+from suds.umx import *
+from suds.sax import Namespace
+
+
+class AttrList:
+    """
+    A filtered attribute list.
+    Items are included during iteration if they are in either the (xs) or
+    (xml) namespaces.
+    @ivar raw: The I{raw} attribute list.
+    @type raw: list
+    """
+    def __init__(self, attributes):
+        """
+        @param attributes: A list of attributes
+        @type attributes: list
+        """
+        self.raw = attributes
+        
+    def real(self):
+        """
+        Get list of I{real} attributes which exclude xs and xml attributes.
+        @return: A list of I{real} attributes.
+        @rtype: I{generator}
+        """
+        for a in self.raw:
+            if self.skip(a): continue
+            yield a
+            
+    def rlen(self):
+        """
+        Get the number of I{real} attributes which exclude xs and xml attributes.
+        @return: A count of I{real} attributes. 
+        @rtype: L{int}
+        """
+        n = 0
+        for a in self.real():
+            n += 1
+        return n
+            
+    def lang(self):
+        """
+        Get list of I{filtered} attributes which exclude xs.
+        @return: A list of I{filtered} attributes.
+        @rtype: I{generator}
+        """
+        for a in self.raw:
+            if a.qname() == 'xml:lang':
+                return a.value
+            return None
+
+    def skip(self, attr):
+        """
+        Get whether to skip (filter-out) the specified attribute.
+        @param attr: An attribute.
+        @type attr: I{Attribute}
+        @return: True if should be skipped.
+        @rtype: bool
+        """
+        ns = attr.namespace()
+        skip = (
+            Namespace.xmlns[1],
+            'http://schemas.xmlsoap.org/soap/encoding/',
+            'http://schemas.xmlsoap.org/soap/envelope/',
+            'http://www.w3.org/2003/05/soap-envelope',
+        )
+        return ( Namespace.xs(ns) or ns[1] in skip )
diff --git a/suds/umx/basic.py b/suds/umx/basic.py
new file mode 100644
index 0000000..cdc1e66
--- /dev/null
+++ b/suds/umx/basic.py
@@ -0,0 +1,41 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides basic unmarshaller classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.umx import *
+from suds.umx.core import Core
+
+
+class Basic(Core):
+    """
+    A object builder (unmarshaller).
+    """
+        
+    def process(self, node):
+        """
+        Process an object graph representation of the xml I{node}.
+        @param node: An XML tree.
+        @type node: L{sax.element.Element}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        content = Content(node)
+        return Core.process(self, content)
\ No newline at end of file
diff --git a/suds/umx/core.py b/suds/umx/core.py
new file mode 100644
index 0000000..7fb4ac4
--- /dev/null
+++ b/suds/umx/core.py
@@ -0,0 +1,216 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides base classes for XML->object I{unmarshalling}.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.umx import *
+from suds.umx.attrlist import AttrList
+from suds.sax.text import Text
+from suds.sudsobject import Factory, merge
+
+
+log = getLogger(__name__)
+
+reserved = { 'class':'cls', 'def':'dfn', }
+
+class Core:
+    """
+    The abstract XML I{node} unmarshaller.  This class provides the
+    I{core} unmarshalling functionality.
+    """
+        
+    def process(self, content):
+        """
+        Process an object graph representation of the xml I{node}.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        self.reset()
+        return self.append(content)
+    
+    def append(self, content):
+        """
+        Process the specified node and convert the XML document into
+        a I{suds} L{object}.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A I{append-result} tuple as: (L{Object}, I{value})
+        @rtype: I{append-result}
+        @note: This is not the proper entry point.
+        @see: L{process()}
+        """
+        self.start(content)
+        self.append_attributes(content)
+        self.append_children(content)
+        self.append_text(content)
+        self.end(content)
+        return self.postprocess(content)
+            
+    def postprocess(self, content):
+        """
+        Perform final processing of the resulting data structure as follows:
+          - Mixed values (children and text) will have a result of the I{content.node}.
+          - Simi-simple values (attributes, no-children and text) will have a result of a
+             property object.
+          - Simple values (no-attributes, no-children with text nodes) will have a string 
+             result equal to the value of the content.node.getText().
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: The post-processed result.
+        @rtype: I{any}
+        """
+        node = content.node
+        if len(node.children) and node.hasText():
+            return node
+        attributes = AttrList(node.attributes)
+        if attributes.rlen() and \
+            not len(node.children) and \
+            node.hasText():
+                p = Factory.property(node.name, node.getText())
+                return merge(content.data, p)
+        if len(content.data):
+            return content.data
+        lang = attributes.lang()
+        if content.node.isnil():
+            return None
+        if not len(node.children) and content.text is None:
+            if self.nillable(content):
+                return None
+            else:
+                return Text('', lang=lang)
+        if isinstance(content.text, basestring):
+            return Text(content.text, lang=lang)
+        else:
+            return content.text
+    
+    def append_attributes(self, content):
+        """
+        Append attribute nodes into L{Content.data}.
+        Attributes in the I{schema} or I{xml} namespaces are skipped.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        attributes = AttrList(content.node.attributes)
+        for attr in attributes.real():
+            name = attr.name
+            value = attr.value
+            self.append_attribute(name, value, content)
+            
+    def append_attribute(self, name, value, content):
+        """
+        Append an attribute name/value into L{Content.data}.
+        @param name: The attribute name
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        key = name
+        key = '_%s' % reserved.get(key, key)
+        setattr(content.data, key, value)
+            
+    def append_children(self, content):
+        """
+        Append child nodes into L{Content.data}
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        for child in content.node.children:
+            cont = Content(child)
+            cval = self.append(cont)
+            key = reserved.get(child.name, child.name)
+            if key in content.data:
+                v = getattr(content.data, key)
+                if isinstance(v, list):
+                    v.append(cval)
+                else:
+                    setattr(content.data, key, [v, cval])
+                continue
+            if self.unbounded(cont):
+                if cval is None:
+                    setattr(content.data, key, [])
+                else:
+                    setattr(content.data, key, [cval,])
+            else:
+                setattr(content.data, key, cval)
+    
+    def append_text(self, content):
+        """
+        Append text nodes into L{Content.data}
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        if content.node.hasText():
+            content.text = content.node.getText()
+        
+    def reset(self):
+        pass
+
+    def start(self, content):
+        """
+        Processing on I{node} has started.  Build and return
+        the proper object.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: A subclass of Object.
+        @rtype: L{Object}
+        """
+        content.data = Factory.object(content.node.name)
+    
+    def end(self, content):
+        """
+        Processing on I{node} has ended.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        pass
+    
+    def bounded(self, content):
+        """
+        Get whether the content is bounded (not a list).
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if bounded, else False
+        @rtype: boolean
+        '"""
+        return ( not self.unbounded(content) )
+    
+    def unbounded(self, content):
+        """
+        Get whether the object is unbounded (a list).
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if unbounded, else False
+        @rtype: boolean
+        '"""
+        return False
+    
+    def nillable(self, content):
+        """
+        Get whether the object is nillable.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: True if nillable, else False
+        @rtype: boolean
+        '"""
+        return False
\ No newline at end of file
diff --git a/suds/umx/encoded.py b/suds/umx/encoded.py
new file mode 100644
index 0000000..afe7374
--- /dev/null
+++ b/suds/umx/encoded.py
@@ -0,0 +1,128 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides soap encoded unmarshaller classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.umx import *
+from suds.umx.typed import Typed
+from suds.sax import splitPrefix, Namespace
+
+log = getLogger(__name__)
+
+#
+# Add encoded extensions
+# aty = The soap (section 5) encoded array type.
+#
+Content.extensions.append('aty')
+
+
+class Encoded(Typed):
+    """
+    A SOAP section (5) encoding unmarshaller.
+    This marshaller supports rpc/encoded soap styles.
+    """
+
+    def start(self, content):
+        #
+        # Grab the array type and continue
+        #
+        self.setaty(content)
+        Typed.start(self, content)
+    
+    def end(self, content):
+        #
+        # Squash soap encoded arrays into python lists.  This is
+        # also where we insure that empty arrays are represented
+        # as empty python lists.
+        #
+        aty = content.aty
+        if aty is not None:
+            self.promote(content)
+        return Typed.end(self, content)
+    
+    def postprocess(self, content):
+        #
+        # Ensure proper rendering of empty arrays.
+        #
+        if content.aty is None:
+            return Typed.postprocess(self, content)
+        else:
+            return content.data
+    
+    def setaty(self, content):
+        """
+        Grab the (aty) soap-enc:arrayType and attach it to the
+        content for proper array processing later in end().
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        @return: self
+        @rtype: L{Encoded}
+        """
+        name = 'arrayType'
+        ns = (None, 'http://schemas.xmlsoap.org/soap/encoding/')
+        aty = content.node.get(name, ns)
+        if aty is not None:
+            content.aty = aty
+            parts = aty.split('[')
+            ref = parts[0]
+            if len(parts) == 2:
+                self.applyaty(content, ref)
+            else:
+                pass # (2) dimensional array
+        return self
+    
+    def applyaty(self, content, xty):
+        """
+        Apply the type referenced in the I{arrayType} to the content
+        (child nodes) of the array.  Each element (node) in the array 
+        that does not have an explicit xsi:type attribute is given one
+        based on the I{arrayType}.
+        @param content: An array content.
+        @type content: L{Content}
+        @param xty: The XSI type reference.
+        @type xty: str
+        @return: self
+        @rtype: L{Encoded}
+        """
+        name = 'type'
+        ns = Namespace.xsins
+        parent = content.node
+        for child in parent.getChildren():
+            ref = child.get(name, ns)
+            if ref is None:
+                parent.addPrefix(ns[0], ns[1])
+                attr = ':'.join((ns[0], name))
+                child.set(attr, xty)
+        return self
+
+    def promote(self, content):
+        """
+        Promote (replace) the content.data with the first attribute
+        of the current content.data that is a I{list}.  Note: the 
+        content.data may be empty or contain only _x attributes.
+        In either case, the content.data is assigned an empty list.
+        @param content: An array content.
+        @type content: L{Content}
+        """
+        for n,v in content.data:
+            if isinstance(v, list):
+                content.data = v
+                return
+        content.data = []
\ No newline at end of file
diff --git a/suds/umx/typed.py b/suds/umx/typed.py
new file mode 100644
index 0000000..f272a25
--- /dev/null
+++ b/suds/umx/typed.py
@@ -0,0 +1,141 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+Provides typed unmarshaller classes.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.umx import *
+from suds.umx.core import Core
+from suds.resolver import NodeResolver, Frame
+from suds.sudsobject import Factory
+
+log = getLogger(__name__)
+
+
+#
+# Add typed extensions
+# type = The expected xsd type
+# real = The 'true' XSD type
+#
+Content.extensions.append('type')
+Content.extensions.append('real')
+
+
+class Typed(Core):
+    """
+    A I{typed} XML unmarshaller
+    @ivar resolver: A schema type resolver.
+    @type resolver: L{NodeResolver}
+    """
+    
+    def __init__(self, schema):
+        """
+        @param schema: A schema object.
+        @type schema: L{xsd.schema.Schema}
+        """
+        self.resolver = NodeResolver(schema)
+        
+    def process(self, node, type):
+        """
+        Process an object graph representation of the xml L{node}.
+        @param node: An XML tree.
+        @type node: L{sax.element.Element}
+        @param type: The I{optional} schema type.
+        @type type: L{xsd.sxbase.SchemaObject}
+        @return: A suds object.
+        @rtype: L{Object}
+        """
+        content = Content(node)
+        content.type = type
+        return Core.process(self, content)
+
+    def reset(self):
+        log.debug('reset')
+        self.resolver.reset()
+    
+    def start(self, content):
+        #
+        # Resolve to the schema type; build an object and setup metadata.
+        #
+        if content.type is None:
+            found = self.resolver.find(content.node)
+            if found is None:
+                log.error(self.resolver.schema)
+                raise TypeNotFound(content.node.qname())
+            content.type = found
+        else:
+            known = self.resolver.known(content.node)
+            frame = Frame(content.type, resolved=known)
+            self.resolver.push(frame)
+        real = self.resolver.top().resolved
+        content.real = real
+        cls_name = real.name
+        if cls_name is None:
+            cls_name = content.node.name
+        content.data = Factory.object(cls_name)
+        md = content.data.__metadata__
+        md.sxtype = real
+        
+    def end(self, content):
+        self.resolver.pop()
+        
+    def unbounded(self, content):
+        return content.type.unbounded()
+    
+    def nillable(self, content):
+        resolved = content.type.resolve()
+        return ( content.type.nillable or \
+            (resolved.builtin() and resolved.nillable ) )
+    
+    def append_attribute(self, name, value, content):
+        """
+        Append an attribute name/value into L{Content.data}.
+        @param name: The attribute name
+        @type name: basestring
+        @param value: The attribute's value
+        @type value: basestring
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        type = self.resolver.findattr(name)
+        if type is None:
+            log.warn('attribute (%s) type, not-found', name)
+        else:
+            value = self.translated(value, type)
+        Core.append_attribute(self, name, value, content)
+    
+    def append_text(self, content):
+        """
+        Append text nodes into L{Content.data}
+        Here is where the I{true} type is used to translate the value
+        into the proper python type.
+        @param content: The current content being unmarshalled.
+        @type content: L{Content}
+        """
+        Core.append_text(self, content)
+        known = self.resolver.top().resolved
+        content.text = self.translated(content.text, known)
+            
+    def translated(self, value, type):
+        """ translate using the schema type """
+        if value is not None:
+            resolved = type.resolve()
+            return resolved.translate(value)
+        else:
+            return value
\ No newline at end of file
diff --git a/suds/wsdl.py b/suds/wsdl.py
new file mode 100644
index 0000000..26f15b5
--- /dev/null
+++ b/suds/wsdl.py
@@ -0,0 +1,922 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{wsdl} module provides an objectification of the WSDL.
+The primary class is I{Definitions} as it represends the root element
+found in the document.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax import splitPrefix
+from suds.sax.element import Element
+from suds.bindings.document import Document
+from suds.bindings.rpc import RPC, Encoded
+from suds.xsd import qualify, Namespace
+from suds.xsd.schema import Schema, SchemaCollection
+from suds.xsd.query import ElementQuery
+from suds.sudsobject import Object, Facade, Metadata
+from suds.reader import DocumentReader, DefinitionsReader
+from urlparse import urljoin
+import re, soaparray
+
+log = getLogger(__name__)
+
+wsdlns = (None, "http://schemas.xmlsoap.org/wsdl/")
+soapns = (None, 'http://schemas.xmlsoap.org/wsdl/soap/')
+soap12ns = (None, 'http://schemas.xmlsoap.org/wsdl/soap12/')
+
+
+class WObject(Object):
+    """
+    Base object for wsdl types.
+    @ivar root: The XML I{root} element.
+    @type root: L{Element}
+    """
+    
+    def __init__(self, root, definitions=None):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        Object.__init__(self)
+        self.root = root
+        pmd = Metadata()
+        pmd.excludes = ['root']
+        pmd.wrappers = dict(qname=repr)
+        self.__metadata__.__print__ = pmd
+        
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        pass
+
+        
+class NamedObject(WObject):
+    """
+    A B{named} WSDL object.
+    @ivar name: The name of the object.
+    @type name: str
+    @ivar qname: The I{qualified} name of the object.
+    @type qname: (name, I{namespace-uri}).
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root, definitions)
+        self.name = root.get('name')
+        self.qname = (self.name, definitions.tns[1])
+        pmd = self.__metadata__.__print__
+        pmd.wrappers['qname'] = repr
+
+
+class Definitions(WObject):
+    """
+    Represents the I{root} container of the WSDL objects as defined
+    by <wsdl:definitions/>
+    @ivar id: The object id.
+    @type id: str
+    @ivar options: An options dictionary.
+    @type options: L{options.Options}
+    @ivar url: The URL used to load the object.
+    @type url: str
+    @ivar tns: The target namespace for the WSDL.
+    @type tns: str
+    @ivar schema: The collective WSDL schema object.
+    @type schema: L{SchemaCollection}
+    @ivar children: The raw list of child objects.
+    @type children: [L{WObject},...]
+    @ivar imports: The list of L{Import} children.
+    @type imports: [L{Import},...]
+    @ivar messages: The dictionary of L{Message} children key'd by I{qname}
+    @type messages: [L{Message},...]
+    @ivar port_types: The dictionary of L{PortType} children key'd by I{qname}
+    @type port_types: [L{PortType},...]
+    @ivar bindings: The dictionary of L{Binding} children key'd by I{qname}
+    @type bindings: [L{Binding},...]
+    @ivar service: The service object.
+    @type service: L{Service}
+    """
+    
+    Tag = 'definitions'
+
+    def __init__(self, url, options):
+        """
+        @param url: A URL to the WSDL.
+        @type url: str
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        """
+        log.debug('reading wsdl at: %s ...', url)
+        reader = DocumentReader(options)
+        d = reader.open(url)
+        root = d.root()
+        WObject.__init__(self, root)
+        self.id = objid(self)
+        self.options = options
+        self.url = url
+        self.tns = self.mktns(root)
+        self.types = []
+        self.schema = None
+        self.children = []
+        self.imports = []
+        self.messages = {}
+        self.port_types = {}
+        self.bindings = {}
+        self.services = []
+        self.add_children(self.root)
+        self.children.sort()
+        pmd = self.__metadata__.__print__
+        pmd.excludes.append('children')
+        pmd.excludes.append('wsdl')
+        pmd.wrappers['schema'] = repr
+        self.open_imports()
+        self.resolve()
+        self.build_schema()
+        self.set_wrapped()
+        for s in self.services:
+            self.add_methods(s)
+        log.debug("wsdl at '%s' loaded:\n%s", url, self)
+        
+    def mktns(self, root):
+        """ Get/create the target namespace """
+        tns = root.get('targetNamespace')
+        prefix = root.findPrefix(tns)
+        if prefix is None:
+            log.debug('warning: tns (%s), not mapped to prefix', tns)
+            prefix = 'tns'
+        return (prefix, tns)
+        
+    def add_children(self, root):
+        """ Add child objects using the factory """
+        for c in root.getChildren(ns=wsdlns):
+            child = Factory.create(c, self)
+            if child is None: continue
+            self.children.append(child)
+            if isinstance(child, Import):
+                self.imports.append(child)
+                continue
+            if isinstance(child, Types):
+                self.types.append(child)
+                continue
+            if isinstance(child, Message):
+                self.messages[child.qname] = child
+                continue
+            if isinstance(child, PortType):
+                self.port_types[child.qname] = child
+                continue
+            if isinstance(child, Binding):
+                self.bindings[child.qname] = child
+                continue
+            if isinstance(child, Service):
+                self.services.append(child)
+                continue
+                
+    def open_imports(self):
+        """ Import the I{imported} WSDLs. """
+        for imp in self.imports:
+            imp.load(self)
+                
+    def resolve(self):
+        """ Tell all children to resolve themselves """
+        for c in self.children:
+            c.resolve(self)
+                
+    def build_schema(self):
+        """ Process L{Types} objects and create the schema collection """
+        container = SchemaCollection(self)
+        for t in [t for t in self.types if t.local()]:
+            for root in t.contents():
+                schema = Schema(root, self.url, self.options, container)
+                container.add(schema)
+        if not len(container): # empty
+            root = Element.buildPath(self.root, 'types/schema')
+            schema = Schema(root, self.url, self.options, container)
+            container.add(schema)
+        self.schema = container.load(self.options)
+        for s in [t.schema() for t in self.types if t.imported()]:
+            self.schema.merge(s)
+        return self.schema
+                
+    def add_methods(self, service):
+        """ Build method view for service """
+        bindings = {
+            'document/literal' : Document(self),
+            'rpc/literal' : RPC(self),
+            'rpc/encoded' : Encoded(self)
+        }
+        for p in service.ports:
+            binding = p.binding
+            ptype = p.binding.type
+            operations = p.binding.type.operations.values()
+            for name in [op.name for op in operations]:
+                m = Facade('Method')
+                m.name = name
+                m.location = p.location
+                m.binding = Facade('binding')
+                op = binding.operation(name)
+                m.soap = op.soap
+                key = '/'.join((op.soap.style, op.soap.input.body.use))
+                m.binding.input = bindings.get(key)
+                key = '/'.join((op.soap.style, op.soap.output.body.use))
+                m.binding.output = bindings.get(key)
+                op = ptype.operation(name)
+                p.methods[name] = m
+                
+    def set_wrapped(self):
+        """ set (wrapped|bare) flag on messages """
+        for b in self.bindings.values():
+            for op in b.operations.values():
+                for body in (op.soap.input.body, op.soap.output.body):
+                    body.wrapped = False
+                    if len(body.parts) != 1:
+                        continue
+                    for p in body.parts:
+                        if p.element is None:
+                            continue
+                        query = ElementQuery(p.element)
+                        pt = query.execute(self.schema)
+                        if pt is None:
+                            raise TypeNotFound(query.ref)
+                        resolved = pt.resolve()
+                        if resolved.builtin():
+                            continue
+                        body.wrapped = True
+                        
+    def __getstate__(self):
+        nopickle = ('options',)
+        state = self.__dict__.copy()
+        for k in nopickle:
+            if k in state:
+                del state[k]
+        return state
+    
+    def __repr__(self):
+        return 'Definitions (id=%s)' % self.id
+
+
+class Import(WObject):
+    """
+    Represents the <wsdl:import/>.
+    @ivar location: The value of the I{location} attribute.
+    @type location: str
+    @ivar ns: The value of the I{namespace} attribute.
+    @type ns: str
+    @ivar imported: The imported object.
+    @type imported: L{Definitions}
+    """
+    
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root, definitions)
+        self.location = root.get('location')
+        self.ns = root.get('namespace')
+        self.imported = None
+        pmd = self.__metadata__.__print__
+        pmd.wrappers['imported'] = repr
+        
+    def load(self, definitions):
+        """ Load the object by opening the URL """
+        url = self.location
+        log.debug('importing (%s)', url)
+        if '://' not in url:
+            url = urljoin(definitions.url, url)
+        reader = DefinitionsReader(definitions.options, Definitions)
+        d = reader.open(url)
+        if d.root.match(Definitions.Tag, wsdlns):
+            self.import_definitions(definitions, d)
+            return
+        if d.root.match(Schema.Tag, Namespace.xsdns):
+            self.import_schema(definitions, d)
+            return
+        raise Exception('document at "%s" is unknown' % url)
+    
+    def import_definitions(self, definitions, d):
+        """ import/merge wsdl definitions """
+        definitions.types += d.types
+        definitions.messages.update(d.messages)
+        definitions.port_types.update(d.port_types)
+        definitions.bindings.update(d.bindings)
+        self.imported = d
+        log.debug('imported (WSDL):\n%s', d)
+        
+    def import_schema(self, definitions, d):
+        """ import schema as <types/> content """
+        if not len(definitions.types):
+            types = Types.create(definitions)
+            definitions.types.append(types)
+        else:
+            types = definitions.types[-1]
+        types.root.append(d.root)
+        log.debug('imported (XSD):\n%s', d.root)
+   
+    def __gt__(self, other):
+        return False
+        
+
+class Types(WObject):
+    """
+    Represents <types><schema/></types>.
+    """
+    
+    @classmethod
+    def create(cls, definitions):
+        root = Element('types', ns=wsdlns)
+        definitions.root.insert(root)
+        return Types(root, definitions)
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        WObject.__init__(self, root, definitions)
+        self.definitions = definitions
+        
+    def contents(self):
+        return self.root.getChildren('schema', Namespace.xsdns)
+    
+    def schema(self):
+        return self.definitions.schema
+    
+    def local(self):
+        return ( self.definitions.schema is None )
+    
+    def imported(self):
+        return ( not self.local() )
+        
+    def __gt__(self, other):
+        return isinstance(other, Import)
+    
+
+class Part(NamedObject):
+    """
+    Represents <message><part/></message>.
+    @ivar element: The value of the {element} attribute.
+        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
+    @type element: str
+    @ivar type: The value of the {type} attribute.
+        Stored as a I{qref} as converted by L{suds.xsd.qualify}.
+    @type type: str
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        pmd = Metadata()
+        pmd.wrappers = dict(element=repr, type=repr)
+        self.__metadata__.__print__ = pmd
+        tns = definitions.tns
+        self.element = self.__getref('element', tns)
+        self.type = self.__getref('type', tns)
+        
+    def __getref(self, a, tns):
+        """ Get the qualified value of attribute named 'a'."""
+        s = self.root.get(a)
+        if s is None:
+            return s
+        else:
+            return qualify(s, self.root, tns)  
+
+
+class Message(NamedObject):
+    """
+    Represents <message/>.
+    @ivar parts: A list of message parts.
+    @type parts: [I{Part},...]
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.parts = []
+        for p in root.getChildren('part'):
+            part = Part(p, definitions)
+            self.parts.append(part)
+            
+    def __gt__(self, other):
+        return isinstance(other, (Import, Types))
+    
+    
+class PortType(NamedObject):
+    """
+    Represents <portType/>.
+    @ivar operations: A list of contained operations.
+    @type operations: list
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.operations = {}
+        for c in root.getChildren('operation'):
+            op = Facade('Operation')
+            op.name = c.get('name')
+            op.tns = definitions.tns
+            input = c.getChild('input')
+            if input is None:
+                op.input = None
+            else:
+                op.input = input.get('message')
+            output = c.getChild('output')
+            if output is None:
+                op.output = None
+            else:
+                op.output = output.get('message')
+            faults = []
+            for fault in c.getChildren('fault'):
+                f = Facade('Fault')
+                f.name = fault.get('name')
+                f.message = fault.get('message')
+                faults.append(f)
+            op.faults = faults
+            self.operations[op.name] = op
+            
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        for op in self.operations.values():
+            if op.input is None:
+                op.input = Message(Element('no-input'), definitions)
+            else:
+                qref = qualify(op.input, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception("msg '%s', not-found" % op.input)
+                else:
+                    op.input = msg
+            if op.output is None:
+                op.output = Message(Element('no-output'), definitions)
+            else:
+                qref = qualify(op.output, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception("msg '%s', not-found" % op.output)
+                else:
+                    op.output = msg
+            for f in op.faults:
+                qref = qualify(f.message, self.root, definitions.tns)
+                msg = definitions.messages.get(qref)
+                if msg is None:
+                    raise Exception, "msg '%s', not-found" % f.message
+                f.message = msg
+                
+    def operation(self, name):
+        """
+        Shortcut used to get a contained operation by name.
+        @param name: An operation name.
+        @type name: str
+        @return: The named operation.
+        @rtype: Operation
+        @raise L{MethodNotFound}: When not found.
+        """
+        try:
+            return self.operations[name]
+        except Exception, e:
+            raise MethodNotFound(name)
+                
+    def __gt__(self, other):
+        return isinstance(other, (Import, Types, Message))
+
+
+class Binding(NamedObject):
+    """
+    Represents <binding/>
+    @ivar operations: A list of contained operations.
+    @type operations: list
+    """
+
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.operations = {}
+        self.type = root.get('type')
+        sr = self.soaproot()
+        if sr is None:
+            self.soap = None
+            log.debug('binding: "%s" not a soap binding', self.name)
+            return
+        soap = Facade('soap')
+        self.soap = soap
+        self.soap.style = sr.get('style', default='document')
+        self.add_operations(self.root, definitions)
+        
+    def soaproot(self):
+        """ get the soap:binding """
+        for ns in (soapns, soap12ns):
+            sr =  self.root.getChild('binding', ns=ns)
+            if sr is not None:
+                return sr
+        return None
+        
+    def add_operations(self, root, definitions):
+        """ Add <operation/> children """
+        dsop = Element('operation', ns=soapns)
+        for c in root.getChildren('operation'):
+            op = Facade('Operation')
+            op.name = c.get('name')
+            sop = c.getChild('operation', default=dsop)
+            soap = Facade('soap')
+            soap.action = '"%s"' % sop.get('soapAction', default='')
+            soap.style = sop.get('style', default=self.soap.style)
+            soap.input = Facade('Input')
+            soap.input.body = Facade('Body')
+            soap.input.headers = []
+            soap.output = Facade('Output')
+            soap.output.body = Facade('Body')
+            soap.output.headers = []
+            op.soap = soap
+            input = c.getChild('input')
+            if input is None:
+                input = Element('input', ns=wsdlns)
+            body = input.getChild('body')
+            self.body(definitions, soap.input.body, body)
+            for header in input.getChildren('header'):
+                self.header(definitions, soap.input, header)
+            output = c.getChild('output')
+            if output is None:
+                output = Element('output', ns=wsdlns)
+            body = output.getChild('body')
+            self.body(definitions, soap.output.body, body)
+            for header in output.getChildren('header'):
+                self.header(definitions, soap.output, header)
+            faults = []
+            for fault in c.getChildren('fault'):
+                sf = fault.getChild('fault')
+                if sf is None:
+                    continue
+                fn = fault.get('name')
+                f = Facade('Fault')
+                f.name = sf.get('name', default=fn)
+                f.use = sf.get('use', default='literal')
+                faults.append(f)
+            soap.faults = faults
+            self.operations[op.name] = op
+            
+    def body(self, definitions, body, root):
+        """ add the input/output body properties """
+        if root is None:
+            body.use = 'literal'
+            body.namespace = definitions.tns
+            body.parts = ()
+            return
+        parts = root.get('parts')
+        if parts is None:
+            body.parts = ()
+        else:
+            body.parts = re.split('[\s,]', parts)
+        body.use = root.get('use', default='literal')
+        ns = root.get('namespace')
+        if ns is None:
+            body.namespace = definitions.tns
+        else:
+            prefix = root.findPrefix(ns, 'b0')
+            body.namespace = (prefix, ns)
+            
+    def header(self, definitions, parent, root):
+        """ add the input/output header properties """
+        if root is None:
+            return
+        header = Facade('Header')
+        parent.headers.append(header)
+        header.use = root.get('use', default='literal')
+        ns = root.get('namespace')
+        if ns is None:
+            header.namespace = definitions.tns
+        else:
+            prefix = root.findPrefix(ns, 'h0')
+            header.namespace = (prefix, ns)
+        msg = root.get('message')
+        if msg is not None:
+            header.message = msg
+        part = root.get('part')
+        if part is not None:
+            header.part = part
+            
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.  This includes
+        cross-linking information (from) the portType (to) the I{soap}
+        protocol information on the binding for each operation.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        self.resolveport(definitions)
+        for op in self.operations.values():
+            self.resolvesoapbody(definitions, op)
+            self.resolveheaders(definitions, op)
+            self.resolvefaults(definitions, op)
+        
+    def resolveport(self, definitions):
+        """
+        Resolve port_type reference.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        ref = qualify(self.type, self.root, definitions.tns)
+        port_type = definitions.port_types.get(ref)
+        if port_type is None:
+            raise Exception("portType '%s', not-found" % self.type)
+        else:
+            self.type = port_type
+            
+    def resolvesoapbody(self, definitions, op):
+        """
+        Resolve soap body I{message} parts by 
+        cross-referencing with operation defined in port type.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        ptop = self.type.operation(op.name)
+        if ptop is None:
+            raise Exception, \
+                "operation '%s' not defined in portType" % op.name
+        soap = op.soap
+        parts = soap.input.body.parts
+        if len(parts):
+            pts = []
+            for p in ptop.input.parts:
+                if p.name in parts:
+                    pts.append(p)
+            soap.input.body.parts = pts
+        else:
+            soap.input.body.parts = ptop.input.parts
+        parts = soap.output.body.parts
+        if len(parts):
+            pts = []
+            for p in ptop.output.parts:
+                if p.name in parts:
+                    pts.append(p)
+            soap.output.body.parts = pts
+        else:
+            soap.output.body.parts = ptop.output.parts
+            
+    def resolveheaders(self, definitions, op):
+        """
+        Resolve soap header I{message} references.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        soap = op.soap
+        headers = soap.input.headers + soap.output.headers
+        for header in headers:
+            mn = header.message
+            ref = qualify(mn, self.root, definitions.tns)
+            message = definitions.messages.get(ref)
+            if message is None:
+                raise Exception, "message'%s', not-found" % mn
+            pn = header.part
+            for p in message.parts:
+                if p.name == pn:
+                    header.part = p
+                    break
+            if pn == header.part:
+                raise Exception, \
+                    "message '%s' has not part named '%s'" % (ref, pn)
+                        
+    def resolvefaults(self, definitions, op):
+        """
+        Resolve soap fault I{message} references by
+        cross-referencing with operation defined in port type.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param op: An I{operation} object.
+        @type op: I{operation}
+        """
+        ptop = self.type.operation(op.name)
+        if ptop is None:
+            raise Exception, \
+                "operation '%s' not defined in portType" % op.name
+        soap = op.soap
+        for fault in soap.faults:
+            for f in ptop.faults:
+                if f.name == fault.name:
+                    fault.parts = f.message.parts
+                    continue
+            if hasattr(fault, 'parts'):
+                continue
+            raise Exception, \
+                "fault '%s' not defined in portType '%s'" % (fault.name, self.type.name)
+            
+    def operation(self, name):
+        """
+        Shortcut used to get a contained operation by name.
+        @param name: An operation name.
+        @type name: str
+        @return: The named operation.
+        @rtype: Operation
+        @raise L{MethodNotFound}: When not found.
+        """
+        try:
+            return self.operations[name]
+        except:
+            raise MethodNotFound(name)
+            
+    def __gt__(self, other):
+        return ( not isinstance(other, Service) )
+
+
+class Port(NamedObject):
+    """
+    Represents a service port.
+    @ivar service: A service.
+    @type service: L{Service}
+    @ivar binding: A binding name.
+    @type binding: str
+    @ivar location: The service location (url).
+    @type location: str
+    """
+    
+    def __init__(self, root, definitions, service):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @param service: A service object.
+        @type service: L{Service}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.__service = service
+        self.binding = root.get('binding')
+        address = root.getChild('address')
+        if address is None:
+            self.location = None
+        else:
+            self.location = address.get('location').encode('utf-8')
+        self.methods = {}
+        
+    def method(self, name):
+        """
+        Get a method defined in this portType by name.
+        @param name: A method name.
+        @type name: str
+        @return: The requested method object.
+        @rtype: I{Method}
+        """
+        return self.methods.get(name)
+        
+
+class Service(NamedObject):
+    """
+    Represents <service/>.
+    @ivar port: The contained ports.
+    @type port: [Port,..]
+    @ivar methods: The contained methods for all ports.
+    @type methods: [Method,..]
+    """
+    
+    def __init__(self, root, definitions):
+        """
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        NamedObject.__init__(self, root, definitions)
+        self.ports = []
+        for p in root.getChildren('port'):
+            port = Port(p, definitions, self)
+            self.ports.append(port)
+            
+    def port(self, name):
+        """
+        Locate a port by name.
+        @param name: A port name.
+        @type name: str
+        @return: The port object.
+        @rtype: L{Port} 
+        """
+        for p in self.ports:
+            if p.name == name:
+                return p
+        return None
+    
+    def setlocation(self, url, names=None):
+        """
+        Override the invocation location (url) for service method.
+        @param url: A url location.
+        @type url: A url.
+        @param names:  A list of method names.  None=ALL
+        @type names: [str,..]
+        """
+        for p in self.ports:
+            for m in p.methods.values():
+                if names is None or m.name in names:
+                    m.location = url
+        
+    def resolve(self, definitions):
+        """
+        Resolve named references to other WSDL objects.
+        Ports without soap bindings are discarded.
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        """
+        filtered = []
+        for p in self.ports:
+            ref = qualify(p.binding, self.root, definitions.tns)
+            binding = definitions.bindings.get(ref)
+            if binding is None:
+                raise Exception("binding '%s', not-found" % p.binding)
+            if binding.soap is None:
+                log.debug('binding "%s" - not a soap, discarded', binding.name)
+                continue
+            p.binding = binding
+            filtered.append(p)
+        self.ports = filtered
+        
+    def __gt__(self, other):
+        return True
+
+
+class Factory:
+    """
+    Simple WSDL object factory.
+    @cvar tags: Dictionary of tag->constructor mappings.
+    @type tags: dict
+    """
+
+    tags =\
+    {
+        'import' : Import, 
+        'types' : Types, 
+        'message' : Message, 
+        'portType' : PortType,
+        'binding' : Binding,
+        'service' : Service,
+    }
+    
+    @classmethod
+    def create(cls, root, definitions):
+        """
+        Create an object based on the root tag name.
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param definitions: A definitions object.
+        @type definitions: L{Definitions}
+        @return: The created object.
+        @rtype: L{WObject} 
+        """
+        fn = cls.tags.get(root.name)
+        if fn is not None:
+            return fn(root, definitions)
+        else:
+            return None
diff --git a/suds/wsse.py b/suds/wsse.py
new file mode 100644
index 0000000..0fc8301
--- /dev/null
+++ b/suds/wsse.py
@@ -0,0 +1,206 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{wsse} module provides WS-Security.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sudsobject import Object
+from suds.sax.element import Element
+from datetime import datetime, timedelta
+
+try:
+    from hashlib import md5
+except ImportError:
+    # Python 2.4 compatibility
+    from md5 import md5
+
+
+dsns = \
+    ('ds',
+     'http://www.w3.org/2000/09/xmldsig#')
+wssens = \
+    ('wsse', 
+     'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd')
+wsuns = \
+    ('wsu',
+     'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd')
+wsencns = \
+    ('wsenc',
+     'http://www.w3.org/2001/04/xmlenc#')
+
+
+class Security(Object):
+    """
+    WS-Security object.
+    @ivar tokens: A list of security tokens
+    @type tokens: [L{Token},...]
+    @ivar signatures: A list of signatures.
+    @type signatures: TBD
+    @ivar references: A list of references.
+    @type references: TBD
+    @ivar keys: A list of encryption keys.
+    @type keys: TBD
+    """
+    
+    def __init__(self):
+        """ """
+        Object.__init__(self)
+        self.mustUnderstand = True
+        self.tokens = []
+        self.signatures = []
+        self.references = []
+        self.keys = []
+        
+    def xml(self):
+        """
+        Get xml representation of the object.
+        @return: The root node.
+        @rtype: L{Element}
+        """
+        root = Element('Security', ns=wssens)
+        root.set('mustUnderstand', str(self.mustUnderstand).lower())
+        for t in self.tokens:
+            root.append(t.xml())
+        return root
+
+
+class Token(Object):
+    """ I{Abstract} security token. """
+    
+    @classmethod
+    def now(cls):
+        return datetime.now()
+    
+    @classmethod
+    def sysdate(cls):
+        return cls.now().isoformat()
+    
+    def __init__(self):
+            Object.__init__(self)
+
+
+class UsernameToken(Token):
+    """
+    Represents a basic I{UsernameToken} WS-Secuirty token.
+    @ivar username: A username.
+    @type username: str
+    @ivar password: A password.
+    @type password: str
+    @ivar nonce: A set of bytes to prevent reply attacks.
+    @type nonce: str
+    @ivar created: The token created.
+    @type created: L{datetime}
+    """
+
+    def __init__(self, username=None, password=None):
+        """
+        @param username: A username.
+        @type username: str
+        @param password: A password.
+        @type password: str
+        """
+        Token.__init__(self)
+        self.username = username
+        self.password = password
+        self.nonce = None
+        self.created = None
+        
+    def setnonce(self, text=None):
+        """
+        Set I{nonce} which is arbitraty set of bytes to prevent
+        reply attacks.
+        @param text: The nonce text value.
+            Generated when I{None}.
+        @type text: str
+        """
+        if text is None:
+            s = []
+            s.append(self.username)
+            s.append(self.password)
+            s.append(Token.sysdate())
+            m = md5()
+            m.update(':'.join(s))
+            self.nonce = m.hexdigest()
+        else:
+            self.nonce = text
+        
+    def setcreated(self, dt=None):
+        """
+        Set I{created}.
+        @param dt: The created date & time.
+            Set as datetime.now() when I{None}.
+        @type dt: L{datetime}
+        """
+        if dt is None:
+            self.created = Token.now()
+        else:
+            self.created = dt
+        
+        
+    def xml(self):
+        """
+        Get xml representation of the object.
+        @return: The root node.
+        @rtype: L{Element}
+        """
+        root = Element('UsernameToken', ns=wssens)
+        u = Element('Username', ns=wssens)
+        u.setText(self.username)
+        root.append(u)
+        p = Element('Password', ns=wssens)
+        p.setText(self.password)
+        root.append(p)
+        if self.nonce is not None:
+            n = Element('Nonce', ns=wssens)
+            n.setText(self.nonce)
+            root.append(n)
+        if self.created is not None:
+            n = Element('Created', ns=wsuns)
+            n.setText(self.created.isoformat())
+            root.append(n)
+        return root
+
+
+class Timestamp(Token):
+    """
+    Represents the I{Timestamp} WS-Secuirty token.
+    @ivar created: The token created.
+    @type created: L{datetime}
+    @ivar expires: The token expires.
+    @type expires: L{datetime}
+    """
+
+    def __init__(self, validity=90):
+        """
+        @param validity: The time in seconds.
+        @type validity: int
+        """
+        Token.__init__(self)
+        self.created = Token.now()
+        self.expires = self.created + timedelta(seconds=validity)
+        
+    def xml(self):
+        root = Element("Timestamp", ns=wsuns)
+        created = Element('Created', ns=wsuns)
+        created.setText(self.created.isoformat())
+        expires = Element('Expires', ns=wsuns)
+        expires.setText(self.expires.isoformat())
+        root.append(created)
+        root.append(expires)
+        return root
\ No newline at end of file
diff --git a/suds/xsd/__init__.py b/suds/xsd/__init__.py
new file mode 100644
index 0000000..0917f3f
--- /dev/null
+++ b/suds/xsd/__init__.py
@@ -0,0 +1,86 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{schema} module provides a intelligent representation of
+an XSD schema.  The I{raw} model is the XML tree and the I{model}
+is the denormalized, objectified and intelligent view of the schema.
+Most of the I{value-add} provided by the model is centered around
+tranparent referenced type resolution and targeted denormalization.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sax import Namespace, splitPrefix
+
+log = getLogger(__name__)
+
+
+def qualify(ref, resolvers, defns=Namespace.default):
+    """
+    Get a reference that is I{qualified} by namespace.
+    @param ref: A referenced schema type name.
+    @type ref: str
+    @param resolvers: A list of objects to be used to resolve types.
+    @type resolvers: [L{sax.element.Element},]
+    @param defns: An optional target namespace used to qualify references
+        when no prefix is specified.
+    @type defns: A default namespace I{tuple: (prefix,uri)} used when ref not prefixed.
+    @return: A qualified reference.
+    @rtype: (name, namespace-uri)
+    """
+    ns = None
+    p, n = splitPrefix(ref)
+    if p is not None:
+        if not isinstance(resolvers, (list, tuple)):
+            resolvers = (resolvers,)
+        for r in resolvers:
+            resolved = r.resolvePrefix(p)
+            if resolved[1] is not None:
+                ns = resolved
+                break
+        if ns is None:
+            raise Exception('prefix (%s) not resolved' % p)
+    else:
+        ns = defns
+    return (n, ns[1])
+
+def isqref(object):
+    """
+    Get whether the object is a I{qualified reference}.
+    @param object: An object to be tested.
+    @type object: I{any}
+    @rtype: boolean
+    @see: L{qualify}
+    """
+    return (\
+        isinstance(object, tuple) and \
+        len(object) == 2 and \
+        isinstance(object[0], basestring) and \
+        isinstance(object[1], basestring))
+
+
+class Filter:
+    def __init__(self, inclusive=False, *items):
+        self.inclusive = inclusive
+        self.items = items
+    def __contains__(self, x):
+        if self.inclusive:
+            result = ( x in self.items )
+        else:
+            result = ( x not in self.items )
+        return result
+
diff --git a/suds/xsd/deplist.py b/suds/xsd/deplist.py
new file mode 100644
index 0000000..14ae19c
--- /dev/null
+++ b/suds/xsd/deplist.py
@@ -0,0 +1,140 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{depsolve} module defines a class for performing dependancy solving.
+"""
+
+from logging import getLogger
+from suds import *
+
+log = getLogger(__name__)
+
+
+class DepList:
+    """
+    Dependancy solving list.
+    Items are tuples: (object, (deps,))
+    @ivar raw: The raw (unsorted) items.
+    @type raw: list
+    @ivar index: The index of (unsorted) items.
+    @type index: list
+    @ivar stack: The sorting stack.
+    @type stack: list
+    @ivar pushed: The I{pushed} set tracks items that have been
+        processed.
+    @type pushed: set
+    @ivar sorted: The sorted list of items.
+    @type sorted: list
+    """
+
+    def __init__(self):
+        """ """
+        self.unsorted = []
+        self.index = {}
+        self.stack = []
+        self.pushed = set()
+        self.sorted = None
+        
+    def add(self, *items):
+        """
+        Add items to be sorted.
+        @param items: One or more items to be added.
+        @type items: I{item}
+        @return: self
+        @rtype: L{DepList}
+        """
+        for item in items:
+            self.unsorted.append(item)
+            key = item[0]
+            self.index[key] = item
+        return self
+        
+    def sort(self):
+        """
+        Sort the list based on dependancies.
+        @return: The sorted items.
+        @rtype: list
+        """
+        self.sorted = list()
+        self.pushed = set()
+        for item in self.unsorted:
+            popped = []
+            self.push(item)            
+            while len(self.stack):
+                try:
+                    top = self.top()
+                    ref = top[1].next()
+                    refd = self.index.get(ref)
+                    if refd is None:
+                        log.debug('"%s" not found, skipped', Repr(ref))
+                        continue
+                    self.push(refd)
+                except StopIteration:
+                    popped.append(self.pop())
+                    continue
+            for p in popped:
+                self.sorted.append(p)
+        self.unsorted = self.sorted
+        return self.sorted
+    
+    def top(self):
+        """
+        Get the item at the top of the stack.
+        @return: The top item.
+        @rtype: (item, iter)
+        """
+        return self.stack[-1]
+    
+    def push(self, item):
+        """
+        Push and item onto the sorting stack.
+        @param item: An item to push.
+        @type item: I{item}
+        @return: The number of items pushed.
+        @rtype: int
+        """
+        if item in self.pushed:
+            return
+        frame = (item, iter(item[1]))
+        self.stack.append(frame)
+        self.pushed.add(item)
+    
+    def pop(self):
+        """
+        Pop the top item off the stack and append
+        it to the sorted list.
+        @return: The popped item.
+        @rtype: I{item}
+        """
+        try:
+            frame = self.stack.pop()
+            return frame[0]
+        except:
+            pass
+
+
+if __name__ == '__main__':
+    a = ('a', ('x',))
+    b = ('b', ('a',))
+    c = ('c', ('a','b'))
+    d = ('d', ('c',))
+    e = ('e', ('d','a'))
+    f = ('f', ('e','c','d','a'))
+    x = ('x', ())
+    L = DepList()
+    L.add(c, e, d, b, f, a, x)
+    print [x[0] for x in L.sort()]
\ No newline at end of file
diff --git a/suds/xsd/doctor.py b/suds/xsd/doctor.py
new file mode 100644
index 0000000..84c9151
--- /dev/null
+++ b/suds/xsd/doctor.py
@@ -0,0 +1,212 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{doctor} module provides classes for fixing broken (sick)
+schema(s).
+"""
+
+from logging import getLogger
+from suds.sax import splitPrefix, Namespace
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+
+class Doctor:
+    """
+    Schema Doctor.
+    """
+    def examine(self, root):
+        """
+        Examine and repair the schema (if necessary).
+        @param root: A schema root element.
+        @type root: L{Element}
+        """
+        pass
+
+
+class Practice(Doctor):
+    """
+    A collection of doctors.
+    @ivar doctors: A list of doctors.
+    @type doctors: list
+    """
+    
+    def __init__(self):
+        self.doctors = []
+        
+    def add(self, doctor):
+        """
+        Add a doctor to the practice
+        @param doctor: A doctor to add.
+        @type doctor: L{Doctor}
+        """
+        self.doctors.append(doctor)
+
+    def examine(self, root):
+        for d in self.doctors:
+            d.examine(root)
+        return root
+
+
+class TnsFilter:
+    """
+    Target Namespace filter.
+    @ivar tns: A list of target namespaces.
+    @type tns: [str,...]
+    """
+
+    def __init__(self, *tns):
+        """
+        @param tns: A list of target namespaces.
+        @type tns: [str,...]
+        """
+        self.tns = []
+        self.add(*tns)
+        
+    def add(self, *tns):
+        """
+        Add I{targetNamesapces} to be added.
+        @param tns: A list of target namespaces.
+        @type tns: [str,...]
+        """
+        self.tns += tns
+
+    def match(self, root, ns):
+        """
+        Match by I{targetNamespace} excluding those that
+        are equal to the specified namespace to prevent
+        adding an import to itself.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        tns = root.get('targetNamespace')
+        if len(self.tns):
+            matched = ( tns in self.tns )
+        else:
+            matched = 1
+        itself = ( ns == tns )
+        return ( matched and not itself )
+    
+
+class Import:
+    """
+    An <xs:import/> to be applied.
+    @cvar xsdns: The XSD namespace.
+    @type xsdns: (p,u)
+    @ivar ns: An import namespace.
+    @type ns: str
+    @ivar location: An optional I{schemaLocation}.
+    @type location: str
+    @ivar filter: A filter used to restrict application to
+        a particular schema.
+    @type filter: L{TnsFilter}
+    """
+
+    xsdns = Namespace.xsdns
+    
+    def __init__(self, ns, location=None):
+        """
+        @param ns: An import namespace.
+        @type ns: str
+        @param location: An optional I{schemaLocation}.
+        @type location: str
+        """
+        self.ns = ns
+        self.location = location
+        self.filter = TnsFilter()
+        
+    def setfilter(self, filter):
+        """
+        Set the filter.
+        @param filter: A filter to set.
+        @type filter: L{TnsFilter}
+        """
+        self.filter = filter
+        
+    def apply(self, root):
+        """
+        Apply the import (rule) to the specified schema.
+        If the schema does not already contain an import for the
+        I{namespace} specified here, it is added.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        if not self.filter.match(root, self.ns):
+            return
+        if self.exists(root):
+            return
+        node = Element('import', ns=self.xsdns)
+        node.set('namespace', self.ns)
+        if self.location is not None:
+            node.set('schemaLocation', self.location)
+        log.debug('inserting: %s', node)
+        root.insert(node)
+        
+    def add(self, root):
+        """
+        Add an <xs:import/> to the specified schema root.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        node = Element('import', ns=self.xsdns)
+        node.set('namespace', self.ns)
+        if self.location is not None:
+            node.set('schemaLocation', self.location)
+        log.debug('%s inserted', node)
+        root.insert(node) 
+        
+    def exists(self, root):
+        """
+        Check to see if the <xs:import/> already exists
+        in the specified schema root by matching I{namesapce}.
+        @param root: A schema root.
+        @type root: L{Element}
+        """
+        for node in root.children:
+            if node.name != 'import':
+                continue
+            ns = node.get('namespace')
+            if self.ns == ns:
+                return 1
+        return 0
+    
+
+class ImportDoctor(Doctor):
+    """
+    Doctor used to fix missing imports.
+    @ivar imports: A list of imports to apply.
+    @type imports: [L{Import},...]
+    """
+
+    def __init__(self, *imports):
+        """
+        """
+        self.imports = []
+        self.add(*imports)
+        
+    def add(self, *imports):
+        """
+        Add a namesapce to be checked.
+        @param imports: A list of L{Import} objects.
+        @type imports: [L{Import},..]
+        """
+        self.imports += imports
+        
+    def examine(self, root):
+        for imp in self.imports:
+            imp.apply(root)
diff --git a/suds/xsd/query.py b/suds/xsd/query.py
new file mode 100644
index 0000000..c88b220
--- /dev/null
+++ b/suds/xsd/query.py
@@ -0,0 +1,208 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{query} module defines a class for performing schema queries.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.sudsobject import *
+from suds.xsd import qualify, isqref
+from suds.xsd.sxbuiltin import Factory
+
+log = getLogger(__name__)
+
+
+class Query(Object):
+    """
+    Schema query base class.
+    """
+    
+    def __init__(self, ref=None):
+        """
+        @param ref: The schema reference being queried.
+        @type ref: qref
+        """
+        Object.__init__(self)
+        self.id = objid(self)
+        self.ref = ref
+        self.history = []
+        self.resolved = False
+        if not isqref(self.ref):
+            raise Exception('%s, must be qref' % tostr(self.ref))
+        
+    def execute(self, schema):
+        """
+        Execute this query using the specified schema.
+        @param schema: The schema associated with the query.  The schema
+            is used by the query to search for items.
+        @type schema: L{schema.Schema}
+        @return: The item matching the search criteria.
+        @rtype: L{sxbase.SchemaObject}
+        """
+        raise Exception, 'not-implemented by subclass'
+        
+    def filter(self, result):
+        """
+        Filter the specified result based on query criteria.
+        @param result: A potential result.
+        @type result: L{sxbase.SchemaObject}
+        @return: True if result should be excluded.
+        @rtype: boolean
+        """
+        if result is None:
+            return True
+        reject = ( result in self.history )
+        if reject:
+            log.debug('result %s, rejected by\n%s', Repr(result), self)
+        return reject
+    
+    def result(self, result):
+        """
+        Query result post processing.
+        @param result: A query result.
+        @type result: L{sxbase.SchemaObject}
+        """
+        if result is None:
+            log.debug('%s, not-found', self.ref)
+            return
+        if self.resolved:
+            result = result.resolve()
+        log.debug('%s, found as: %s', self.ref, Repr(result))
+        self.history.append(result)
+        return result
+
+
+class BlindQuery(Query):
+    """
+    Schema query class that I{blindly} searches for a reference in
+    the specified schema.  It may be used to find Elements and Types but
+    will match on an Element first.  This query will also find builtins.
+    """
+        
+    def execute(self, schema):
+        if schema.builtin(self.ref):
+            name = self.ref[0]
+            b = Factory.create(schema, name)
+            log.debug('%s, found builtin (%s)', self.id, name)
+            return b
+        result = None
+        for d in (schema.elements, schema.types):
+            result = d.get(self.ref)
+            if self.filter(result):
+                result = None
+            else:
+                break
+        if result is None:
+            eq = ElementQuery(self.ref)
+            eq.history = self.history
+            result = eq.execute(schema)
+        return self.result(result)
+
+
+class TypeQuery(Query):
+    """
+    Schema query class that searches for Type references in
+    the specified schema.  Matches on root types only.
+    """
+        
+    def execute(self, schema):
+        if schema.builtin(self.ref):
+            name = self.ref[0]
+            b = Factory.create(schema, name)
+            log.debug('%s, found builtin (%s)', self.id, name)
+            return b
+        result = schema.types.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class GroupQuery(Query):
+    """
+    Schema query class that searches for Group references in
+    the specified schema.
+    """
+        
+    def execute(self, schema):
+        result = schema.groups.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class AttrQuery(Query):
+    """
+    Schema query class that searches for Attribute references in
+    the specified schema.  Matches on root Attribute by qname first, then searches
+    deep into the document.
+    """
+        
+    def execute(self, schema):
+        result = schema.attributes.get(self.ref)
+        if self.filter(result):
+            result = self.__deepsearch(schema)
+        return self.result(result)
+    
+    def __deepsearch(self, schema):
+        from suds.xsd.sxbasic import Attribute
+        result = None
+        for e in schema.all:
+            result = e.find(self.ref, (Attribute,))
+            if self.filter(result):
+                result = None
+            else:
+                break
+        return result
+
+
+class AttrGroupQuery(Query):
+    """
+    Schema query class that searches for attributeGroup references in
+    the specified schema.
+    """
+        
+    def execute(self, schema):
+        result = schema.agrps.get(self.ref)
+        if self.filter(result):
+            result = None
+        return self.result(result)
+
+
+class ElementQuery(Query):
+    """
+    Schema query class that searches for Element references in
+    the specified schema.  Matches on root Elements by qname first, then searches
+    deep into the document.
+    """
+        
+    def execute(self, schema):
+        result = schema.elements.get(self.ref)
+        if self.filter(result):
+            result = self.__deepsearch(schema)
+        return self.result(result)
+    
+    def __deepsearch(self, schema):
+        from suds.xsd.sxbasic import Element
+        result = None
+        for e in schema.all:
+            result = e.find(self.ref, (Element,))
+            if self.filter(result):
+                result = None
+            else:
+                break
+        return result
\ No newline at end of file
diff --git a/suds/xsd/schema.py b/suds/xsd/schema.py
new file mode 100644
index 0000000..a22f1c5
--- /dev/null
+++ b/suds/xsd/schema.py
@@ -0,0 +1,415 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{schema} module provides a intelligent representation of
+an XSD schema.  The I{raw} model is the XML tree and the I{model}
+is the denormalized, objectified and intelligent view of the schema.
+Most of the I{value-add} provided by the model is centered around
+tranparent referenced type resolution and targeted denormalization.
+"""
+
+from logging import getLogger
+import suds.metrics
+from suds import *
+from suds.xsd import *
+from suds.xsd.sxbuiltin import *
+from suds.xsd.sxbasic import Factory as BasicFactory
+from suds.xsd.sxbuiltin import Factory as BuiltinFactory
+from suds.xsd.sxbase import SchemaObject
+from suds.xsd.deplist import DepList
+from suds.sax.element import Element
+from suds.sax import splitPrefix, Namespace
+
+log = getLogger(__name__)
+
+
+class SchemaCollection:
+    """
+    A collection of schema objects.  This class is needed because WSDLs 
+    may contain more then one <schema/> node.
+    @ivar wsdl: A wsdl object.
+    @type wsdl: L{suds.wsdl.Definitions}
+    @ivar children: A list contained schemas.
+    @type children: [L{Schema},...]
+    @ivar namespaces: A dictionary of contained schemas by namespace.
+    @type namespaces: {str:L{Schema}}
+    """
+    
+    def __init__(self, wsdl):
+        """
+        @param wsdl: A wsdl object.
+        @type wsdl: L{suds.wsdl.Definitions}
+        """
+        self.wsdl = wsdl
+        self.children = []
+        self.namespaces = {}
+        
+    def add(self, schema):
+        """
+        Add a schema node to the collection.  Schema(s) within the same target
+        namespace are consolidated.
+        @param schema: A schema object.
+        @type schema: (L{Schema})
+        """
+        key = schema.tns[1]
+        existing = self.namespaces.get(key)
+        if existing is None:
+            self.children.append(schema)
+            self.namespaces[key] = schema
+        else:
+            existing.root.children += schema.root.children
+            existing.root.nsprefixes.update(schema.root.nsprefixes)
+        
+    def load(self, options):
+        """
+        Load the schema objects for the root nodes.
+            - de-references schemas
+            - merge schemas
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The merged schema.
+        @rtype: L{Schema}
+        """
+        if options.autoblend:
+            self.autoblend()
+        for child in self.children:
+            child.build()
+        for child in self.children:
+            child.open_imports(options)
+        for child in self.children:
+            child.dereference()
+        log.debug('loaded:\n%s', self)
+        merged = self.merge()
+        log.debug('MERGED:\n%s', merged)
+        return merged
+        
+    def autoblend(self):
+        """
+        Ensure that all schemas within the collection
+        import each other which has a blending effect.
+        @return: self
+        @rtype: L{SchemaCollection}
+        """
+        namespaces = self.namespaces.keys()
+        for s in self.children:
+            for ns in namespaces:
+                tns = s.root.get('targetNamespace')
+                if  tns == ns:
+                    continue
+                for imp in s.root.getChildren('import'):
+                    if imp.get('namespace') == ns:
+                        continue
+                imp = Element('import', ns=Namespace.xsdns)
+                imp.set('namespace', ns)
+                s.root.append(imp)
+        return self
+        
+    def locate(self, ns):
+        """
+        Find a schema by namespace.  Only the URI portion of
+        the namespace is compared to each schema's I{targetNamespace}
+        @param ns: A namespace.
+        @type ns: (prefix,URI)
+        @return: The schema matching the namesapce, else None.
+        @rtype: L{Schema}
+        """
+        return self.namespaces.get(ns[1])
+    
+    def merge(self):
+        """
+        Merge the contained schemas into one.
+        @return: The merged schema.
+        @rtype: L{Schema}
+        """
+        if len(self):
+            schema = self.children[0]
+            for s in self.children[1:]:
+                schema.merge(s)
+            return schema
+        else:
+            return None
+    
+    def __len__(self):
+        return len(self.children)
+    
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        result = ['\nschema collection']
+        for s in self.children:
+            result.append(s.str(1))
+        return '\n'.join(result)
+
+
+class Schema:
+    """
+    The schema is an objectification of a <schema/> (xsd) definition.
+    It provides inspection, lookup and type resolution.
+    @ivar root: The root node.
+    @type root: L{sax.element.Element}
+    @ivar baseurl: The I{base} URL for this schema.
+    @type baseurl: str
+    @ivar container: A schema collection containing this schema.
+    @type container: L{SchemaCollection}
+    @ivar types: A schema types cache.
+    @type types: {name:L{SchemaObject}}
+    @ivar groups: A schema groups cache.
+    @type groups: {name:L{SchemaObject}}
+    @ivar children: A list of direct top level children.
+    @type children: [L{SchemaObject},...]
+    @ivar all: A list of all (includes imported) top level children.
+    @type all: [L{SchemaObject},...]
+    @ivar imports: A list of import objects.
+    @type imports: [L{SchemaObject},...]
+    @ivar form_qualified: The flag indicating:
+        (@elementFormDefault).
+    @type form_qualified: bool
+    """
+    
+    Tag = 'schema'
+    
+    def __init__(self, root, baseurl, options, container=None):
+        """
+        @param root: The xml root.
+        @type root: L{sax.element.Element}
+        @param baseurl: The base url used for importing.
+        @type baseurl: basestring
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @param container: An optional container.
+        @type container: L{SchemaCollection}
+        """
+        self.root = root
+        self.id = objid(self)
+        self.tns = self.mktns()
+        self.baseurl = baseurl
+        self.container = container
+        self.children = []
+        self.all = []
+        self.types = {}
+        self.imports = []
+        self.elements = {}
+        self.attributes = {}
+        self.groups = {}
+        self.agrps = {}
+        if options.doctor is not None:
+            options.doctor.examine(root)
+        form = self.root.get('elementFormDefault')
+        if form is None:
+            self.form_qualified = False
+        else:
+            self.form_qualified = ( form == 'qualified' )
+        if container is None:
+            self.build()
+            self.open_imports(options)
+            log.debug('built:\n%s', self)
+            self.dereference()
+            log.debug('dereferenced:\n%s', self)
+                
+    def mktns(self):
+        """
+        Make the schema's target namespace.
+        @return: The namespace representation of the schema's
+            targetNamespace value.
+        @rtype: (prefix, uri)
+        """
+        tns = [None, self.root.get('targetNamespace')]
+        if tns[1] is not None:
+            tns[0] = self.root.findPrefix(tns[1])
+        return tuple(tns)
+                
+    def build(self):
+        """
+        Build the schema (object graph) using the root node
+        using the factory.
+            - Build the graph.
+            - Collate the children.
+        """
+        self.children = BasicFactory.build(self.root, self)
+        collated = BasicFactory.collate(self.children)
+        self.children = collated[0]
+        self.attributes = collated[2]
+        self.imports = collated[1]
+        self.elements = collated[3]
+        self.types = collated[4]
+        self.groups = collated[5]
+        self.agrps = collated[6]
+        
+    def merge(self, schema):
+        """
+        Merge the contents from the schema.  Only objects not already contained
+        in this schema's collections are merged.  This is to provide for bidirectional
+        import which produce cyclic includes.
+        @returns: self
+        @rtype: L{Schema} 
+        """
+        for item in schema.attributes.items():
+            if item[0] in self.attributes:
+                continue
+            self.all.append(item[1])
+            self.attributes[item[0]] = item[1]
+        for item in schema.elements.items():
+            if item[0] in self.elements:
+                continue
+            self.all.append(item[1])
+            self.elements[item[0]] = item[1]
+        for item in schema.types.items():
+            if item[0] in self.types:
+                continue
+            self.all.append(item[1])
+            self.types[item[0]] = item[1]
+        for item in schema.groups.items():
+            if item[0] in self.groups:
+                continue
+            self.all.append(item[1])
+            self.groups[item[0]] = item[1]
+        for item in schema.agrps.items():
+            if item[0] in self.agrps:
+                continue
+            self.all.append(item[1])
+            self.agrps[item[0]] = item[1]
+        schema.merged = True
+        return self
+        
+    def open_imports(self, options):
+        """
+        Instruct all contained L{sxbasic.Import} children to import
+        the schema's which they reference.  The contents of the
+        imported schema are I{merged} in.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        """
+        for imp in self.imports:
+            imported = imp.open(options)
+            if imported is None:
+                continue
+            imported.open_imports(options)
+            log.debug('imported:\n%s', imported)
+            self.merge(imported)
+            
+    def dereference(self):
+        """
+        Instruct all children to perform dereferencing.
+        """
+        all = []
+        indexes = {}
+        for child in self.children:
+            child.content(all)
+        deplist = DepList()
+        for x in all:
+            x.qualify()
+            midx, deps = x.dependencies()
+            item = (x, tuple(deps))
+            deplist.add(item)
+            indexes[x] = midx
+        for x, deps in deplist.sort():
+            midx = indexes.get(x)
+            if midx is None: continue
+            d = deps[midx]
+            log.debug('(%s) merging %s <== %s', self.tns[1], Repr(x), Repr(d))
+            x.merge(d)
+        
+    def locate(self, ns):
+        """
+        Find a schema by namespace.  Only the URI portion of
+        the namespace is compared to each schema's I{targetNamespace}.
+        The request is passed to the container.
+        @param ns: A namespace.
+        @type ns: (prefix,URI)
+        @return: The schema matching the namesapce, else None.
+        @rtype: L{Schema}
+        """
+        if self.container is not None:
+            return self.container.locate(ns)
+        else:
+            return None
+
+    def custom(self, ref, context=None):
+        """
+        Get whether the specified reference is B{not} an (xs) builtin.
+        @param ref: A str or qref.
+        @type ref: (str|qref)
+        @return: True if B{not} a builtin, else False.
+        @rtype: bool 
+        """
+        if ref is None:
+            return True
+        else:
+            return ( not self.builtin(ref, context) )
+    
+    def builtin(self, ref, context=None):
+        """
+        Get whether the specified reference is an (xs) builtin.
+        @param ref: A str or qref.
+        @type ref: (str|qref)
+        @return: True if builtin, else False.
+        @rtype: bool 
+        """
+        w3 = 'http://www.w3.org'
+        try:
+            if isqref(ref):
+                ns = ref[1]
+                return ( ref[0] in Factory.tags and ns.startswith(w3) )
+            if context is None:
+                context = self.root    
+            prefix = splitPrefix(ref)[0]
+            prefixes = context.findPrefixes(w3, 'startswith')
+            return ( prefix in prefixes and ref[0] in Factory.tags )
+        except:
+            return False
+        
+    def instance(self, root, baseurl, options):
+        """
+        Create and return an new schema object using the
+        specified I{root} and I{url}.
+        @param root: A schema root node.
+        @type root: L{sax.element.Element}
+        @param baseurl: A base URL.
+        @type baseurl: str
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The newly created schema object.
+        @rtype: L{Schema}
+        @note: This is only used by Import children.
+        """
+        return Schema(root, baseurl, options)
+
+    def str(self, indent=0):
+        tab = '%*s'%(indent*3, '')
+        result = []
+        result.append('%s%s' % (tab, self.id))
+        result.append('%s(raw)' % tab)
+        result.append(self.root.str(indent+1))
+        result.append('%s(model)' % tab)
+        for c in self.children:
+            result.append(c.str(indent+1))
+        result.append('')
+        return '\n'.join(result)
+        
+    def __repr__(self):
+        myrep = '<%s tns="%s"/>' % (self.id, self.tns[1])
+        return myrep.encode('utf-8')
+    
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+    
+    def __unicode__(self):
+        return self.str()
+
+
+
diff --git a/suds/xsd/sxbase.py b/suds/xsd/sxbase.py
new file mode 100644
index 0000000..c03f525
--- /dev/null
+++ b/suds/xsd/sxbase.py
@@ -0,0 +1,649 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{sxbase} module provides I{base} classes that represent
+schema objects.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.xsd import *
+from suds.sax.element import Element
+
+log = getLogger(__name__)
+
+
+class SchemaObject(object):
+    """
+    A schema object is an extension to object object with
+    with schema awareness.
+    @ivar root: The XML root element.
+    @type root: L{Element}
+    @ivar schema: The schema containing this object.
+    @type schema: L{schema.Schema}
+    @ivar form_qualified: A flag that inidcates that @elementFormDefault
+        has a value of I{qualified}.
+    @type form_qualified: boolean
+    @ivar nillable: A flag that inidcates that @nillable
+        has a value of I{true}.
+    @type nillable: boolean
+    @ivar default: The default value.
+    @type default: object
+    @ivar rawchildren: A list raw of all children.
+    @type rawchildren: [L{SchemaObject},...]
+    """
+
+    @classmethod
+    def prepend(cls, d, s, filter=Filter()):
+        """
+        Prepend schema object's from B{s}ource list to 
+        the B{d}estination list while applying the filter.
+        @param d: The destination list.
+        @type d: list
+        @param s: The source list.
+        @type s: list
+        @param filter: A filter that allows items to be prepended.
+        @type filter: L{Filter}
+        """
+        i = 0
+        for x in s:
+            if x in filter:
+                d.insert(i, x)
+                i += 1
+    
+    @classmethod
+    def append(cls, d, s, filter=Filter()):
+        """
+        Append schema object's from B{s}ource list to 
+        the B{d}estination list while applying the filter.
+        @param d: The destination list.
+        @type d: list
+        @param s: The source list.
+        @type s: list
+        @param filter: A filter that allows items to be appended.
+        @type filter: L{Filter}
+        """
+        for item in s:
+            if item in filter:
+                d.append(item)
+
+    def __init__(self, schema, root):
+        """
+        @param schema: The containing schema.
+        @type schema: L{schema.Schema}
+        @param root: The xml root node.
+        @type root: L{Element}
+        """
+        self.schema = schema
+        self.root = root
+        self.id = objid(self)
+        self.name = root.get('name')
+        self.qname = (self.name, schema.tns[1])
+        self.min = root.get('minOccurs')
+        self.max = root.get('maxOccurs')
+        self.type = root.get('type')
+        self.ref = root.get('ref')
+        self.form_qualified = schema.form_qualified
+        self.nillable = False
+        self.default = root.get('default')
+        self.rawchildren = []
+        self.cache = {}
+        
+    def attributes(self, filter=Filter()):
+        """
+        Get only the attribute content.
+        @param filter: A filter to constrain the result.
+        @type filter: L{Filter}
+        @return: A list of tuples (attr, ancestry)
+        @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
+        """
+        result = []
+        for child, ancestry in self:
+            if child.isattr() and child in filter:
+                result.append((child, ancestry))
+        return result
+                
+    def children(self, filter=Filter()):
+        """
+        Get only the I{direct} or non-attribute content.
+        @param filter: A filter to constrain the result.
+        @type filter: L{Filter}
+        @return: A list tuples: (child, ancestry)
+        @rtype: [(L{SchemaObject}, [L{SchemaObject},..]),..]
+        """
+        result = []
+        for child, ancestry in self:
+            if not child.isattr() and child in filter:
+                result.append((child, ancestry))
+        return result
+                
+    def get_attribute(self, name):
+        """
+        Get (find) a I{non-attribute} attribute by name.
+        @param name: A attribute name.
+        @type name: str
+        @return: A tuple: the requested (attribute, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        """
+        for child, ancestry in self.attributes():
+            if child.name == name:
+                return (child, ancestry)
+        return (None, [])
+                
+    def get_child(self, name):
+        """
+        Get (find) a I{non-attribute} child by name.
+        @param name: A child name.
+        @type name: str
+        @return: A tuple: the requested (child, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        """
+        for child, ancestry in self.children():
+            if child.any() or child.name == name:
+                return (child, ancestry)
+        return (None, [])
+
+    def namespace(self, prefix=None):
+        """
+        Get this properties namespace
+        @param prefix: The default prefix.
+        @type prefix: str
+        @return: The schema's target namespace
+        @rtype: (I{prefix},I{URI})
+        """
+        ns = self.schema.tns
+        if ns[0] is None:
+            ns = (prefix, ns[1])
+        return ns
+    
+    def default_namespace(self):
+        return self.root.defaultNamespace()
+    
+    def unbounded(self):
+        """
+        Get whether this node is unbounded I{(a collection)}
+        @return: True if unbounded, else False.
+        @rtype: boolean
+        """
+        max = self.max
+        if max is None:
+            max = '1'
+        if max.isdigit():
+            return (int(max) > 1)
+        else:
+            return ( max == 'unbounded' )
+    
+    def optional(self):
+        """
+        Get whether this type is optional.
+        @return: True if optional, else False
+        @rtype: boolean
+        """
+        min = self.min
+        if min is None:
+            min = '1'
+        return ( min == '0' )
+    
+    def required(self):
+        """
+        Get whether this type is required.
+        @return: True if required, else False
+        @rtype: boolean
+        """
+        return ( not self.optional() )
+        
+    
+    def resolve(self, nobuiltin=False):
+        """
+        Resolve and return the nodes true self.
+        @param nobuiltin: Flag indicates that resolution must
+            not continue to include xsd builtins.
+        @return: The resolved (true) type.
+        @rtype: L{SchemaObject}
+        """
+        return self.cache.get(nobuiltin, self)
+    
+    def sequence(self):
+        """
+        Get whether this is an <xs:sequence/>
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def all(self):
+        """
+        Get whether this is an <xs:all/>
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def choice(self):
+        """
+        Get whether this is n <xs:choice/>
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+        
+    def any(self):
+        """
+        Get whether this is an <xs:any/>
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def builtin(self):
+        """
+        Get whether this is a schema-instance (xs) type.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def enum(self):
+        """
+        Get whether this is a simple-type containing an enumeration.
+        @return: True if any, else False
+        @rtype: boolean
+        """
+        return False
+    
+    def isattr(self):
+        """
+        Get whether the object is a schema I{attribute} definition.
+        @return: True if an attribute, else False.
+        @rtype: boolean
+        """
+        return False
+    
+    def extension(self):
+        """
+        Get whether the object is an extension of another type.
+        @return: True if an extension, else False.
+        @rtype: boolean
+        """
+        return False
+    
+    def restriction(self):
+        """
+        Get whether the object is an restriction of another type.
+        @return: True if an restriction, else False.
+        @rtype: boolean
+        """
+        return False
+        
+    def find(self, qref, classes=()):
+        """
+        Find a referenced type in self or children.
+        @param qref: A qualified reference.
+        @type qref: qref
+        @param classes: A list of classes used to qualify the match.
+        @type classes: [I{class},...] 
+        @return: The referenced type.
+        @rtype: L{SchemaObject}
+        @see: L{qualify()}
+        """
+        if not len(classes):
+            classes = (self.__class__,)
+        if self.qname == qref and self.__class__ in classes:
+            return self
+        for c in self.rawchildren:
+            p = c.find(qref, classes)
+            if p is not None:
+                return p
+        return None
+
+    def translate(self, value, topython=True):
+        """
+        Translate a value (type) to/from a python type.
+        @param value: A value to translate.
+        @return: The converted I{language} type.
+        """
+        return value
+    
+    def childtags(self):
+        """
+        Get a list of valid child tag names.
+        @return: A list of child tag names.
+        @rtype: [str,...]
+        """
+        return ()
+    
+    def dependencies(self):
+        """
+        Get a list of dependancies for dereferencing.
+        @return: A merge dependancy index and a list of dependancies.
+        @rtype: (int, [L{SchemaObject},...])
+        """
+        return (None, [])
+    
+    def autoqualified(self):
+        """
+        The list of I{auto} qualified attribute values.
+        Qualification means to convert values into I{qref}.
+        @return: A list of attibute names.
+        @rtype: list
+        """
+        return ['type', 'ref']
+    
+    def qualify(self):
+        """
+        Convert attribute values, that are references to other
+        objects, into I{qref}.
+        """
+        defns = self.root.defaultNamespace()
+        for a in self.autoqualified():
+            ref = getattr(self, a)
+            if ref is None:
+                continue
+            if isqref(ref):
+                continue
+            qref = qualify(ref, self.root, defns)
+            log.debug('%s, convert %s="%s" to %s', self.id, a, ref, qref)
+            setattr(self, a, qref)
+            
+    def merge(self, other):
+        """
+        Merge another object as needed.
+        """
+        other.qualify()
+        for n in ('name',
+                  'qname',
+                  'min',
+                  'max',
+                  'default',
+                  'type',
+                  'nillable',
+                  'form_qualified',):
+            if getattr(self, n) is not None:
+                continue
+            v = getattr(other, n)
+            if v is None:
+                continue
+            setattr(self, n, v)
+            
+            
+    def content(self, collection=None, filter=Filter(), history=None):
+        """
+        Get a I{flattened} list of this nodes contents.
+        @param collection: A list to fill.
+        @type collection: list
+        @param filter: A filter used to constrain the result.
+        @type filter: L{Filter}
+        @param history: The history list used to prevent cyclic dependency.
+        @type history: list
+        @return: The filled list.
+        @rtype: list
+        """
+        if collection is None:
+            collection = []
+        if history is None:
+            history = []
+        if self in history:
+            return collection
+        history.append(self)
+        if self in filter:
+            collection.append(self)
+        for c in self.rawchildren:
+            c.content(collection, filter, history[:])
+        return collection
+    
+    def str(self, indent=0, history=None):
+        """
+        Get a string representation of this object.
+        @param indent: The indent.
+        @type indent: int
+        @return: A string.
+        @rtype: str
+        """
+        if history is None: 
+            history = []
+        if self in history:
+            return '%s ...' % Repr(self)
+        history.append(self)
+        tab = '%*s'%(indent*3, '')
+        result  = []
+        result.append('%s<%s' % (tab, self.id))
+        for n in self.description():
+            if not hasattr(self, n):
+                continue
+            v = getattr(self, n)
+            if v is None:
+                continue
+            result.append(' %s="%s"' % (n, v))
+        if len(self):
+            result.append('>')
+            for c in self.rawchildren:
+                result.append('\n')
+                result.append(c.str(indent+1, history[:]))
+                if c.isattr():
+                    result.append('@')
+            result.append('\n%s' % tab)
+            result.append('</%s>' % self.__class__.__name__)
+        else:
+            result.append(' />')
+        return ''.join(result)
+    
+    def description(self):
+        """
+        Get the names used for str() and repr() description.
+        @return:  A dictionary of relavent attributes.
+        @rtype: [str,...]
+        """
+        return ()
+        
+    def __str__(self):
+        return unicode(self).encode('utf-8')
+            
+    def __unicode__(self):
+        return unicode(self.str())
+    
+    def __repr__(self):
+        s = []
+        s.append('<%s' % self.id)
+        for n in self.description():
+            if not hasattr(self, n):
+                continue
+            v = getattr(self, n)
+            if v is None:
+                continue
+            s.append(' %s="%s"' % (n, v))
+        s.append(' />')
+        myrep = ''.join(s)
+        return myrep.encode('utf-8')
+    
+    def __len__(self):
+        n = 0
+        for x in self: n += 1
+        return n
+    
+    def __iter__(self):
+        return Iter(self)
+    
+    def __getitem__(self, index):
+        i = 0
+        for c in self:
+            if i == index:
+                return c
+
+
+class Iter:
+    """
+    The content iterator - used to iterate the L{Content} children.  The iterator
+    provides a I{view} of the children that is free of container elements
+    such as <sequence/> and <choice/>.
+    @ivar stack: A stack used to control nesting.
+    @type stack: list
+    """
+    
+    class Frame:
+        """ A content iterator frame. """
+        
+        def __init__(self, sx):
+            """
+            @param sx: A schema object.
+            @type sx: L{SchemaObject}
+            """
+            self.sx = sx
+            self.items = sx.rawchildren
+            self.index = 0
+            
+        def next(self):
+            """
+            Get the I{next} item in the frame's collection.
+            @return: The next item or None
+            @rtype: L{SchemaObject}
+            """
+            if self.index < len(self.items):
+                result = self.items[self.index]
+                self.index += 1
+                return result
+    
+    def __init__(self, sx):
+        """
+        @param sx: A schema object.
+        @type sx: L{SchemaObject}
+        """
+        self.stack = []
+        self.push(sx)
+        
+    def push(self, sx):
+        """
+        Create a frame and push the specified object.
+        @param sx: A schema object to push.
+        @type sx: L{SchemaObject}
+        """
+        self.stack.append(Iter.Frame(sx))
+        
+    def pop(self):
+        """
+        Pop the I{top} frame.
+        @return: The popped frame.
+        @rtype: L{Frame}
+        @raise StopIteration: when stack is empty.
+        """
+        if len(self.stack):
+            return self.stack.pop()
+        else:
+            raise StopIteration()
+        
+    def top(self):
+        """
+        Get the I{top} frame.
+        @return: The top frame.
+        @rtype: L{Frame}
+        @raise StopIteration: when stack is empty.
+        """
+        if len(self.stack):
+            return self.stack[-1]
+        else:
+            raise StopIteration()
+    
+    def next(self):
+        """
+        Get the next item.
+        @return: A tuple: the next (child, ancestry).
+        @rtype: (L{SchemaObject}, [L{SchemaObject},..])
+        @raise StopIteration: A the end.
+        """
+        frame = self.top()
+        while True:
+            result = frame.next()
+            if result is None:
+                self.pop()
+                return self.next()
+            if isinstance(result, Content):
+                ancestry = [f.sx for f in self.stack]
+                return (result, ancestry)
+            self.push(result)
+            return self.next()
+    
+    def __iter__(self):
+        return self
+
+
+class XBuiltin(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:*/> node
+    """
+    
+    def __init__(self, schema, name):
+        """
+        @param schema: The containing schema.
+        @type schema: L{schema.Schema}
+        """
+        root = Element(name)
+        SchemaObject.__init__(self, schema, root)
+        self.name = name
+        self.nillable = True
+            
+    def namespace(self, prefix=None):
+        return Namespace.xsdns
+    
+    def builtin(self):
+        return True
+    
+    def resolve(self, nobuiltin=False):
+        return self
+
+
+class Content(SchemaObject):
+    """
+    This class represents those schema objects that represent
+    real XML document content.
+    """
+    pass
+
+    
+class NodeFinder:
+    """
+    Find nodes based on flexable criteria.  The I{matcher} is
+    may be any object that implements a match(n) method.
+    @ivar matcher: An object used as criteria for match.
+    @type matcher: I{any}.match(n)
+    @ivar limit: Limit the number of matches.  0=unlimited.
+    @type limit: int
+    """
+    def __init__(self, matcher, limit=0):
+        """
+        @param matcher: An object used as criteria for match.
+        @type matcher: I{any}.match(n)
+        @param limit: Limit the number of matches.  0=unlimited.
+        @type limit: int
+        """
+        self.matcher = matcher
+        self.limit = limit
+        
+    def find(self, node, list):
+        """
+        Traverse the tree looking for matches.
+        @param node: A node to match on.
+        @type node: L{SchemaObject}
+        @param list: A list to fill.
+        @type list: list
+        """
+        if self.matcher.match(node):
+            list.append(node)
+            self.limit -= 1
+            if self.limit == 0:
+                return
+        for c in node.rawchildren:
+            self.find(c, list)
+        return self
\ No newline at end of file
diff --git a/suds/xsd/sxbasic.py b/suds/xsd/sxbasic.py
new file mode 100644
index 0000000..4c7e8a4
--- /dev/null
+++ b/suds/xsd/sxbasic.py
@@ -0,0 +1,797 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{sxbasic} module provides classes that represent
+I{basic} schema objects.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.xsd import *
+from suds.xsd.sxbase import *
+from suds.xsd.query import *
+from suds.sax import splitPrefix, Namespace
+from suds.transport import TransportError
+from suds.reader import DocumentReader
+from urlparse import urljoin
+
+
+log = getLogger(__name__)
+
+
+class RestrictionMatcher:
+    """
+    For use with L{NodeFinder} to match restriction.
+    """
+    def match(self, n):
+        return isinstance(n, Restriction)
+    
+
+class TypedContent(Content):
+    """
+    Represents any I{typed} content.
+    """
+    def resolve(self, nobuiltin=False):
+        qref = self.qref()
+        if qref is None:
+            return self
+        key = 'resolved:nb=%s' % nobuiltin
+        cached = self.cache.get(key)
+        if cached is not None:
+            return cached
+        result = self
+        query = TypeQuery(qref)
+        query.history = [self]
+        log.debug('%s, resolving: %s\n using:%s', self.id, qref, query)
+        resolved = query.execute(self.schema)
+        if resolved is None:
+            log.debug(self.schema)
+            raise TypeNotFound(qref)
+        self.cache[key] = resolved
+        if resolved.builtin():
+            if nobuiltin:
+                result = self
+            else:
+                result = resolved
+        else:
+            result = resolved.resolve(nobuiltin)
+        return result
+    
+    def qref(self):
+        """
+        Get the I{type} qualified reference to the referenced xsd type.
+        This method takes into account simple types defined through
+        restriction with are detected by determining that self is simple
+        (len=0) and by finding a restriction child.
+        @return: The I{type} qualified reference.
+        @rtype: qref
+        """
+        qref = self.type
+        if qref is None and len(self) == 0:
+            ls = []
+            m = RestrictionMatcher()
+            finder = NodeFinder(m, 1)
+            finder.find(self, ls)
+            if len(ls):
+                return ls[0].ref
+        return qref
+
+
+class Complex(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:complexType/> node.
+    @cvar childtags: A list of valid child node names
+    @type childtags: (I{str},...)
+    """
+        
+    def childtags(self):
+        return (
+            'attribute', 
+            'attributeGroup', 
+            'sequence', 
+            'all', 
+            'choice', 
+            'complexContent',
+            'simpleContent', 
+            'any', 
+            'group')
+
+    def description(self):
+        return ('name',)
+    
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+
+
+class Group(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:group/> node.
+    @cvar childtags: A list of valid child node names
+    @type childtags: (I{str},...)
+    """
+        
+    def childtags(self):
+        return ('sequence', 'all', 'choice')
+        
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:     
+            query = GroupQuery(self.ref)
+            g = query.execute(self.schema)
+            if g is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(g)
+            midx = 0
+        return (midx, deps)
+    
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return ('name', 'ref',)
+    
+
+class AttributeGroup(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:attributeGroup/> node.
+    @cvar childtags: A list of valid child node names
+    @type childtags: (I{str},...)
+    """
+        
+    def childtags(self):
+        return ('attribute', 'attributeGroup')
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = AttrGroupQuery(self.ref)
+            ag = query.execute(self.schema)
+            if ag is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(ag)
+            midx = 0
+        return (midx, deps)
+    
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return ('name', 'ref',)
+    
+
+class Simple(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:simpleType/> node
+    """
+
+    def childtags(self):
+        return ('restriction', 'any',)
+    
+    def enum(self):
+        for child, ancestry in self.children():
+            if isinstance(child, Enumeration):
+                return True
+        return False
+
+    def description(self):
+        return ('name',)
+    
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+    
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+   
+class Restriction(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:restriction/> node
+    """
+    
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ref = root.get('base')
+
+    def childtags(self):
+        return ('enumeration', 'attribute', 'attributeGroup')
+    
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = TypeQuery(self.ref)
+            super = query.execute(self.schema)
+            if super is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            if not super.builtin():
+                deps.append(super)
+                midx = 0
+        return (midx, deps)
+    
+    def restriction(self):
+        return True
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        filter = Filter(False, self.rawchildren)
+        self.prepend(self.rawchildren, other.rawchildren, filter)
+        
+    def description(self):
+        return ('ref',)
+    
+    
+class Collection(SchemaObject):
+    """
+    Represents an (xsd) schema collection node:
+        - sequence
+        - choice
+        - all
+    """
+
+    def childtags(self):
+        return ('element', 'sequence', 'all', 'choice', 'any', 'group')
+
+
+class Sequence(Collection):
+    """
+    Represents an (xsd) schema <xs:sequence/> node.
+    """
+    def sequence(self):
+        return True
+
+
+class All(Collection):
+    """
+    Represents an (xsd) schema <xs:all/> node.
+    """
+    def all(self):
+        return True
+
+class Choice(Collection):
+    """
+    Represents an (xsd) schema <xs:choice/> node.
+    """
+    def choice(self):
+        return True
+
+
+class ComplexContent(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:complexContent/> node.
+    """
+        
+    def childtags(self):
+        return ('attribute', 'attributeGroup', 'extension', 'restriction')
+    
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+    
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+
+class SimpleContent(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:simpleContent/> node.
+    """
+        
+    def childtags(self):
+        return ('extension', 'restriction')
+    
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+    
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+
+
+class Enumeration(Content):
+    """
+    Represents an (xsd) schema <xs:enumeration/> node
+    """
+
+    def __init__(self, schema, root):
+        Content.__init__(self, schema, root)
+        self.name = root.get('value')
+        
+    def enum(self):
+        return True
+
+    
+class Element(TypedContent):
+    """
+    Represents an (xsd) schema <xs:element/> node.
+    """
+    
+    def __init__(self, schema, root):
+        TypedContent.__init__(self, schema, root)
+        a = root.get('form')
+        if a is not None:
+            self.form_qualified = ( a == 'qualified' )
+        a = self.root.get('nillable')
+        if a is not None:
+            self.nillable = ( a in ('1', 'true') )
+        self.implany()
+            
+    def implany(self):
+        """
+        Set the type as any when implicit.
+        An implicit <xs:any/> is when an element has not
+        body and no type defined.
+        @return: self
+        @rtype: L{Element}
+        """
+        if self.type is None and \
+            self.ref is None and \
+            self.root.isempty():
+                self.type = self.anytype()
+        return self
+        
+    def childtags(self):
+        return ('attribute', 'simpleType', 'complexType', 'any',)
+    
+    def extension(self):
+        for c in self.rawchildren:
+            if c.extension():
+                return True
+        return False
+    
+    def restriction(self):
+        for c in self.rawchildren:
+            if c.restriction():
+                return True
+        return False
+    
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = ElementQuery(self.ref)
+            e = query.execute(self.schema)
+            if e is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(e)
+            midx = 0
+        return (midx, deps)
+    
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        self.rawchildren = other.rawchildren
+
+    def description(self):
+        return ('name', 'ref', 'type')
+        
+    def anytype(self):
+        """ create an xsd:anyType reference """
+        p,u = Namespace.xsdns
+        mp = self.root.findPrefix(u)
+        if mp is None:
+            mp = p
+            self.root.addPrefix(p, u)
+        return ':'.join((mp, 'anyType'))
+
+
+class Extension(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:extension/> node.
+    """
+    
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ref = root.get('base')
+        
+    def childtags(self):
+        return ('attribute',
+                'attributeGroup', 
+                'sequence', 
+                'all', 
+                'choice', 
+                'group')
+        
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = TypeQuery(self.ref)
+            super = query.execute(self.schema)
+            if super is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            if not super.builtin():
+                deps.append(super)
+                midx = 0
+        return (midx, deps)
+
+    def merge(self, other):
+        SchemaObject.merge(self, other)
+        filter = Filter(False, self.rawchildren)
+        self.prepend(self.rawchildren, other.rawchildren, filter)
+        
+    def extension(self):
+        return ( self.ref is not None )
+
+    def description(self):
+        return ('ref',)
+
+
+class Import(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:import/> node
+    @cvar locations: A dictionary of namespace locations.
+    @type locations: dict
+    @ivar ns: The imported namespace.
+    @type ns: str
+    @ivar location: The (optional) location.
+    @type location: namespace-uri
+    @ivar opened: Opened and I{imported} flag.
+    @type opened: boolean
+    """
+    
+    locations = {}
+    
+    @classmethod
+    def bind(cls, ns, location=None):
+        """
+        Bind a namespace to a schema location (URI).  
+        This is used for imports that don't specify a schemaLocation.
+        @param ns: A namespace-uri.
+        @type ns: str
+        @param location: The (optional) schema location for the
+            namespace.  (default=ns).
+        @type location: str
+        """
+        if location is None:
+            location = ns
+        cls.locations[ns] = location
+    
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.ns = (None, root.get('namespace'))
+        self.location = root.get('schemaLocation')
+        if self.location is None:
+            self.location = self.locations.get(self.ns[1])
+        self.opened = False
+        
+    def open(self, options):
+        """
+        Open and import the refrenced schema.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The referenced schema.
+        @rtype: L{Schema}
+        """
+        if self.opened:
+            return
+        self.opened = True
+        log.debug('%s, importing ns="%s", location="%s"', self.id, self.ns[1], self.location)
+        result = self.locate()
+        if result is None:
+            if self.location is None:
+                log.debug('imported schema (%s) not-found', self.ns[1])
+            else:
+                result = self.download(options)
+        log.debug('imported:\n%s', result)
+        return result
+    
+    def locate(self):
+        """ find the schema locally """
+        if self.ns[1] == self.schema.tns[1]:
+            return None
+        else:
+            return self.schema.locate(self.ns)
+
+    def download(self, options):
+        """ download the schema """
+        url = self.location
+        try:
+            if '://' not in url:
+                url = urljoin(self.schema.baseurl, url)
+            reader = DocumentReader(options)
+            d = reader.open(url)
+            root = d.root()
+            root.set('url', url)
+            return self.schema.instance(root, url, options)
+        except TransportError:
+            msg = 'imported schema (%s) at (%s), failed' % (self.ns[1], url)
+            log.error('%s, %s', self.id, msg, exc_info=True)
+            raise Exception(msg)
+ 
+    def description(self):
+        return ('ns', 'location')
+    
+
+class Include(SchemaObject):
+    """
+    Represents an (xsd) schema <xs:include/> node
+    @ivar location: The (optional) location.
+    @type location: namespace-uri
+    @ivar opened: Opened and I{imported} flag.
+    @type opened: boolean
+    """
+    
+    locations = {}
+    
+    def __init__(self, schema, root):
+        SchemaObject.__init__(self, schema, root)
+        self.location = root.get('schemaLocation')
+        if self.location is None:
+            self.location = self.locations.get(self.ns[1])
+        self.opened = False
+        
+    def open(self, options):
+        """
+        Open and include the refrenced schema.
+        @param options: An options dictionary.
+        @type options: L{options.Options}
+        @return: The referenced schema.
+        @rtype: L{Schema}
+        """
+        if self.opened:
+            return
+        self.opened = True
+        log.debug('%s, including location="%s"', self.id, self.location)
+        result = self.download(options)
+        log.debug('included:\n%s', result)
+        return result
+
+    def download(self, options):
+        """ download the schema """
+        url = self.location
+        try:
+            if '://' not in url:
+                url = urljoin(self.schema.baseurl, url)
+            reader = DocumentReader(options)
+            d = reader.open(url)
+            root = d.root()
+            root.set('url', url)
+            self.__applytns(root)
+            return self.schema.instance(root, url, options)
+        except TransportError:
+            msg = 'include schema at (%s), failed' % url
+            log.error('%s, %s', self.id, msg, exc_info=True)
+            raise Exception(msg)
+        
+    def __applytns(self, root):
+        """ make sure included schema has same tns. """
+        TNS = 'targetNamespace'
+        tns = root.get(TNS)
+        if tns is None:
+            tns = self.schema.tns[1]
+            root.set(TNS, tns)
+        else:
+            if self.schema.tns[1] != tns:
+                raise Exception, '%s mismatch' % TNS
+                
+ 
+    def description(self):
+        return ('location')
+
+   
+class Attribute(TypedContent):
+    """
+    Represents an (xsd) <attribute/> node
+    """
+
+    def __init__(self, schema, root):
+        TypedContent.__init__(self, schema, root)
+        self.use = root.get('use', default='')
+        
+    def childtags(self):
+        return ('restriction',)
+        
+    def isattr(self):
+        return True
+
+    def get_default(self):
+        """
+        Gets the <xs:attribute default=""/> attribute value.
+        @return: The default value for the attribute
+        @rtype: str
+        """
+        return self.root.get('default', default='')
+    
+    def optional(self):
+        return ( self.use != 'required' )
+
+    def dependencies(self):
+        deps = []
+        midx = None
+        if self.ref is not None:
+            query = AttrQuery(self.ref)
+            a = query.execute(self.schema)
+            if a is None:
+                log.debug(self.schema)
+                raise TypeNotFound(self.ref)
+            deps.append(a)
+            midx = 0
+        return (midx, deps)
+    
+    def description(self):
+        return ('name', 'ref', 'type')
+
+
+class Any(Content):
+    """
+    Represents an (xsd) <any/> node
+    """
+
+    def get_child(self, name):
+        root = self.root.clone()
+        root.set('note', 'synthesized (any) child')
+        child = Any(self.schema, root)
+        return (child, [])
+    
+    def get_attribute(self, name):
+        root = self.root.clone()
+        root.set('note', 'synthesized (any) attribute')
+        attribute = Any(self.schema, root)
+        return (attribute, [])
+    
+    def any(self):
+        return True
+    
+    
+class Factory:
+    """
+    @cvar tags: A factory to create object objects based on tag.
+    @type tags: {tag:fn,}
+    """
+
+    tags =\
+    {
+        'import' : Import,
+        'include' : Include, 
+        'complexType' : Complex,
+        'group' : Group,
+        'attributeGroup' : AttributeGroup, 
+        'simpleType' : Simple, 
+        'element' : Element,
+        'attribute' : Attribute,
+        'sequence' : Sequence,
+        'all' : All,
+        'choice' : Choice,
+        'complexContent' : ComplexContent,
+        'simpleContent' : SimpleContent,
+        'restriction' : Restriction,
+        'enumeration' : Enumeration,
+        'extension' : Extension,
+        'any' : Any,
+    }
+    
+    @classmethod
+    def maptag(cls, tag, fn):
+        """
+        Map (override) tag => I{class} mapping.
+        @param tag: An xsd tag name.
+        @type tag: str
+        @param fn: A function or class.
+        @type fn: fn|class.
+        """
+        cls.tags[tag] = fn
+    
+    @classmethod
+    def create(cls, root, schema):
+        """
+        Create an object based on the root tag name.
+        @param root: An XML root element.
+        @type root: L{Element}
+        @param schema: A schema object.
+        @type schema: L{schema.Schema}
+        @return: The created object.
+        @rtype: L{SchemaObject} 
+        """
+        fn = cls.tags.get(root.name)
+        if fn is not None:
+            return fn(schema, root)
+        else:
+            return None
+
+    @classmethod
+    def build(cls, root, schema, filter=('*',)):
+        """
+        Build an xsobject representation.
+        @param root: An schema XML root.
+        @type root: L{sax.element.Element}
+        @param filter: A tag filter.
+        @type filter: [str,...]
+        @return: A schema object graph.
+        @rtype: L{sxbase.SchemaObject}
+        """
+        children = []
+        for node in root.getChildren(ns=Namespace.xsdns):
+            if '*' in filter or node.name in filter:
+                child = cls.create(node, schema)
+                if child is None:
+                    continue
+                children.append(child)
+                c = cls.build(node, schema, child.childtags())
+                child.rawchildren = c
+        return children
+    
+    @classmethod
+    def collate(cls, children):
+        imports = []
+        elements = {}
+        attributes = {}
+        types = {}
+        groups = {}
+        agrps = {}
+        for c in children:
+            if isinstance(c, (Import, Include)):
+                imports.append(c)
+                continue
+            if isinstance(c, Attribute):
+                attributes[c.qname] = c
+                continue
+            if isinstance(c, Element):
+                elements[c.qname] = c
+                continue
+            if isinstance(c, Group):
+                groups[c.qname] = c
+                continue
+            if isinstance(c, AttributeGroup):
+                agrps[c.qname] = c
+                continue
+            types[c.qname] = c
+        for i in imports:
+            children.remove(i)
+        return (children, imports, attributes, elements, types, groups, agrps)
+
+    
+
+
+#######################################################
+# Static Import Bindings :-(
+#######################################################
+Import.bind(
+    'http://schemas.xmlsoap.org/soap/encoding/',
+    'suds://schemas.xmlsoap.org/soap/encoding/')
+Import.bind(
+    'http://www.w3.org/XML/1998/namespace',
+    'http://www.w3.org/2001/xml.xsd')
+Import.bind(
+    'http://www.w3.org/2001/XMLSchema',
+    'http://www.w3.org/2001/XMLSchema.xsd')
diff --git a/suds/xsd/sxbuiltin.py b/suds/xsd/sxbuiltin.py
new file mode 100644
index 0000000..f8cf428
--- /dev/null
+++ b/suds/xsd/sxbuiltin.py
@@ -0,0 +1,274 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+"""
+The I{sxbuiltin} module provides classes that represent
+XSD I{builtin} schema objects.
+"""
+
+from logging import getLogger
+from suds import *
+from suds.xsd import *
+from suds.sax.date import *
+from suds.xsd.sxbase import XBuiltin
+import datetime as dt
+
+
+log = getLogger(__name__)
+    
+    
+class XString(XBuiltin):
+    """
+    Represents an (xsd) <xs:string/> node
+    """
+    pass
+
+  
+class XAny(XBuiltin):
+    """
+    Represents an (xsd) <any/> node
+    """
+    
+    def __init__(self, schema, name):
+        XBuiltin.__init__(self, schema, name)
+        self.nillable = False
+    
+    def get_child(self, name):
+        child = XAny(self.schema, name)
+        return (child, [])
+    
+    def any(self):
+        return True
+
+
+class XBoolean(XBuiltin):
+    """
+    Represents an (xsd) boolean builtin type.
+    """
+    
+    translation = (
+        { '1':True,'true':True,'0':False,'false':False },
+        { True:'true',1:'true',False:'false',0:'false' },
+    )
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring):
+                return XBoolean.translation[0].get(value)
+            else:
+                return None
+        else:
+            if isinstance(value, (bool,int)):
+                return XBoolean.translation[1].get(value)
+            else:
+                return value
+
+   
+class XInteger(XBuiltin):
+    """
+    Represents an (xsd) xs:int builtin type.
+    """
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return int(value)
+            else:
+                return None
+        else:
+            if isinstance(value, int):
+                return str(value)
+            else:
+                return value
+            
+class XLong(XBuiltin):
+    """
+    Represents an (xsd) xs:long builtin type.
+    """
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return long(value)
+            else:
+                return None
+        else:
+            if isinstance(value, (int,long)):
+                return str(value)
+            else:
+                return value
+
+       
+class XFloat(XBuiltin):
+    """
+    Represents an (xsd) xs:float builtin type.
+    """
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return float(value)
+            else:
+                return None
+        else:
+            if isinstance(value, float):
+                return str(value)
+            else:
+                return value
+            
+
+class XDate(XBuiltin):
+    """
+    Represents an (xsd) xs:date builtin type.
+    """
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return Date(value).date
+            else:
+                return None
+        else:
+            if isinstance(value, dt.date):
+                return str(Date(value))
+            else:
+                return value
+
+
+class XTime(XBuiltin):
+    """
+    Represents an (xsd) xs:time builtin type.
+    """
+        
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return Time(value).time
+            else:
+                return None
+        else:
+            if isinstance(value, dt.date):
+                return str(Time(value))
+            else:
+                return value
+
+
+class XDateTime(XBuiltin):
+    """
+    Represents an (xsd) xs:datetime builtin type.
+    """
+
+    def translate(self, value, topython=True):
+        if topython:
+            if isinstance(value, basestring) and len(value):
+                return DateTime(value).datetime
+            else:
+                return None
+        else:
+            if isinstance(value, dt.date):
+                return str(DateTime(value))
+            else:
+                return value
+            
+            
+class Factory:
+
+    tags =\
+    {
+        # any
+        'anyType' : XAny,
+        # strings
+        'string' : XString,
+        'normalizedString' : XString,
+        'ID' : XString,
+        'Name' : XString,
+        'QName' : XString,
+        'NCName' : XString,
+        'anySimpleType' : XString,
+        'anyURI' : XString,
+        'NOTATION' : XString,
+        'token' : XString,
+        'language' : XString,
+        'IDREFS' : XString,
+        'ENTITIES' : XString,
+        'IDREF' : XString,
+        'ENTITY' : XString,
+        'NMTOKEN' : XString,
+        'NMTOKENS' : XString,
+        # binary
+        'hexBinary' : XString,
+        'base64Binary' : XString,
+        # integers
+        'int' : XInteger,
+        'integer' : XInteger,
+        'unsignedInt' : XInteger,
+        'positiveInteger' : XInteger,
+        'negativeInteger' : XInteger,
+        'nonPositiveInteger' : XInteger,
+        'nonNegativeInteger' : XInteger,
+        # longs
+        'long' : XLong,
+        'unsignedLong' : XLong,
+        # shorts
+        'short' : XInteger,
+        'unsignedShort' : XInteger,
+        'byte' : XInteger,
+        'unsignedByte' : XInteger,
+        # floats
+        'float' : XFloat,
+        'double' : XFloat,
+        'decimal' : XFloat,
+        # dates & times
+        'date' : XDate,
+        'time' : XTime,
+        'dateTime': XDateTime,
+        'duration': XString,
+        'gYearMonth' : XString,
+        'gYear' : XString,
+        'gMonthDay' : XString,
+        'gDay' : XString,
+        'gMonth' : XString,
+        # boolean
+        'boolean' : XBoolean,
+    }
+    
+    @classmethod
+    def maptag(cls, tag, fn):
+        """
+        Map (override) tag => I{class} mapping.
+        @param tag: An xsd tag name.
+        @type tag: str
+        @param fn: A function or class.
+        @type fn: fn|class.
+        """
+        cls.tags[tag] = fn
+
+    @classmethod
+    def create(cls, schema, name):
+        """
+        Create an object based on the root tag name.
+        @param schema: A schema object.
+        @type schema: L{schema.Schema}
+        @param name: The name.
+        @type name: str
+        @return: The created object.
+        @rtype: L{XBuiltin} 
+        """
+        fn = cls.tags.get(name)
+        if fn is not None:
+            return fn(schema, name)
+        else:
+            return XBuiltin(schema, name)
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..5aad187
--- /dev/null
+++ b/tests/__init__.py
@@ -0,0 +1,25 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+import logging
+
+def setup_logging():
+    if sys.version_info < (2, 5):
+        fmt = '%(asctime)s [%(levelname)s] @%(filename)s:%(lineno)d\n%(message)s\n'
+    else:
+        fmt = '%(asctime)s [%(levelname)s] %(funcName)s() @%(filename)s:%(lineno)d\n%(message)s\n'
+    logging.basicConfig(level=logging.INFO, format=fmt)
\ No newline at end of file
diff --git a/tests/axis1.py b/tests/axis1.py
new file mode 100644
index 0000000..d06823d
--- /dev/null
+++ b/tests/axis1.py
@@ -0,0 +1,303 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+#
+# This test requires installation or visability to my local axis(1) server.
+#
+
+import sys
+sys.path.append('../')
+
+import logging
+import traceback as tb
+import suds.metrics as metrics
+from tests import *
+from suds import WebFault
+from suds.client import Client
+from suds.sudsobject import Object
+from suds.transport.https import HttpAuthenticated
+
+errors = 0
+
+credentials = dict(username='jortel', password='abc123')
+
+setup_logging()
+
+
+#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+
+def start(url):
+    global errors
+    print '\n________________________________________________________________\n' 
+    print 'Test @ ( %s )\nerrors = %d\n' % (url, errors)
+
+try:
+    url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl'
+    start(url)
+    t = HttpAuthenticated(**credentials)
+    client = Client(url, transport=t, cache=None)
+    print client
+    #
+    # create a name object using the wsdl
+    #
+    print 'create name'
+    name = client.factory.create('ns0:Name')
+    name.first = u'jeff'+unichr(1234)
+    name.last = 'ortel'
+    print name
+    #
+    # create a phone object using the wsdl
+    #
+    print 'create phone'
+    phoneA = client.factory.create('ns0:Phone')
+    phoneA.npa = 410
+    phoneA.nxx = 555
+    phoneA.number = 5138
+    phoneB = client.factory.create('ns0:Phone')
+    phoneB.npa = 919
+    phoneB.nxx = 555
+    phoneB.number = 4406
+    phoneC = {
+        'npa':205,
+        'nxx':777,
+        'number':1212
+    }
+    #
+    # create a dog
+    #
+    dog = client.factory.create('ns0:Dog')
+    dog.name = 'Chance'
+    dog.trained = True
+    #
+    # create a person object using the wsdl
+    #
+    person = client.factory.create('ns0:Person')
+    print '{empty} person=\n%s' % person
+    person.name = name
+    person.age = 43
+    person.phone = [phoneA,phoneB,phoneC]
+    person.pets = [dog]
+    print 'person=\n%s' % person
+    #
+    # add the person (using the webservice)
+    #
+    print 'addPersion()'
+    result = client.service.addPerson(person)
+    print '\nreply(\n%s\n)\n' % str(result)
+    #
+    # create a new name object used to update the person
+    #
+    newname = client.factory.create('ns0:Name')
+    newname.first = 'Todd'
+    newname.last = None
+    #
+    # create AnotherPerson using Person
+    #
+    ap = client.factory.create('ns0:AnotherPerson')
+    ap.name = person.name
+    ap.age = person.age
+    ap.phone = person.phone
+    ap.pets = person.pets
+    print 'AnotherPerson\n%s' % ap
+    #
+    # update the person's name (using the webservice)
+    #
+    print 'updatePersion()'
+    result = client.service.updatePerson(ap, newname)
+    print '\nreply(\n%s\n)\n' % str(result)
+    result = client.service.updatePerson(ap, None)
+    print '\nreply(\n%s\n)\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl'
+    start(url)
+    t = HttpAuthenticated(**credentials)
+    client = Client(url, transport=t, cache=None)
+    print client
+    #
+    # create a name object as dict
+    #
+    print 'create name'
+    name = {}
+    name['first'] = 'Elmer'
+    name['last'] = 'Fudd'
+    print name
+    #
+    # create a phone as dict
+    #
+    print 'create phone'
+    phoneA = {}
+    phoneA['npa'] = 410
+    phoneA['nxx'] = 555
+    phoneA['number'] = 5138
+    phoneB = {}
+    phoneB['npa'] = 919
+    phoneB['nxx'] = 555
+    phoneB['number'] = 4406
+    phoneC = {
+        'npa':205,
+        'nxx':777,
+        'number':1212
+    }
+    #
+    # create a dog
+    #
+    dog = {
+        'name':'Chance',
+        'trained':True,
+    }
+    #
+    # create a person as dict
+    #
+    person = {}
+    print '{empty} person=\n%s' % person
+    person['name'] = name
+    person['age'] = 43
+    person['phone'] = [phoneA,phoneB, phoneC]
+    person['pets'] = [dog]
+    print 'person=\n%s' % person
+    #
+    # add the person (using the webservice)
+    #
+    print 'addPersion()'
+    result = client.service.addPerson(person)
+    print '\nreply(\n%s\n)\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    print "echo(' this is cool ')"
+    result = client.service.echo('this is cool')
+    print '\nreply( "%s" )\n' % str(result)
+    print 'echo(None)'
+    result = client.service.echo(None)
+    print '\nreply( "%s" )\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    print 'hello()'
+    result = client.service.hello()
+    print '\nreply( %s )\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    print 'testVoid()'
+    result = client.service.getVoid()
+    print '\nreply( %s )\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    print '** new style arrays **'
+    words = ['my', 'dog', 'likes', 'steak']
+    result = client.service.printList(words)
+    print '\nreply( %s )\n' % str(result)
+
+    print '** old style arrays **'
+    array = client.factory.create('ArrayOf_xsd_string')
+    print 'ArrayOf_xsd_string=\n%s' % array
+    array.item = ['my', 'dog', 'likes', 'steak']
+    result = client.service.printList(array)
+    print '\nreply( %s )\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    s = 'hello'
+    for n in range(0, 3):
+        print 'getList(%s, %d)' % (s, n)
+        result = client.service.getList(s, n)
+        print '\nreply( %s )\n' % str(result)
+        assert ( isinstance(result, list) and len(result) == n )
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    print 'testExceptions()' 
+    result = client.service.throwException()
+    print '\nreply( %s )\n' % tostr(result)
+    raise Exception('Fault expected and not raised')
+except WebFault, f:
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = 'http://localhost:8081/axis/services/basic-rpc-encoded?wsdl'
+    start(url)
+    client = Client(url, faults=False, **credentials)
+    print 'testExceptions()'
+    result = client.service.throwException()
+    print '\nreply( %s )\n' % str(result)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+print '\nFinished: errors=%d' % errors
diff --git a/tests/axis2.py b/tests/axis2.py
new file mode 100644
index 0000000..8ed0bdd
--- /dev/null
+++ b/tests/axis2.py
@@ -0,0 +1,215 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+sys.path.append('../')
+
+import logging
+import traceback as tb
+import suds.metrics as metrics
+from tests import *
+from suds import *
+from suds.client import Client
+from datetime import datetime
+
+errors = 0
+
+setup_logging()
+
+#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+
+url = 'http://localhost:8080/axis2/services/BasicService?wsdl'
+    
+print 'url=%s' % url
+
+#
+# create a service client using the wsdl.
+#
+client = Client(url)
+
+#
+# print the service (introspection)
+#
+print client
+
+print 'printList()'
+print client.service.printList(['a','b'])
+
+#
+# create a name object using the wsdl
+#
+print 'create name'
+name = client.factory.create('ns2:Name')
+name.first = u'jeff'+unichr(1234)
+name.last = 'ortel'
+
+print name
+
+#
+# create a phone object using the wsdl
+#
+print 'create phone'
+phoneA = client.factory.create('ns2:Phone')
+phoneA.npa = 410
+phoneA.nxx = 822
+phoneA.number = 5138
+
+phoneB = client.factory.create('ns2:Phone')
+phoneB.npa = 919
+phoneB.nxx = 606
+phoneB.number = 4406
+
+#
+# create a dog
+#
+dog = client.factory.create('ns2:Dog')
+print dog
+dog.name = 'Chance'
+dog.trained = True
+print dog
+
+#
+# create a person object using the wsdl
+#
+person = client.factory.create('ns2:Person')
+
+#
+# inspect empty person
+#
+print '{empty} person=\n%s' % person
+
+person.name = name
+person.age = None
+person.birthday = datetime.now()
+person.phone.append(phoneA)
+person.phone.append(phoneB)
+person.pets.append(dog)
+
+#
+# inspect person
+#
+print 'person=\n%s' % person
+
+#
+# add the person (using the webservice)
+#
+print 'addPersion()'
+result = client.service.addPerson(person)
+print '\nreply(\n%s\n)\n' % result.encode('utf-8')
+
+#
+# create a new name object used to update the person
+#
+newname = client.factory.create('ns2:Name')
+newname.first = 'Todd'
+newname.last = None
+
+#
+# update the person's name (using the webservice) and print return person object
+#
+print 'updatePersion()'
+result = client.service.updatePerson(person, newname)
+print '\nreply(\n%s\n)\n' % str(result)
+result = client.service.updatePerson(person, None)
+print '\nreply(\n%s\n)\n' % str(result)
+
+
+#
+# invoke the echo service
+#
+print 'echo()'
+client.service.echo(None)
+result = client.service.echo('this is cool')
+print '\nreply( %s )\n' % str(result)
+
+print 'echo() with {none}'
+result = client.service.echo(None)
+print '\nreply( %s )\n' % str(result)
+
+#
+# invoke the hello service
+#
+print 'hello()'
+result = client.service.hello()
+print '\nreply( %s )\n' % str(result)
+
+#
+# invoke the testVoid service
+#
+try:
+    print 'getVoid()'
+    result = client.service.getVoid()
+    print '\nreply( %s )\n' % str(result)
+except Exception, e:
+    print e
+
+#
+# test list args
+#
+print 'getList(list)'
+mylist = ['my', 'dog', 'likes', 'steak']
+result = client.service.printList(mylist)
+print '\nreply( %s )\n' % str(result)
+# tuple
+print 'testListArgs(tuple)'
+mylist = ('my', 'dog', 'likes', 'steak')
+result = client.service.printList(mylist)
+print '\nreply( %s )\n' % str(result)
+
+#
+# test list returned
+#
+for n in range(0, 3):
+    print 'getList(str, %d)' % n
+    result = client.service.getList('hello', n)
+    print '\nreply( %s )\n' % str(result)
+    assert ( isinstance(result, list) and len(result) == n )
+
+print 'addPet()'
+dog = client.factory.create('ns2:Dog')
+dog.name = 'Chance'
+dog.trained = True
+print dog
+try:
+    result = client.service.addPet(person, dog)
+    print '\nreply( %s )\n' % str(result)
+except Exception, e:
+    print e
+
+print '___________________ E X C E P T I O N S __________________________'
+
+#
+# test exceptions
+#
+try:
+    print 'throwException() faults=True'
+    result = client.service.throwException()
+    print '\nreply( %s )\n' % tostr(result)
+except Exception, e:
+    print e
+    
+#
+# test faults
+#
+try:
+    print 'throwException() faults=False'
+    client.set_options(faults=False)
+    result = client.service.throwException()
+    print '\nreply( %s )\n' % tostr(result)
+except Exception, e:
+    print e
+
+print '\nfinished: errors=%d' % errors
\ No newline at end of file
diff --git a/tests/builtin.py b/tests/builtin.py
new file mode 100644
index 0000000..31ca3c3
--- /dev/null
+++ b/tests/builtin.py
@@ -0,0 +1,548 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+sys.path.append('../')
+from suds.sax.date import Timezone as Tz
+from suds.xsd.sxbuiltin import *
+from unittest import TestCase
+from tests import *
+
+setup_logging()
+
+
+class Date(XDate):
+    def __init__(self):
+        pass
+class Time(XTime):
+    def __init__(self):
+        pass
+class DateTime(XDateTime):
+    def __init__(self):
+        pass
+    
+class DateTest(TestCase):
+    
+    def testSimple(self):
+        ref = dt.date(1941, 12, 7)
+        s = '%.4d-%.2d-%.2d' % (ref.year, ref.month, ref.day)
+        xdate = Date()
+        d = xdate.translate(s)
+        self.assertEqual(d, ref)
+        
+    def testNegativeTimezone(self):
+        self.equalsTimezone(-6)
+        
+    def testPositiveTimezone(self):
+        self.equalsTimezone(6)
+        
+    def testUtcTimezone(self):
+        Timezone.local = 0
+        ref = dt.date(1941, 12, 7)
+        s = '%.4d-%.2d-%.2dZ' % (ref.year, ref.month, ref.day)
+        xdate = Date()
+        d = xdate.translate(s)
+        self.assertEqual(d, ref)
+        
+    def equalsTimezone(self, tz):
+        Timezone.local = tz
+        ref = dt.date(1941, 12, 7)
+        s = '%.4d-%.2d-%.2d%+.2d:00' % (ref.year, ref.month, ref.day, tz)
+        xdate = Date()
+        d = xdate.translate(s)
+        self.assertEqual(d, ref)
+
+
+  
+class TimeTest(TestCase):
+
+    def testSimple(self):
+        ref = dt.time(10, 30, 22)
+        s = '%.2d:%.2d:%.2d' % (ref.hour, ref.minute, ref.second)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testSimpleWithShortMicrosecond(self):
+        ref = dt.time(10, 30, 22, 34)
+        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, ref.microsecond)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testSimpleWithMicrosecond(self):
+        ref = dt.time(10, 30, 22, 999999)
+        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, ref.microsecond)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testSimpleWithLongMicrosecond(self):
+        ref = dt.time(10, 30, 22, 999999)
+        s = '%.2d:%.2d:%.2d.%4.d' % (ref.hour, ref.minute, ref.second, int('999999999'))
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testPositiveTimezone(self):
+        self.equalsTimezone(6)
+        
+    def testNegativeTimezone(self):
+        self.equalsTimezone(-6)
+        
+    def testUtcTimezone(self):
+        Timezone.local = 0
+        ref = dt.time(10, 30, 22)
+        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def equalsTimezone(self, tz):
+        Timezone.local = tz
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, tz)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testConvertNegativeToGreaterNegative(self):
+        Timezone.local = -6
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, -5)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour-1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToLesserNegative(self):
+        Timezone.local = -5
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour+1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToGreaterPositive(self):
+        Timezone.local = 3
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, 2)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour+1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToLesserPositive(self):
+        Timezone.local = 2
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour-1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToNegative(self):
+        Timezone.local = -6
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour-9, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToPositive(self):
+        Timezone.local = 3
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour+9, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToUtc(self):
+        Timezone.local = 0
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, -6)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour+6, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToUtc(self):
+        Timezone.local = 0
+        ref = dt.time(10, 30, 22)
+        s = self.strTime(ref.hour, ref.minute, ref.second, 3)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour-3, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertUtcToPositive(self):
+        Timezone.local = 3
+        ref = dt.time(10, 30, 22)
+        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour+3, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertUtcToNegative(self):
+        Timezone.local = -6
+        ref = dt.time(10, 30, 22)
+        s = '%.2d:%.2d:%.2dZ' % (ref.hour, ref.minute, ref.second)
+        xtime = Time()
+        t = xtime.translate(s)
+        self.assertEqual(ref.hour-6, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def strTime(self, h, m, s, offset):
+        return '%.2d:%.2d:%.2d%+.2d:00' % (h, m, s, offset)
+
+
+class DateTimeTest(TestCase):
+
+    def testSimple(self):
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d' \
+            % (ref.year,
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testOverflow(self):
+        ref = dt.datetime(1, 1, 1, 0, 0, 0)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
+            % (ref.year,
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testSimpleWithMicrosecond(self):
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22, 454)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d.%.4d' \
+            % (ref.year, 
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second, 
+               ref.microsecond)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testPositiveTimezone(self):
+        self.equalsTimezone(6)
+        
+    def testNegativeTimezone(self):
+        self.equalsTimezone(-6)
+        
+    def testUtcTimezone(self):
+        Timezone.local = 0
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d' \
+            % (ref.year,
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(t, ref)
+        
+    def equalsTimezone(self, tz):
+        Timezone.local = tz
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                tz)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(t, ref)
+        
+    def testConvertNegativeToGreaterNegative(self):
+        Timezone.local = -6
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -5)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour-1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToLesserNegative(self):
+        Timezone.local = -5
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -6)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour+1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToGreaterPositive(self):
+        Timezone.local = 3
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                2)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour+1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToLesserPositive(self):
+        Timezone.local = 2
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                3)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour-1, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToNegative(self):
+        Timezone.local = -6
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                3)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour-9, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToPositive(self):
+        Timezone.local = 3
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -6)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour+9, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToUtc(self):
+        Timezone.local = 0
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -6)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour+6, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertPositiveToUtc(self):
+        Timezone.local = 0
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                3)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour-3, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertUtcToPositive(self):
+        Timezone.local = 3
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
+            % (ref.year,
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour+3, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertUtcToNegative(self):
+        Timezone.local = -6
+        ref = dt.datetime(1941, 12, 7, 10, 30, 22)
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2dZ' \
+            % (ref.year,
+               ref.month, 
+               ref.day, 
+               ref.hour, 
+               ref.minute, 
+               ref.second)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(ref.day, t.day)
+        self.assertEqual(ref.hour-6, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToGreaterNegativeAndPreviousDay(self):
+        Timezone.local = -6
+        ref = dt.datetime(1941, 12, 7, 0, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -5)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(6, t.day)
+        self.assertEqual(23, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def testConvertNegativeToLesserNegativeAndNextDay(self):
+        Timezone.local = -5
+        ref = dt.datetime(1941, 12, 7, 23, 30, 22)
+        s = self.strDateTime(
+                ref.year,
+                ref.month,
+                ref.day,
+                ref.hour,
+                ref.minute,
+                ref.second,
+                -6)
+        xdt = DateTime()
+        t = xdt.translate(s)
+        self.assertEqual(ref.year, t.year)
+        self.assertEqual(ref.month, t.month)
+        self.assertEqual(8, t.day)
+        self.assertEqual(0, t.hour)
+        self.assertEqual(ref.minute, t.minute)
+        self.assertEqual(ref.second, t.second)
+        
+    def strDateTime(self, Y, M, D, h, m, s, offset):
+        s = '%.4d-%.2d-%.2dT%.2d:%.2d:%.2d%+.2d:00' \
+            % (Y, M, D, h, m, s, offset)
+        return s
+
+        
+if __name__ == '__main__':
+    unittest.main()
diff --git a/tests/jasper.py b/tests/jasper.py
new file mode 100644
index 0000000..79389a1
--- /dev/null
+++ b/tests/jasper.py
@@ -0,0 +1,54 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+sys.path.append('../')
+
+import logging
+import traceback as tb
+import urllib2
+import suds.metrics as metrics
+import traceback as tb
+from tests import *
+from suds import WebFault
+from suds.client import Client
+
+errors = 0
+
+setup_logging()
+
+#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+
+def start(url):
+    print '\n________________________________________________________________\n' 
+    print 'Test @ ( %s )' % url
+    
+try:
+    url = 'http://localhost:9090/jasperserver-pro/services/repository?wsdl'
+    start(url)
+    client = Client(url, username='jeff', password='ortel')
+    print client
+    print client.service.list('')
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+print '\nFinished: errors = %d' % errors
diff --git a/tests/public.py b/tests/public.py
new file mode 100644
index 0000000..ca39cc5
--- /dev/null
+++ b/tests/public.py
@@ -0,0 +1,289 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+import sys
+sys.path.append('../')
+
+import logging
+import traceback as tb
+import suds.metrics as metrics
+from tests import *
+from suds import WebFault
+from suds.client import Client
+
+errors = 0
+
+setup_logging()
+
+#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+#logging.getLogger('suds.metrics').setLevel(logging.DEBUG)
+#logging.getLogger('suds').setLevel(logging.DEBUG)
+
+
+def start(url):
+    global errors
+    print '\n________________________________________________________________\n' 
+    print 'Test @ ( %s ) %d' % (url, errors)
+
+try:
+    url = 'http://mssoapinterop.org/asmx/simple.asmx?WSDL'
+    start(url)
+    client = Client(url)
+    print client
+    # string
+    input = "42"
+    d = dict(inputString=input)
+    result = client.service.echoString(**d)
+    print 'echoString() =  %s' % result
+    assert result == input
+    # int
+    input = 42
+    result = client.service.echoInteger(input)
+    print 'echoInteger() = %s' % result
+    assert result == input
+    # float
+    input = 4.2
+    result = client.service.echoFloat(input)
+    print 'echoFloat() = %s' % result
+    assert result == input
+    # suds 0.3.8+
+    result = client.service.echoIntegerArray([])
+    print 'echoIntegerArray() = %s' % result
+    assert result is None
+    input = [1,2,3,4]
+    result = client.service.echoIntegerArray(input)
+    print 'echoIntegerArray() = %s' % result
+    assert result == input
+    result = client.service.echoIntegerArray(inputIntegerArray=input)
+    print 'echoIntegerArray() = %s' % result
+    assert result == input
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e: 
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = 'http://jira.atlassian.com/rpc/soap/jirasoapservice-v2?wsdl'
+    start(url)
+    client = Client(url)
+    print client
+    token = client.service.login('soaptester', 'soaptester')
+    print 'token="%s"' % token
+    user = client.service.getUser(token, 'soaptester')
+    print 'user="%s"' % user
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e: 
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = 'http://jira.atlassian.com/rpc/soap/jirasoapservice-v2?wsdl'
+    start(url+'  ** cloned **')
+    client = Client(url).clone()
+    print client
+    token = client.service.login('soaptester', 'soaptester')
+    print 'token="%s"' % token
+    user = client.service.getUser(token, 'soaptester')
+    print 'user="%s"' % user
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e: 
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = ' http://www.boyzoid.com/comp/randomQuote.cfc?wsdl '
+    start(url)
+    client = Client(url)
+    print client
+    print client.service.getQuote(False)
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = 'http://www.zenfolio.com/zf/api/zfapi.asmx?wsdl'
+    start(url)
+    client = Client(url)
+    print client
+    #client.setport(0)
+    group = client.factory.create('Group')
+    print 'Group:\n%s' % group
+    print 'LoadGroupHierarchy("demo")'
+    groupHierarchy = client.service.LoadGroupHierarchy("demo")
+    print 'result:\n%s' % groupHierarchy
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = 'http://cert.synxis.com/interface/ChannelConnect.asmx?WSDL'
+    start(url)
+    client = Client(url)
+    print client
+    #client.setport(0)
+    tpa = client.factory.create('ns1:TPA_Extensions')
+    print client.service.Ping(tpa, "hello")
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = 'https://sec.neurofuzz-software.com/paos/genSSHA-SOAP.php?wsdl'
+    start(url)
+    client = Client(url)
+    print client
+    print client.service.genSSHA('hello', 'sha1')
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = 'http://ap1314-dsr.compmed.ucdavis.edu/dataserver/Aperio.Images/Image?method=wsdl'
+    start(url)
+    client = Client(url)
+    #print client.factory.resolver.schema
+    print client
+    print 'Logon()'
+    reply = client.service.Logon('testuser','test')
+    print reply
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = 'http://soa.ebrev.info/service.wsdl'
+    start(url)
+    client = Client(url)
+    print client
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = 'http://arcweb.esri.com/services/v2/MapImage.wsdl'
+    start(url)
+    client = Client(url)
+    print client
+    env = client.factory.create('ns2:Envelope')
+    print env
+    options = client.factory.create('ns4:MapImageOptions')
+    print options
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+try:
+    url = "http://www.thomas-bayer.com/axis2/services/BLZService?wsdl"
+    start(url)
+    client = Client(url)
+    print client
+    #client.setport(0)
+    print client.service.getBank("76251020")
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+    
+try:
+    url = "http://arcweb.esri.com/services/v2/RouteFinder.wsdl"
+    start(url)
+    client = Client(url)
+    print client
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+timer = metrics.Timer()
+
+try:
+    url = "https://www.e-conomic.com/secure/api1/EconomicWebService.asmx?WSDL"
+    start(url)
+    timer.start()
+    client = Client(url)
+    #client.setport(0)
+    timer.stop()
+    print 'create client: %s' % timer
+    timer.start()
+    s = str(client)
+    timer.stop()
+    print 'str(client): %s' % timer
+    print 'client:\n%s' % s
+except WebFault, f:
+    errors += 1
+    print f
+    print f.fault
+except Exception, e:
+    errors += 1
+    print e
+    tb.print_exc()
+
+print '\nFinished: errors = %d' % errors
diff --git a/tests/rhq.py b/tests/rhq.py
new file mode 100644
index 0000000..9c87d0b
--- /dev/null
+++ b/tests/rhq.py
@@ -0,0 +1,540 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+#
+# This test requires installation or visability to an RHQ server.
+# ( http://www.rhq-project.org )
+#
+
+import sys
+sys.path.append('../')
+
+import logging
+import traceback as tb
+import suds.metrics as metrics
+from tests import *
+from suds import null, WebFault
+from suds.client import Client
+from suds.xsd.sxbasic import Import
+from suds.transport.https import HttpAuthenticated
+from suds.transport.cache import FileCache
+
+errors = 0
+
+setup_logging()
+
+Import.bind('http://schemas.xmlsoap.org/soap/encoding/')
+
+#logging.getLogger('suds.client').setLevel(logging.DEBUG)
+#logging.getLogger('suds.metrics').setLevel(logging.DEBUG)
+#logging.getLogger('suds').setLevel(logging.DEBUG)
+
+class MyTransport(HttpAuthenticated):
+    pass
+
+mycache = FileCache(days=90)
+mytransport = MyTransport(cache=mycache)
+
+def start(url):
+    global errors
+    print '\n________________________________________________________________\n' 
+    print 'Test @ ( %s ) %d' % (url, errors)
+
+def basic_doc_literal():
+    
+    global errors
+
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceTestBean?wsdl'
+        start(url)
+        client = Client(url, transport=mytransport)
+        print client
+        #
+        # create name
+        #
+        name = client.factory.create('name')
+        name.first = u'Jeff'+unichr(1234)
+        name.last = 'Ortel &lt; Company'
+        #
+        # create a phone object using the wsdl
+        #
+        phoneA = client.factory.create('phone')
+        phoneA.npa = 410
+        phoneA.nxx = 555
+        phoneA.number = 5138
+        phoneB = client.factory.create('phone')
+        phoneB.npa = 919
+        phoneB.nxx = 555
+        phoneB.number = 4406
+        #
+        # create a person object using the wsdl
+        #
+        person = client.factory.create('person')
+        print person
+        person.name = name
+        person.age = 43
+        person.phone.append(phoneA)
+        person.phone.append(phoneB)
+        print person       
+        #
+        # addPerson()
+        #
+        print 'addPersion()'
+        result = client.service.addPerson(person)
+        sent = client.last_sent()
+        rcvd = client.last_received()
+        print '\nreply(\n%s\n)\n' % result
+        #
+        # create a new name object used to update the person
+        #
+        newname = client.factory.create('name')
+        newname.first = 'Todd'
+        newname.last = None
+        #
+        # update the person's name (using the webservice)
+        #
+        print 'updatePersion()'
+        result = client.service.updatePerson(person, newname)
+        print '\nreply(\n%s\n)\n' % str(result)
+        result = client.service.updatePerson(person, None)
+        print '\nreply(\n%s\n)\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+  
+    try:
+        print "echo('this is cool')"
+        result = client.service.echo('this is cool')
+        print '\nreply( %s )\n' % str(result)
+        print 'echo(None)'
+        result = client.service.echo(None)
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+    try:
+        print 'hello()'
+        result = client.service.hello()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+    try:
+        print 'testVoid()'
+        result = client.service.testVoid()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+    try:
+        mylist = ['my', 'dog', 'likes', 'steak']
+        print 'testListArgs(%s)' % mylist
+        result = client.service.testListArg(mylist)
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+    
+    try:
+        s = 'hello'
+        for n in range(0, 3):
+            print 'getList(%s, %d)' % (s, n)
+            result = client.service.getList(s, n)
+            print '\nreply( %s )\n' % str(result)
+            if len(result) != n:
+                errors += 1
+                print 'expected (%d), reply (%d)' % (n, len(result))
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+    
+    try:
+        print 'testExceptions()' 
+        result = client.service.testExceptions()
+        print '\nreply( %s )\n' % tostr(result)
+        raise Exception('Fault expected and not raised')
+    except WebFault, f:
+        print f
+        print f.fault
+        print f.document
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+    #
+    # test faults
+    #
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceTestBean?wsdl'
+        start(url)
+        client = Client(url, faults=False)
+        print 'testExceptions() faults=No'
+        result = client.service.testExceptions()
+        sent = client.last_sent()
+        rcvd = client.last_received()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+def basic_rpc_literal():
+    
+    global errors
+
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceRPCTestBean?wsdl'
+        start(url)
+        client = Client(url)
+        print client
+        #
+        # create name
+        #
+        name = client.factory.create('name')
+        name.first = u'jeff'+unichr(1234)
+        name.last = 'ortel'
+        #
+        # create a phone object using the wsdl
+        #
+        phoneA = client.factory.create('phone')
+        phoneA.npa = 410
+        phoneA.nxx = 555
+        phoneA.number = 5138
+        phoneB = client.factory.create('phone')
+        phoneB.npa = 919
+        phoneB.nxx = 555
+        phoneB.number = 4406
+        #
+        # create a person object using the wsdl
+        #
+        person = client.factory.create('person')
+        print person
+        person.name = name
+        person.age = 43
+        person.phone.append(phoneA)
+        person.phone.append(phoneB)
+        print person       
+        #
+        # addPerson()
+        #
+        print 'addPersion()'
+        result = client.service.addPerson(person)
+        print '\nreply(\n%s\n)\n' % result
+        #
+        # create a new name object used to update the person
+        #
+        newname = client.factory.create('name')
+        newname.first = 'Todd'
+        newname.last = None
+        #
+        # update the person's name (using the webservice)
+        #
+        print 'updatePersion()'
+        result = client.service.updatePerson(person, newname)
+        print '\nreply(\n%s\n)\n' % str(result)
+        result = client.service.updatePerson(person, null())
+        print '\nreply(\n%s\n)\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+  
+    try:
+        print "echo('this is cool')"
+        result = client.service.echo('this is cool')
+        print '\nreply( %s )\n' % str(result)
+        print 'echo(None)'
+        result = client.service.echo(null())
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+    try:
+        print 'hello()'
+        result = client.service.hello()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+    try:
+        print 'testVoid()'
+        result = client.service.testVoid()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+    try:
+        array = client.factory.create('ns0:stringArray')
+        array.item = ['my', 'dog', 'likes', 'steak']
+        print 'testListArgs()\n%s\n' % array
+        result = client.service.testListArg(array)
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+    
+    try:
+        s = 'hello'
+        for n in range(0, 3):
+            print 'getList(%s, %d)' % (s, n)
+            result = client.service.getList(s, n)
+            print '\nreply( %s )\n' % str(result)
+            if n > 0 and n != len(result.item):
+                errors += 1
+                print 'expected (%d), reply (%d)' % (n, len(result.item))
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+    
+    try:
+        print 'testExceptions()' 
+        result = client.service.testExceptions()
+        print '\nreply( %s )\n' % tostr(result)
+        raise Exception('Fault expected and not raised')
+    except WebFault, f:
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/WebServiceRPCTestBean?wsdl'
+        start(url)
+        client = Client(url, faults=False)
+        print 'testExceptions()'
+        result = client.service.testExceptions()
+        print '\nreply( %s )\n' % str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+def authentication():
+    
+    global subject, errors
+    
+    try:
+        
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/SubjectManagerBean?wsdl'
+        start(url)
+        client = Client(url)
+        print client
+        #
+        # test enumerations
+        #
+        permission = client.factory.create('permission')
+        #
+        # login
+        #
+        print 'login()'
+        subject = client.service.login('rhqadmin', 'rhqadmin')
+        print '\nreply(\n%s\n)\n' % str(subject)
+        #
+        # create page control and get all subjects
+        #
+        pc = client.factory.create('pageControl')
+        pc.pageNumber = 0
+        pc.pageSize = 0
+        #
+        # getAllSubjects()
+        #
+        print 'getAllSubjects()'
+        users = client.service.getAllSubjects(pc)
+        print 'Reply:\n(\n%s\n)\n' % str(users)
+        #
+        # get user preferences
+        #
+        print 'loadUserConfiguration()'
+        id = subject.id
+        print subject
+        prefs = client.service.loadUserConfiguration(id)
+        print 'Reply:\n(\n%s\n)\n' % str(prefs)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+def perspectives():
+    
+    global subject, errors
+    
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/PerspectiveManagerBean?wsdl'
+        start(url)
+        client = Client(url)
+        print client
+        #
+        # get all (content) perspectives
+        #
+        print 'getPerspective(content)'
+        perspectives = client.service.getPerspective("content")
+        print 'perspectives: ', str(perspectives)
+        #
+        # get all perspectives
+        #     
+        print 'getAllPerspective()'
+        perspectives = client.service.getAllPerspectives()
+        print 'perspectives: ', str(perspectives)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+        
+def content_source():
+    
+    global subject, errors
+    
+    try:
+        url = 'http://localhost:7080/rhq-rhq-enterprise-server-ejb3/ContentSourceManagerBean?wsdl'
+        start(url)
+        client = Client(url, xstq=False)
+        print client
+        #
+        # create a configuration
+        #
+        configuration = client.factory.create('configuration')
+        entry = client.factory.create('configuration.properties.entry')
+        simple = client.factory.create('propertySimple')
+        entry.key = 'location'
+        simple.name = 'location'
+        simple.stringValue = 'http://download.skype.com/linux/repos/fedora/updates/i586'
+        entry.value = simple
+        configuration.properties.entry.append(entry)
+        configuration.notes = 'SkipeAdapter'
+        configuration.version = 1234
+        print configuration
+        #
+        # create: name, description and type.
+        #
+        name = 'SkipeAdapter'
+        description = 'The skipe adapter'
+        type = 'YumSource'
+        #
+        # create a content source.
+        #
+        print 'createContentSource()'
+        result = client.service.createContentSource(
+                            subject, 
+                            name, 
+                            description, 
+                            type, 
+                            configuration, 
+                            False)
+        print 'createContentSource: ', str(result)
+    except WebFault, f:
+        errors += 1
+        print f
+        print f.fault
+    except Exception, e:
+        errors += 1
+        print e
+        tb.print_exc()
+
+        
+if __name__ == '__main__':
+    
+    errors = 0
+    basic_doc_literal()
+    basic_rpc_literal()
+    authentication()
+    perspectives()
+    content_source()
+    
+    print '\nFinished: errors=%d' % errors
diff --git a/tests/saxenc.py b/tests/saxenc.py
new file mode 100644
index 0000000..e88e335
--- /dev/null
+++ b/tests/saxenc.py
@@ -0,0 +1,56 @@
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the (LGPL) GNU Lesser General Public License as
+# published by the Free Software Foundation; either version 3 of the 
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Library Lesser General Public License for more details at
+# ( http://www.gnu.org/licenses/lgpl.html ).
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+# written by: Jeff Ortel ( jortel at redhat.com )
+
+#
+# sax encoding/decoding test.
+#
+
+from suds.sax.element import Element
+from suds.sax.parser import Parser
+
+def basic():
+    xml = "<a>Me && <b>my</b> shadow's <i>dog</i> love to 'play' and sing "la,la,la";</a>"
+    p = Parser()
+    d = p.parse(string=xml)
+    a = d.root()
+    print 'A(parsed)=\n%s' % a
+    assert str(a) == xml
+    b = Element('a')
+    b.setText('Me && <b>my</b> shadow\'s <i>dog</i> love to \'play\' and sing "la,la,la";')
+    print 'B(encoded)=\n%s' % b
+    assert str(b) == xml
+    print 'A(text-decoded)=\n%s' % a.getText()
+    print 'B(text-decoded)=\n%s' % b.getText()
+    assert a.getText() == b.getText()
+    print 'test pruning'
+    j = Element('A')
+    j.set('n', 1)
+    j.append(Element('B'))
+    print j
+    j.prune()
+    print j
+
+def cdata():
+    xml = '<a><![CDATA[<b>This is my &<tag></b>]]></a>'
+    p = Parser()
+    d = p.parse(string=xml)
+    print d
+    a = d.root()
+    print a.getText()
+
+if __name__ == '__main__':
+    #basic()
+    cdata()
-- 
suds



More information about the tryton-debian-vcs mailing list