[tryton-debian-vcs] python-zeep branch upstream updated. upstream/2.1.1-1-g4faba01

Mathias Behrle tryton-debian-vcs at alioth.debian.org
Mon Sep 4 19:01:38 UTC 2017


The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/python-zeep.git;a=commitdiff;h=upstream/2.1.1-1-g4faba01

commit 4faba01ac38b29fa748e346c2790ad6f580067c0
Author: Mathias Behrle <mathiasb at m9s.biz>
Date:   Mon Sep 4 16:23:13 2017 +0200

    Adding upstream version 2.4.0.
    
    Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>

diff --git a/CHANGES b/CHANGES
index 8bbebc5..55771b8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,29 @@
+2.4.0 (2017-08-26)
+------------------
+ - Add support for tornado async transport via gen.coroutine (#530, Kateryna Burda)
+ - Check if soap:address is defined in the service port instead of raising an
+   exception (#527)
+ - Update packaging (stop using find_packages()) (#529)
+ - Properly handle None values when rendering complex types (#526)
+ - Fix generating signature for empty wsdl messages (#542)
+ - Support passing strings to xsd:Time objects (#540)
+
+
+2.3.0 (2017-08-06)
+------------------
+ - The XML send to the server is no longer using ``pretty_print=True`` (#484)
+ - Refactor of the multiref support to fix issues with child elements (#489)
+ - Add workaround to support negative durations (#486)
+ - Fix creating XML documents for operations without aguments (#479)
+ - Fix xsd:extension on xsd:group elements (#523)
+
+
+2.2.0 (2017-06-19)
+------------------
+ - Automatically import the soap-encoding schema if it is required (#473)
+ - Add support for XOP messages (this is a rewrite of #325 by vashek)
+
+
 2.1.1 (2017-06-11)
 ------------------
  - Fix previous release, it contained an incorrect dependency (Mock 2.1.) due
diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
index 44a9546..063f6ce 100644
--- a/CONTRIBUTORS.rst
+++ b/CONTRIBUTORS.rst
@@ -4,42 +4,51 @@ Authors
 
 Contributors
 ============
-* vashek
+
+* Kateryna Burda
+* Alexey Stepanov
 * Marco Vellinga
 * jaceksnet
 * Andrew Serong
-* Joeri Bekker
-* Eric Wong
-* Jacek Stępniewski
-* Alexey Stepanov
+* vashek
+* Seppo Yli-Olli
+* Sam Denton
+* Dani Möller
 * Julien Delasoie
+* Christian González
 * bjarnagin
 * mcordes
-* Sam Denton
-* David Baumgold
+* Joeri Bekker
+* Bartek Wójcicki
+* jhorman
 * fiebiga
+* David Baumgold
 * Antonio Cuni
 * Alexandre de Mari
-* Jason Vertrees
 * Nicolas Evrard
+* Eric Wong
+* Jason Vertrees
+* Falldog
 * Matt Grimm (mgrimm)
 * Marek Wywiał
-* Falldog
 * btmanm
 * Caleb Salt
+* Ondřej Lanč
+* Jan Murre
+* Stefano Parmesan
 * Julien Marechal
-* Mike Fiedler
 * Dave Wapstra
-* OrangGeeGee
-* Stefano Parmesan
-* Jan Murre
-* Ben Tucker
+* Mike Fiedler
+* Derek Harland
 * Bruno Duyé
 * Christoph Heuel
-* Derek Harland
+* Ben Tucker
 * Eric Waller
 * Falk Schuetzenmeister
 * Jon Jenkins
+* OrangGeeGee
 * Raymond Piller
 * Zoltan Benedek
 * Øyvind Heddeland Instefjord
+
+
diff --git a/PKG-INFO b/PKG-INFO
index 6f21657..2d57228 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: zeep
-Version: 2.1.1
+Version: 2.4.0
 Summary: A modern/fast Python SOAP client based on lxml / requests
 Home-page: http://docs.python-zeep.org
 Author: Michael van Tellingen
@@ -18,7 +18,9 @@ Description: ========================
          * Support for Soap 1.1, Soap 1.2 and HTTP bindings
          * Support for WS-Addressing headers
          * Support for WSSE (UserNameToken / x.509 signing)
-         * Experimental support for asyncio via aiohttp (Python 3.5+)
+         * Support for tornado async transport via gen.coroutine (Python 2.7+)
+         * Support for asyncio via aiohttp (Python 3.5+)
+         * Experimental support for XOP messages
         
         
         Please see for more information the documentation at
diff --git a/README.rst b/README.rst
index 647b78c..fde244e 100644
--- a/README.rst
+++ b/README.rst
@@ -10,7 +10,9 @@ Highlights:
  * Support for Soap 1.1, Soap 1.2 and HTTP bindings
  * Support for WS-Addressing headers
  * Support for WSSE (UserNameToken / x.509 signing)
- * Experimental support for asyncio via aiohttp (Python 3.5+)
+ * Support for tornado async transport via gen.coroutine (Python 2.7+)
+ * Support for asyncio via aiohttp (Python 3.5+)
+ * Experimental support for XOP messages
 
 
 Please see for more information the documentation at
diff --git a/setup.cfg b/setup.cfg
index e77a765..54d59b5 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
 [bumpversion]
-current_version = 2.1.1
+current_version = 2.4.0
 commit = true
 tag = true
 tag_name = {new_version}
diff --git a/setup.py b/setup.py
index dff575e..edd6a14 100755
--- a/setup.py
+++ b/setup.py
@@ -19,6 +19,10 @@ docs_require = [
     'sphinx>=1.4.0',
 ]
 
+tornado_require = [
+    'tornado>=4.0.2'
+]
+
 async_require = []  # see below
 
 xmlsec_require = [
@@ -29,15 +33,17 @@ tests_require = [
     'freezegun==0.3.8',
     'mock==2.0.0',
     'pretend==1.0.8',
-    'pytest-cov==2.4.0',
-    'pytest==3.0.6',
+    'pytest-cov==2.5.1',
+    'pytest==3.1.3',
     'requests_mock>=0.7.0',
+    'pytest-tornado==0.4.5',
 
     # Linting
     'isort==4.2.5',
-    'flake8==3.2.1',
+    'flake8==3.3.0',
     'flake8-blind-except==0.1.1',
     'flake8-debugger==1.4.0',
+    'flake8-imports==0.1.1',
 ]
 
 
@@ -52,7 +58,7 @@ with open('README.rst') as fh:
 
 setup(
     name='zeep',
-    version='2.1.1',
+    version='2.4.0',
     description='A modern/fast Python SOAP client based on lxml / requests',
     long_description=long_description,
     author="Michael van Tellingen",
@@ -65,11 +71,12 @@ setup(
         'docs': docs_require,
         'test': tests_require,
         'async': async_require,
+        'tornado': tornado_require,
         'xmlsec': xmlsec_require,
     },
     entry_points={},
     package_dir={'': 'src'},
-    packages=find_packages('src'),
+    packages=['zeep'],
     include_package_data=True,
 
     license='MIT',
diff --git a/src/zeep.egg-info/PKG-INFO b/src/zeep.egg-info/PKG-INFO
index 6f21657..2d57228 100644
--- a/src/zeep.egg-info/PKG-INFO
+++ b/src/zeep.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: zeep
-Version: 2.1.1
+Version: 2.4.0
 Summary: A modern/fast Python SOAP client based on lxml / requests
 Home-page: http://docs.python-zeep.org
 Author: Michael van Tellingen
@@ -18,7 +18,9 @@ Description: ========================
          * Support for Soap 1.1, Soap 1.2 and HTTP bindings
          * Support for WS-Addressing headers
          * Support for WSSE (UserNameToken / x.509 signing)
-         * Experimental support for asyncio via aiohttp (Python 3.5+)
+         * Support for tornado async transport via gen.coroutine (Python 2.7+)
+         * Support for asyncio via aiohttp (Python 3.5+)
+         * Experimental support for XOP messages
         
         
         Please see for more information the documentation at
diff --git a/src/zeep.egg-info/SOURCES.txt b/src/zeep.egg-info/SOURCES.txt
index ab2e621..df1bde9 100644
--- a/src/zeep.egg-info/SOURCES.txt
+++ b/src/zeep.egg-info/SOURCES.txt
@@ -34,6 +34,9 @@ src/zeep.egg-info/top_level.txt
 src/zeep/asyncio/__init__.py
 src/zeep/asyncio/bindings.py
 src/zeep/asyncio/transport.py
+src/zeep/tornado/__init__.py
+src/zeep/tornado/bindings.py
+src/zeep/tornado/transport.py
 src/zeep/wsdl/__init__.py
 src/zeep/wsdl/attachments.py
 src/zeep/wsdl/definitions.py
@@ -49,6 +52,7 @@ src/zeep/wsdl/messages/http.py
 src/zeep/wsdl/messages/mime.py
 src/zeep/wsdl/messages/multiref.py
 src/zeep/wsdl/messages/soap.py
+src/zeep/wsdl/messages/xop.py
 src/zeep/wsse/__init__.py
 src/zeep/wsse/compose.py
 src/zeep/wsse/signature.py
@@ -92,6 +96,8 @@ tests/test_main.py
 tests/test_pprint.py
 tests/test_response.py
 tests/test_soap_multiref.py
+tests/test_soap_xop.py
+tests/test_tornado_transport.py
 tests/test_transports.py
 tests/test_wsa.py
 tests/test_wsdl.py
diff --git a/src/zeep.egg-info/requires.txt b/src/zeep.egg-info/requires.txt
index bbcba39..3c7acf2 100644
--- a/src/zeep.egg-info/requires.txt
+++ b/src/zeep.egg-info/requires.txt
@@ -18,14 +18,19 @@ sphinx>=1.4.0
 freezegun==0.3.8
 mock==2.0.0
 pretend==1.0.8
-pytest-cov==2.4.0
-pytest==3.0.6
+pytest-cov==2.5.1
+pytest==3.1.3
 requests_mock>=0.7.0
+pytest-tornado==0.4.5
 isort==4.2.5
-flake8==3.2.1
+flake8==3.3.0
 flake8-blind-except==0.1.1
 flake8-debugger==1.4.0
+flake8-imports==0.1.1
 aioresponses>=0.1.3
 
+[tornado]
+tornado>=4.0.2
+
 [xmlsec]
 xmlsec>=0.6.1
diff --git a/src/zeep/__init__.py b/src/zeep/__init__.py
index faa5a67..a2c1f81 100644
--- a/src/zeep/__init__.py
+++ b/src/zeep/__init__.py
@@ -3,4 +3,4 @@ from zeep.transports import Transport  # noqa
 from zeep.plugins import Plugin  # noqa
 from zeep.xsd.valueobjects import AnyObject  # noqa
 
-__version__ = '2.1.1'
+__version__ = '2.4.0'
diff --git a/src/zeep/asyncio/transport.py b/src/zeep/asyncio/transport.py
index ec3cc40..217d6ad 100644
--- a/src/zeep/asyncio/transport.py
+++ b/src/zeep/asyncio/transport.py
@@ -4,10 +4,12 @@ Adds asyncio support to Zeep. Contains Python 3.5+ only syntax!
 """
 import asyncio
 import logging
+from . import bindings
 
 import aiohttp
 from requests import Response
 
+from zeep.exceptions import TransportError
 from zeep.transports import Transport
 from zeep.utils import get_version
 from zeep.wsdl.utils import etree_to_string
@@ -17,7 +19,10 @@ __all__ = ['AsyncTransport']
 
 class AsyncTransport(Transport):
     """Asynchronous Transport class using aiohttp."""
-    supports_async = True
+    binding_classes = [
+                bindings.AsyncSoap11Binding,
+                bindings.AsyncSoap12Binding,
+            ]
 
     def __init__(self, loop, cache=None, timeout=300, operation_timeout=None,
                  session=None):
@@ -45,6 +50,14 @@ class AsyncTransport(Transport):
             with aiohttp.Timeout(self.load_timeout):
                 response = await self.session.get(url)
                 result = await response.read()
+                try:
+                    response.raise_for_status()
+                except aiohttp.ClientError as exc:
+                    raise TransportError(
+                        message=str(exc),
+                        status_code=response.status,
+                        content=result
+                    ).with_traceback(exc.__traceback__) from exc
 
         # Block until we have the data
         self.loop.run_until_complete(_load_remote_data_async())
diff --git a/src/zeep/client.py b/src/zeep/client.py
index cad16ac..d8933a9 100644
--- a/src/zeep/client.py
+++ b/src/zeep/client.py
@@ -167,11 +167,12 @@ class Client(object):
 
 
         """
-        # Store current options
-        old_raw_raw_response = self.raw_response
+        if raw_response is not NotSet:
+            # Store current options
+            old_raw_response = self.raw_response
 
-        # Set new options
-        self.raw_response = raw_response
+            # Set new options
+            self.raw_response = raw_response
 
         if timeout is not NotSet:
             timeout_ctx = self.transport._options(timeout=timeout)
@@ -179,7 +180,8 @@ class Client(object):
 
         yield
 
-        self.raw_response = old_raw_raw_response
+        if raw_response is not NotSet:
+            self.raw_response = old_raw_response
 
         if timeout is not NotSet:
             timeout_ctx.__exit__(None, None, None)
diff --git a/src/zeep/exceptions.py b/src/zeep/exceptions.py
index a7a4c1d..7be0773 100644
--- a/src/zeep/exceptions.py
+++ b/src/zeep/exceptions.py
@@ -8,7 +8,9 @@ class Error(Exception):
 
 
 class XMLSyntaxError(Error):
-    pass
+    def __init__(self, *args, **kwargs):
+        self.content = kwargs.pop('content', None)
+        super(XMLSyntaxError, self).__init__(*args, **kwargs)
 
 
 class XMLParseError(Error):
@@ -35,7 +37,10 @@ class WsdlSyntaxError(Error):
 
 
 class TransportError(Error):
-    pass
+    def __init__(self, message='', status_code=0, content=None):
+        super(TransportError, self).__init__(message)
+        self.status_code = status_code
+        self.content = content
 
 
 class LookupError(Error):
diff --git a/src/zeep/loader.py b/src/zeep/loader.py
index 58ea81e..561b800 100644
--- a/src/zeep/loader.py
+++ b/src/zeep/loader.py
@@ -46,7 +46,10 @@ def parse_xml(content, transport, base_url=None, strict=True,
     try:
         return fromstring(content, parser=parser, base_url=base_url)
     except etree.XMLSyntaxError as exc:
-        raise XMLSyntaxError("Invalid XML content received (%s)" % exc.msg)
+        raise XMLSyntaxError(
+            "Invalid XML content received (%s)" % exc.msg,
+            content=content
+        )
 
 
 def load_external(url, transport, base_url=None, strict=True):
diff --git a/src/zeep/tornado/__init__.py b/src/zeep/tornado/__init__.py
new file mode 100644
index 0000000..3011239
--- /dev/null
+++ b/src/zeep/tornado/__init__.py
@@ -0,0 +1,2 @@
+from .transport import *  # noqa
+from .bindings import *  # noqa
diff --git a/src/zeep/tornado/bindings.py b/src/zeep/tornado/bindings.py
new file mode 100644
index 0000000..ba73800
--- /dev/null
+++ b/src/zeep/tornado/bindings.py
@@ -0,0 +1,28 @@
+from zeep.wsdl import bindings
+from tornado import gen
+
+__all__ = ['AsyncSoap11Binding', 'AsyncSoap12Binding']
+
+
+class AsyncSoapBinding(object):
+
+    @gen.coroutine
+    def send(self, client, options, operation, args, kwargs):
+        envelope, http_headers = self._create(
+            operation, args, kwargs,
+            client=client,
+            options=options)
+
+        response = yield client.transport.post_xml(
+            options['address'], envelope, http_headers)
+
+        operation_obj = self.get(operation)
+        raise gen.Return(self.process_reply(client, operation_obj, response))
+
+
+class AsyncSoap11Binding(AsyncSoapBinding, bindings.Soap11Binding):
+    pass
+
+
+class AsyncSoap12Binding(AsyncSoapBinding, bindings.Soap12Binding):
+    pass
diff --git a/src/zeep/tornado/transport.py b/src/zeep/tornado/transport.py
new file mode 100644
index 0000000..c80d971
--- /dev/null
+++ b/src/zeep/tornado/transport.py
@@ -0,0 +1,133 @@
+"""
+Adds async tornado.gen support to Zeep.
+
+"""
+import logging
+import urllib
+from . import bindings
+
+from tornado import gen, httpclient
+from requests import Response, Session
+from requests.auth import HTTPBasicAuth, HTTPDigestAuth
+
+from zeep.transports import Transport
+from zeep.utils import get_version
+from zeep.wsdl.utils import etree_to_string
+
+__all__ = ['TornadoAsyncTransport']
+
+
+class TornadoAsyncTransport(Transport):
+    """Asynchronous Transport class using tornado gen."""
+    binding_classes = [
+        bindings.AsyncSoap11Binding,
+        bindings.AsyncSoap12Binding]
+
+    def __init__(self, cache=None, timeout=300, operation_timeout=None,
+                 session=None):
+        self.cache = cache
+        self.load_timeout = timeout
+        self.operation_timeout = operation_timeout
+        self.logger = logging.getLogger(__name__)
+
+        self.session = session or Session()
+        self.session.headers['User-Agent'] = (
+            'Zeep/%s (www.python-zeep.org)' % (get_version()))
+
+    def _load_remote_data(self, url):
+        client = httpclient.HTTPClient()
+        kwargs = {
+            'method': 'GET',
+            'request_timeout': self.load_timeout
+        }
+        http_req = httpclient.HTTPRequest(url, **kwargs)
+        response = client.fetch(http_req)
+        return response.body
+
+    @gen.coroutine
+    def post(self, address, message, headers):
+        response = yield self.fetch(address, 'POST', headers, message)
+
+        raise gen.Return(response)
+
+    @gen.coroutine
+    def post_xml(self, address, envelope, headers):
+        message = etree_to_string(envelope)
+
+        response = yield self.post(address, message, headers)
+
+        raise gen.Return(response)
+
+    @gen.coroutine
+    def get(self, address, params, headers):
+        if params:
+            address += '?' + urllib.urlencode(params)
+        response = yield self.fetch(address, 'GET', headers)
+
+        raise gen.Return(response)
+
+    @gen.coroutine
+    def fetch(self, address, method, headers, message=None):
+        async_client = httpclient.AsyncHTTPClient()
+
+        # extracting auth
+        auth_username = None
+        auth_password = None
+        auth_mode = None
+
+        if self.session.auth:
+            if type(self.session.auth) is tuple:
+                auth_username = self.session.auth[0]
+                auth_password = self.session.auth[1]
+                auth_mode = 'basic'
+            elif type(self.session.auth) is HTTPBasicAuth:
+                auth_username = self.session.username
+                auth_password = self.session.password
+                auth_mode = 'basic'
+            elif type(self.session.auth) is HTTPDigestAuth:
+                auth_username = self.session.username
+                auth_password = self.session.password
+                auth_mode = 'digest'
+            else:
+                raise StandardError('Not supported authentication.')
+
+        # extracting client cert
+        client_cert = None
+        client_key = None
+
+        if self.session.cert:
+            if type(self.session.cert) is str:
+                client_cert = self.session.cert
+            elif type(self.session.cert) is tuple:
+                client_cert = self.session.cert[0]
+                client_key = self.session.cert[1]
+
+        session_headers = dict(self.session.headers.items())
+
+        kwargs = {
+            'method': method,
+            'request_timeout': self.operation_timeout,
+            'headers': dict(headers, **session_headers),
+            'auth_username': auth_username,
+            'auth_password': auth_password,
+            'auth_mode': auth_mode,
+            'validate_cert': self.session.verify,
+            'client_key': client_key,
+            'client_cert': client_cert
+        }
+
+        if message:
+            kwargs['body'] = message
+
+        http_req = httpclient.HTTPRequest(address, **kwargs)
+        response = yield async_client.fetch(http_req)
+
+        raise gen.Return(self.new_response(response))
+
+    def new_response(self, response):
+        """Convert an tornado.HTTPResponse object to a requests.Response object"""
+        new = Response()
+        new._content = response.body
+        new.status_code = response.code
+        new.headers = dict(response.headers.get_all())
+        return new
\ No newline at end of file
diff --git a/src/zeep/transports.py b/src/zeep/transports.py
index 29a4453..1f18772 100644
--- a/src/zeep/transports.py
+++ b/src/zeep/transports.py
@@ -19,7 +19,6 @@ class Transport(object):
     :param session: A :py:class:`request.Session()` object (optional)
 
     """
-    supports_async = False
 
     def __init__(self, cache=None, timeout=300, operation_timeout=None,
                  session=None):
diff --git a/src/zeep/utils.py b/src/zeep/utils.py
index 2d15c51..b763c8a 100644
--- a/src/zeep/utils.py
+++ b/src/zeep/utils.py
@@ -26,7 +26,8 @@ def as_qname(value, nsmap, target_namespace=None):
             namespace = nsmap.get(prefix)
 
         if not namespace:
-            raise XMLParseError("No namespace defined for %r" % prefix)
+            raise XMLParseError(
+                "No namespace defined for %r (%r)" % (prefix, value))
 
         # Workaround for https://github.com/mvantellingen/python-zeep/issues/349
         if not local:
diff --git a/src/zeep/wsdl/attachments.py b/src/zeep/wsdl/attachments.py
index 505980c..5ddd816 100644
--- a/src/zeep/wsdl/attachments.py
+++ b/src/zeep/wsdl/attachments.py
@@ -75,6 +75,6 @@ class Attachment(object):
         if encoding == 'base64':
             return base64.b64decode(content)
         elif encoding == 'binary':
-            return content
+            return content.strip(b'\r\n')
         else:
             return content
diff --git a/src/zeep/wsdl/bindings/soap.py b/src/zeep/wsdl/bindings/soap.py
index ae08ad7..59adf00 100644
--- a/src/zeep/wsdl/bindings/soap.py
+++ b/src/zeep/wsdl/bindings/soap.py
@@ -10,6 +10,7 @@ from zeep.utils import as_qname, get_media_type, qname_attr
 from zeep.wsdl.attachments import MessagePack
 from zeep.wsdl.definitions import Binding, Operation
 from zeep.wsdl.messages import DocumentMessage, RpcMessage
+from zeep.wsdl.messages.xop import process_xop
 from zeep.wsdl.utils import etree_to_string, url_http_to_https
 
 logger = logging.getLogger(__name__)
@@ -133,16 +134,18 @@ class SoapBinding(Binding):
         if response.status_code != 200 and not response.content:
             raise TransportError(
                 u'Server returned HTTP status %d (no content available)'
-                % response.status_code)
+                % response.status_code,
+                status_code=response.status_code)
 
         content_type = response.headers.get('Content-Type', 'text/xml')
         media_type = get_media_type(content_type)
         message_pack = None
 
+        # If the reply is a multipart/related then we need to retrieve all the
+        # parts
         if media_type == 'multipart/related':
             decoder = MultipartDecoder(
                 response.content, content_type, response.encoding or 'utf-8')
-
             content = decoder.parts[0].content
             if len(decoder.parts) > 1:
                 message_pack = MessagePack(parts=decoder.parts[1:])
@@ -157,7 +160,14 @@ class SoapBinding(Binding):
         except XMLSyntaxError:
             raise TransportError(
                 'Server returned HTTP status %d (%s)'
-                % (response.status_code, response.content))
+                % (response.status_code, response.content),
+                status_code=response.status_code,
+                content=response.content)
+
+        # Check if this is an XOP message which we need to decode first
+        if message_pack:
+            if process_xop(doc, message_pack):
+                message_pack = None
 
         if client.wsse:
             client.wsse.verify(doc)
@@ -184,6 +194,9 @@ class SoapBinding(Binding):
 
     def process_service_port(self, xmlelement, force_https=False):
         address_node = xmlelement.find('soap:address', namespaces=self.nsmap)
+        if address_node is None:
+            logger.debug("No valid soap:address found for service")
+            return
 
         # Force the usage of HTTPS when the force_https boolean is true
         location = address_node.get('location')
diff --git a/src/zeep/wsdl/messages/multiref.py b/src/zeep/wsdl/messages/multiref.py
index 907049e..04abec3 100644
--- a/src/zeep/wsdl/messages/multiref.py
+++ b/src/zeep/wsdl/messages/multiref.py
@@ -1,4 +1,4 @@
-import copy
+import re
 
 from lxml import etree
 
@@ -19,6 +19,7 @@ def process_multiref(node):
     used_nodes = []
 
     def process(node):
+        """Recursive"""
         # TODO (In Soap 1.2 this is 'ref')
         href = node.attrib.get('href')
 
@@ -26,14 +27,7 @@ def process_multiref(node):
             obj = multiref_objects.get(href[1:])
             if obj is not None:
                 used_nodes.append(obj)
-                parent = node.getparent()
-
-                new = _dereference_element(obj, node)
-
-                # Replace the node with the new dereferenced node
-                parent.insert(parent.index(node), new)
-                parent.remove(node)
-                node = new
+                node = _dereference_element(obj, node)
 
         for child in node:
             process(child)
@@ -48,34 +42,117 @@ def process_multiref(node):
 
 
 def _dereference_element(source, target):
-    reverse_nsmap = {v: k for k, v in target.nsmap.items()}
-    specific_nsmap = {k: v for k, v in source.nsmap.items() if k not in target.nsmap}
+    """Move the referenced node (source) in the main response tree (target)
 
-    new = etree.Element(target.tag, nsmap=specific_nsmap)
+    :type source: lxml.etree._Element
+    :type target: lxml.etree._Element
+    :rtype target: lxml.etree._Element
 
-    # Copy the attributes. This is actually the difficult part since the
-    # namespace prefixes can change in the attribute values. So for example
-    # the xsi:type="ns11:my-type" need's to be parsed to use a new global
-    # prefix.
-    for key, value in source.attrib.items():
-        if key == 'id':
-            continue
+    """
+    specific_nsmap = {
+        k: v for k, v in source.nsmap.items() if k not in target.nsmap
+    }
 
-        setted = False
-        if value.count(':') == 1:
-            prefix, localname = value.split(':')
-            if prefix in specific_nsmap:
-                namespace = specific_nsmap[prefix]
-                if namespace in reverse_nsmap:
-                    new.set(key, '%s:%s' % (reverse_nsmap[namespace], localname))
-                    setted = True
+    new = _clone_element(source, target.tag, specific_nsmap)
 
-        if not setted:
-            new.set(key, value)
+    # Replace the node with the new dereferenced node
+    parent = target.getparent()
+    parent.insert(parent.index(target), new)
+    parent.remove(target)
 
-    # Copy the children and the text content
-    for child in source:
-        new.append(copy.deepcopy(child))
-    new.text = source.text
+    # Update all descendants
+    for obj in new.iter():
+        _prefix_node(obj)
 
     return new
+
+
+def _clone_element(node, tag_name=None, nsmap=None):
+    """Clone the given node and return it.
+
+    This is a recursive call since we want to clone the children the same
+    way.
+
+    :type source: lxml.etree._Element
+    :type tag_name: str
+    :type nsmap: dict
+    :rtype source: lxml.etree._Element
+
+    """
+    tag_name = tag_name or node.tag
+    nsmap = node.nsmap if nsmap is None else nsmap
+    new = etree.Element(tag_name, nsmap=nsmap)
+
+    for child in node:
+        new_child = _clone_element(child)
+        new.append(new_child)
+    new.text = node.text
+
+    for key, value in _get_attributes(node):
+        new.set(key, value)
+
+    return new
+
+
+def _prefix_node(node):
+    """Translate the internal attribute values back to prefixed tokens.
+
+    This reverses the translation done in _get_attributes
+
+    For example::
+
+        {
+            'foo:type': '{http://example.com}string'
+        }
+
+    will be converted to:
+
+        {
+            'foo:type': 'example:string'
+        }
+
+    :type node: lxml.etree._Element
+
+    """
+    reverse_nsmap = {v: k for k, v in node.nsmap.items()}
+
+    prefix_re = re.compile('^{([^}]+)}(.*)')
+
+    for key, value in node.attrib.items():
+        if value.startswith('{'):
+            match = prefix_re.match(value)
+            namespace, localname = match.groups()
+
+            if namespace in reverse_nsmap:
+                value = '%s:%s' % (reverse_nsmap.get(namespace), localname)
+                node.set(key, value)
+
+
+def _get_attributes(node):
+    """Return the node attributes where prefixed values are dereferenced.
+
+    For example the following xml::
+
+        <foobar xmlns:xsi="foo" xmlns:ns0="bar" xsi:type="ns0:string">
+
+    will return the dict::
+
+        {
+            'foo:type': '{http://example.com}string'
+        }
+
+    :type node: lxml.etree._Element
+
+    """
+    nsmap = node.nsmap
+    result = {}
+
+    for key, value in node.attrib.items():
+        if value.count(':') == 1:
+            prefix, localname = value.split(':')
+
+            if prefix in nsmap:
+                namespace = nsmap[prefix]
+                value = '{%s}%s' % (namespace, localname)
+        result[key] = value
+    return list(result.items())
diff --git a/src/zeep/wsdl/messages/soap.py b/src/zeep/wsdl/messages/soap.py
index 940a2e6..265f5d0 100644
--- a/src/zeep/wsdl/messages/soap.py
+++ b/src/zeep/wsdl/messages/soap.py
@@ -9,7 +9,6 @@ from collections import OrderedDict
 from lxml import etree
 from lxml.builder import ElementMaker
 
-from zeep import ns
 from zeep import exceptions, xsd
 from zeep.utils import as_qname
 from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
@@ -53,24 +52,22 @@ class SoapMessage(ConcreteMessage):
         nsmap.update(self.wsdl.types._prefix_map_custom)
 
         soap = ElementMaker(namespace=self.nsmap['soap-env'], nsmap=nsmap)
-        body = header = None
 
         # Create the soap:header element
         headers_value = kwargs.pop('_soapheaders', None)
         header = self._serialize_header(headers_value, nsmap)
 
         # Create the soap:body element
+        body = soap.Body()
         if self.body:
             body_value = self.body(*args, **kwargs)
-            body = soap.Body()
             self.body.render(body, body_value)
 
         # Create the soap:envelope
         envelope = soap.Envelope()
         if header is not None:
             envelope.append(header)
-        if body is not None:
-            envelope.append(body)
+        envelope.append(body)
 
         # XXX: This is only used in Soap 1.1 so should be moved to the the
         # Soap11Binding._set_http_headers(). But let's keep it like this for
@@ -89,7 +86,6 @@ class SoapMessage(ConcreteMessage):
         if not self.envelope:
             return None
 
-
         body = envelope.find('soap-env:Body', namespaces=self.nsmap)
         body_result = self._deserialize_body(body)
 
@@ -136,7 +132,10 @@ class SoapMessage(ConcreteMessage):
                     return None
             return self.envelope.type.signature(schema=self.wsdl.types, standalone=False)
 
-        parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
+        if self.body:
+            parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
+        else:
+            parts = []
         if self.header.type._element:
             parts.append('_soapheaders={%s}' % self.header.type.signature(
                 schema=self.wsdl.types, standalone=False))
@@ -301,7 +300,9 @@ class SoapMessage(ConcreteMessage):
                 xsd.Element('{%s}header' % self.nsmap['soap-env'], self.header.type))
 
         all_elements.append(
-            xsd.Element('{%s}body' % self.nsmap['soap-env'], self.body.type))
+            xsd.Element(
+                '{%s}body' % self.nsmap['soap-env'],
+                self.body.type if self.body else None))
 
         return xsd.Element('{%s}envelope' % self.nsmap['soap-env'], xsd.ComplexType(all_elements))
 
@@ -414,7 +415,7 @@ class DocumentMessage(SoapMessage):
         name = etree.QName(self.nsmap['soap-env'], 'Body')
 
         if not info or not parts:
-            return xsd.Element(name, xsd.ComplexType([]))
+            return None
 
         # If the part name is omitted then all parts are available under
         # the soap:body tag. Otherwise only the part with the given name.
@@ -470,9 +471,8 @@ class RpcMessage(SoapMessage):
         name and its namespace is the value of the namespace attribute.
 
         """
-        name = etree.QName(self.nsmap['soap-env'], 'Body')
         if not info:
-            return xsd.Element(name, xsd.ComplexType([]))
+            return None
 
         namespace = info['namespace']
         if self.type == 'input':
diff --git a/src/zeep/wsdl/messages/xop.py b/src/zeep/wsdl/messages/xop.py
new file mode 100644
index 0000000..b62645b
--- /dev/null
+++ b/src/zeep/wsdl/messages/xop.py
@@ -0,0 +1,26 @@
+import base64
+
+
+def process_xop(document, message_pack):
+    """Iterate through the tree and replace the xop:include elements."""
+
+    xop_nodes = document.xpath('//xop:Include', namespaces={
+        'xop': 'http://www.w3.org/2004/08/xop/include'
+    })
+    num_replaced = 0
+
+    for xop_node in xop_nodes:
+        href = xop_node.get('href')
+        if href.startswith('cid:'):
+            href = '<%s>' % href[4:]
+
+        value = message_pack.get_by_content_id(href)
+        if not value:
+            raise ValueError("No part found for: %r" % xop_node.get('href'))
+        num_replaced += 1
+
+        xop_parent = xop_node.getparent()
+        xop_parent.remove(xop_node)
+        xop_parent.text = base64.b64encode(value.content)
+
+    return num_replaced > 0
diff --git a/src/zeep/wsdl/parse.py b/src/zeep/wsdl/parse.py
index f2b1506..d3b8eee 100644
--- a/src/zeep/wsdl/parse.py
+++ b/src/zeep/wsdl/parse.py
@@ -34,6 +34,7 @@ def parse_abstract_message(wsdl, xmlelement):
 
     """
     tns = wsdl.target_namespace
+    message_name = qname_attr(xmlelement, 'name', tns)
     parts = []
 
     for part in xmlelement.findall('wsdl:part', namespaces=NSMAP):
@@ -49,15 +50,14 @@ def parse_abstract_message(wsdl, xmlelement):
 
         except (NamespaceError, LookupError):
             raise IncompleteMessage((
-                "The wsdl:message for %r contains "
-                "invalid xsd types or elements"
-            ) % part_name)
+                "The wsdl:message for %r contains an invalid part (%r): "
+                "invalid xsd type or elements"
+            ) % (message_name.text, part_name))
 
         part = definitions.MessagePart(part_element, part_type)
         parts.append((part_name, part))
 
     # Create the object, add the parts and return it
-    message_name = qname_attr(xmlelement, 'name', tns)
     msg = definitions.AbstractMessage(message_name)
     for part_name, part in parts:
         msg.add_part(part_name, part)
diff --git a/src/zeep/wsdl/utils.py b/src/zeep/wsdl/utils.py
index e73ac20..37537e2 100644
--- a/src/zeep/wsdl/utils.py
+++ b/src/zeep/wsdl/utils.py
@@ -23,7 +23,7 @@ def get_or_create_header(envelope):
 
 def etree_to_string(node):
     return etree.tostring(
-        node, pretty_print=True, xml_declaration=True, encoding='utf-8')
+        node, pretty_print=False, xml_declaration=True, encoding='utf-8')
 
 
 def url_http_to_https(value):
diff --git a/src/zeep/wsdl/wsdl.py b/src/zeep/wsdl/wsdl.py
index bc43806..7a32d1e 100644
--- a/src/zeep/wsdl/wsdl.py
+++ b/src/zeep/wsdl/wsdl.py
@@ -389,7 +389,8 @@ class Definition(object):
 
         """
         result = {}
-        if not getattr(self.wsdl.transport, 'supports_async', False):
+
+        if not getattr(self.wsdl.transport, 'binding_classes', None):
             from zeep.wsdl import bindings
             binding_classes = [
                 bindings.Soap11Binding,
@@ -398,11 +399,7 @@ class Definition(object):
                 bindings.HttpPostBinding,
             ]
         else:
-            from zeep.asyncio import bindings  # Python 3.5+ syntax
-            binding_classes = [
-                bindings.AsyncSoap11Binding,
-                bindings.AsyncSoap12Binding,
-            ]
+            binding_classes = self.wsdl.transport.binding_classes
 
         for binding_node in doc.findall('wsdl:binding', namespaces=NSMAP):
             # Detect the binding type
diff --git a/src/zeep/wsse/signature.py b/src/zeep/wsse/signature.py
index ccc8e18..e2d448b 100644
--- a/src/zeep/wsse/signature.py
+++ b/src/zeep/wsse/signature.py
@@ -25,22 +25,23 @@ except ImportError:
 # SOAP envelope
 SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/'
 
+
 def _read_file(f_name):
     with open(f_name, "rb") as f:
         return f.read()
 
+
 def _make_sign_key(key_data, cert_data, password):
-    key = xmlsec.Key.from_memory(key_data,
-                                 xmlsec.KeyFormat.PEM, password)
-    key.load_cert_from_memory(cert_data,
-                              xmlsec.KeyFormat.PEM)
+    key = xmlsec.Key.from_memory(key_data, xmlsec.KeyFormat.PEM, password)
+    key.load_cert_from_memory(cert_data, xmlsec.KeyFormat.PEM)
     return key
 
+
 def _make_verify_key(cert_data):
-    key = xmlsec.Key.from_memory(cert_data,
-                                 xmlsec.KeyFormat.CERT_PEM, None)
+    key = xmlsec.Key.from_memory(cert_data, xmlsec.KeyFormat.CERT_PEM, None)
     return key
 
+
 class MemorySignature(object):
     """Sign given SOAP envelope with WSSE sig using given key and cert."""
 
@@ -61,13 +62,14 @@ class MemorySignature(object):
         _verify_envelope_with_key(envelope, key)
         return envelope
 
+
 class Signature(MemorySignature):
     """Sign given SOAP envelope with WSSE sig using given key file and cert file."""
 
     def __init__(self, key_file, certfile, password=None):
-        super(Signature, self).__init__(_read_file(key_file),
-                                        _read_file(certfile),
-                                        password)
+        super(Signature, self).__init__(
+            _read_file(key_file), _read_file(certfile), password)
+
 
 def check_xmlsec_import():
     if xmlsec is None:
@@ -170,7 +172,9 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
     key = _make_sign_key(_read_file(keyfile), _read_file(certfile), password)
     return _sign_envelope_with_key(envelope, key)
 
+
 def _sign_envelope_with_key(envelope, key):
+    soap_env = detect_soap_env(envelope)
 
     # Create the Signature node.
     signature = xmlsec.template.create(
@@ -189,17 +193,13 @@ def _sign_envelope_with_key(envelope, key):
     # Insert the Signature node in the wsse:Security header.
     security = get_security_header(envelope)
     security.insert(0, signature)
+    security.append(etree.Element(QName(ns.WSU, 'Timestamp')))
 
     # Perform the actual signing.
     ctx = xmlsec.SignatureContext()
     ctx.key = key
-
-    security.append(etree.Element(QName(ns.WSU, 'Timestamp')))
-
-    soap_env = detect_soap_env(envelope)
     _sign_node(ctx, signature, envelope.find(QName(soap_env, 'Body')))
     _sign_node(ctx, signature, security.find(QName(ns.WSU, 'Timestamp')))
-
     ctx.sign(signature)
 
     # Place the X509 data inside a WSSE SecurityTokenReference within
@@ -223,11 +223,12 @@ def verify_envelope(envelope, certfile):
     key = _make_verify_key(_read_file(certfile))
     return _verify_envelope_with_key(envelope, key)
 
+
 def _verify_envelope_with_key(envelope, key):
     soap_env = detect_soap_env(envelope)
 
     header = envelope.find(QName(soap_env, 'Header'))
-    if not header:
+    if header is None:
         raise SignatureVerificationFailed()
 
     security = header.find(QName(ns.WSSE, 'Security'))
diff --git a/src/zeep/xsd/const.py b/src/zeep/xsd/const.py
index 75b4097..c8354cc 100644
--- a/src/zeep/xsd/const.py
+++ b/src/zeep/xsd/const.py
@@ -2,6 +2,7 @@ from lxml import etree
 
 from zeep import ns
 
+
 def xsi_ns(localname):
     return etree.QName(ns.XSI, localname)
 
@@ -21,3 +22,8 @@ class _StaticIdentity(object):
 NotSet = _StaticIdentity('NotSet')
 SkipValue = _StaticIdentity('SkipValue')
 Nil = _StaticIdentity('Nil')
+
+
+AUTO_IMPORT_NAMESPACES = [
+    'http://schemas.xmlsoap.org/soap/encoding/'
+]
diff --git a/src/zeep/xsd/elements/any.py b/src/zeep/xsd/elements/any.py
index e799191..7e01d82 100644
--- a/src/zeep/xsd/elements/any.py
+++ b/src/zeep/xsd/elements/any.py
@@ -183,7 +183,7 @@ class Any(Base):
         if self.restrict:
             expected_types = (etree._Element, dict,) + self.restrict.accepted_types
         else:
-            expected_types = (etree._Element,  dict,AnyObject)
+            expected_types = (etree._Element, dict, AnyObject)
 
         if not isinstance(value, expected_types):
             type_names = [
diff --git a/src/zeep/xsd/elements/indicators.py b/src/zeep/xsd/elements/indicators.py
index 10ccf00..601affe 100644
--- a/src/zeep/xsd/elements/indicators.py
+++ b/src/zeep/xsd/elements/indicators.py
@@ -629,7 +629,7 @@ class Group(Indicator):
         super(Group, self).__init__()
         self.child = child
         self.qname = name
-        self.name = name.localname
+        self.name = name.localname if name else None
         self.max_occurs = max_occurs
         self.min_occurs = min_occurs
 
@@ -648,7 +648,7 @@ class Group(Indicator):
 
     def clone(self, name, min_occurs=1, max_occurs=1):
         return self.__class__(
-            name=name,
+            name=None,
             child=self.child,
             min_occurs=min_occurs,
             max_occurs=max_occurs)
diff --git a/src/zeep/xsd/schema.py b/src/zeep/xsd/schema.py
index 8026009..2e86147 100644
--- a/src/zeep/xsd/schema.py
+++ b/src/zeep/xsd/schema.py
@@ -4,6 +4,8 @@ from collections import OrderedDict
 from lxml import etree
 
 from zeep import exceptions, ns
+from zeep.loader import load_external
+from zeep.xsd import const
 from zeep.xsd.elements import builtins as xsd_builtins_elements
 from zeep.xsd.types import builtins as xsd_builtins_types
 from zeep.xsd.visitor import SchemaVisitor
@@ -115,6 +117,15 @@ class Schema(object):
 
         self._prefix_map_auto = self._create_prefix_map()
 
+    def add_document_by_url(self, url):
+        schema_node = load_external(
+            url,
+            self._transport,
+            strict=self.strict)
+
+        document = self.create_new_document(schema_node, url=url)
+        document.resolve()
+
     def get_element(self, qname):
         """Return a global xsd.Element object with the given qname
 
@@ -304,6 +315,13 @@ class Schema(object):
         :rtype: list of SchemaDocument
 
         """
+        if (
+            namespace not in self._documents
+            and namespace in const.AUTO_IMPORT_NAMESPACES
+        ):
+            logger.debug("Auto importing missing known schema: %s", namespace)
+            self.add_document_by_url(namespace)
+
         if namespace not in self._documents:
             if fail_silently:
                 return []
@@ -385,7 +403,6 @@ class SchemaDocument(object):
                         "%(file)s. (via %(parent)s)"
                     ) % {
                         'item_name': exc.item_name,
-                        'item_name': exc.item_name,
                         'qname': exc.qname,
                         'file': exc.location,
                         'parent': obj.qname,
diff --git a/src/zeep/xsd/types/builtins.py b/src/zeep/xsd/types/builtins.py
index 0bbe5d9..08cd9a6 100644
--- a/src/zeep/xsd/types/builtins.py
+++ b/src/zeep/xsd/types/builtins.py
@@ -109,7 +109,12 @@ class Duration(BuiltinType, AnySimpleType):
         return isodate.duration_isoformat(value)
 
     def pythonvalue(self, value):
-        return isodate.parse_duration(value)
+        if value.startswith('PT-'):
+            value = value.replace('PT-', 'PT')
+            result = isodate.parse_duration(value)
+            return datetime.timedelta(0 - result.total_seconds())
+        else:
+            return isodate.parse_duration(value)
 
 
 class DateTime(BuiltinType, AnySimpleType):
@@ -142,6 +147,9 @@ class Time(BuiltinType, AnySimpleType):
 
     @check_no_collection
     def xmlvalue(self, value):
+        if isinstance(value, six.string_types):
+            return value
+
         if value.microsecond:
             return isodate.isostrf.strftime(value, '%H:%M:%S.%f%Z')
         return isodate.isostrf.strftime(value, '%H:%M:%S%Z')
diff --git a/src/zeep/xsd/types/complex.py b/src/zeep/xsd/types/complex.py
index 7e65a70..d65a57f 100644
--- a/src/zeep/xsd/types/complex.py
+++ b/src/zeep/xsd/types/complex.py
@@ -13,7 +13,7 @@ from zeep.xsd.elements.indicators import OrderIndicator
 from zeep.xsd.types.any import AnyType
 from zeep.xsd.types.simple import AnySimpleType
 from zeep.xsd.utils import NamePrefixGenerator
-from zeep.xsd.valueobjects import CompoundValue, ArrayValue
+from zeep.xsd.valueobjects import ArrayValue, CompoundValue
 
 logger = logging.getLogger(__name__)
 
@@ -212,6 +212,10 @@ class ComplexType(AnyType):
         if not self.elements_nested and not self.attributes:
             return
 
+        # TODO: Implement test case for this
+        if value is None:
+            value = {}
+
         if isinstance(value, ArrayValue):
             value = value.as_value_object()
 
@@ -377,6 +381,9 @@ class ComplexType(AnyType):
                 elif isinstance(element, OrderIndicator):
                     for item in reversed(base_element):
                         element.insert(0, item)
+                elif isinstance(element, Group):
+                    for item in reversed(base_element):
+                        element.child.insert(0, item)
 
             elif isinstance(self._element, Group):
                 raise NotImplementedError('TODO')
diff --git a/src/zeep/xsd/visitor.py b/src/zeep/xsd/visitor.py
index 9ba0101..a9e47c5 100644
--- a/src/zeep/xsd/visitor.py
+++ b/src/zeep/xsd/visitor.py
@@ -9,7 +9,7 @@ from zeep.loader import absolute_location, load_external
 from zeep.utils import as_qname, qname_attr
 from zeep.xsd import elements as xsd_elements
 from zeep.xsd import types as xsd_types
-from zeep.xsd.const import xsd_ns
+from zeep.xsd.const import AUTO_IMPORT_NAMESPACES, xsd_ns
 from zeep.xsd.types.unresolved import UnresolvedCustomType, UnresolvedType
 
 logger = logging.getLogger(__name__)
@@ -1138,9 +1138,11 @@ class SchemaVisitor(object):
         # that fact and handle it by auto-importing the schema if it is
         # referenced.
         if (
-            name.namespace == 'http://schemas.xmlsoap.org/soap/encoding/' and
-            not self.document.is_imported(name.namespace)
+            name.namespace in AUTO_IMPORT_NAMESPACES
+            and not self.document.is_imported(name.namespace)
         ):
+            logger.debug(
+                "Auto importing missing known schema: %s", name.namespace)
             import_node = etree.Element(
                 tags.import_,
                 namespace=name.namespace, schemaLocation=name.namespace)
diff --git a/tests/test_asyncio_transport.py b/tests/test_asyncio_transport.py
index 7aca012..0032251 100644
--- a/tests/test_asyncio_transport.py
+++ b/tests/test_asyncio_transport.py
@@ -4,7 +4,7 @@ from lxml import etree
 import aiohttp
 from aioresponses import aioresponses
 
-from zeep import cache, asyncio
+from zeep import cache, asyncio, exceptions
 
 
 @pytest.mark.requests
@@ -58,3 +58,19 @@ async def test_session_no_close(event_loop):
     transport = asyncio.AsyncTransport(loop=event_loop, session=session)
     del transport
     assert not session.closed
+
+
+ at pytest.mark.requests
+def test_http_error(event_loop):
+    transport = asyncio.AsyncTransport(loop=event_loop)
+
+    with aioresponses() as m:
+        m.get(
+            'http://tests.python-zeep.org/test.xml',
+            body='x',
+            status=500,
+        )
+        with pytest.raises(exceptions.TransportError) as exc:
+            transport.load('http://tests.python-zeep.org/test.xml')
+            assert exc.value.status_code == 500
+            assert exc.value.message is None
diff --git a/tests/test_client.py b/tests/test_client.py
index 6a45e85..9803f08 100644
--- a/tests/test_client.py
+++ b/tests/test_client.py
@@ -183,6 +183,20 @@ def test_set_context_options_timeout():
     assert obj.transport.operation_timeout is None
 
 
+def test_set_context_options_raw_response():
+    obj = client.Client('tests/wsdl_files/soap.wsdl')
+
+    assert obj.raw_response is False
+    with obj.options(raw_response=True):
+        assert obj.raw_response is True
+
+        with obj.options():
+            # Check that raw_response is not changed by default value
+            assert obj.raw_response is True
+    # Check that the original value returned
+    assert obj.raw_response is False
+
+
 @pytest.mark.requests
 def test_default_soap_headers():
     header = xsd.ComplexType(
diff --git a/tests/test_soap_multiref.py b/tests/test_soap_multiref.py
index ae9ab7e..9cbcebc 100644
--- a/tests/test_soap_multiref.py
+++ b/tests/test_soap_multiref.py
@@ -12,7 +12,7 @@ from zeep.transports import Transport
 
 
 @pytest.mark.requests
-def test_parse_soap_wsdl():
+def test_parse_multiref_soap_response():
     wsdl_file = io.StringIO(u"""
         <?xml version="1.0"?>
         <wsdl:definitions
@@ -94,7 +94,8 @@ def test_parse_soap_wsdl():
         <?xml version="1.0"?>
         <soapenv:Envelope
             xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
-            xmlns:tns="http://tests.python-zeep.org/">
+            xmlns:tns="http://tests.python-zeep.org/"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
            <soapenv:Body>
               <tns:TestOperationResponse>
                 <tns:output>
@@ -132,3 +133,135 @@ def test_parse_soap_wsdl():
     assert result.item_2.subitem_1.subitem_1 == 'foo'
     assert result.item_2.subitem_1.subitem_2 == 'bar'
     assert result.item_2.subitem_2 == 'bar'
+
+
+
+ at pytest.mark.requests
+def test_parse_multiref_soap_response_child():
+    wsdl_file = io.StringIO(u"""
+        <?xml version="1.0"?>
+        <wsdl:definitions
+          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+          xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+          xmlns:tns="http://tests.python-zeep.org/"
+          xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+          xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
+          targetNamespace="http://tests.python-zeep.org/">
+
+          <wsdl:types>
+            <xsd:schema
+                targetNamespace="http://tests.python-zeep.org/"
+                xmlns:tns="http://tests.python-zeep.org/"
+                elementFormDefault="qualified">
+              <xsd:element name="input" type="xsd:string"/>
+
+              <xsd:element name="output">
+                <xsd:complexType>
+                  <xsd:sequence>
+                    <xsd:element name="item_1" type="tns:type_1"/>
+                    <xsd:element name="item_2" type="tns:type_2"/>
+                  </xsd:sequence>
+                </xsd:complexType>
+             </xsd:element>
+
+              <xsd:complexType name="type_1">
+                <xsd:sequence>
+                  <xsd:element name="subitem_1" type="xsd:string"/>
+                  <xsd:element name="subitem_2" type="xsd:string"/>
+                  <xsd:element name="subitem_3" type="tns:type_3"/>
+                </xsd:sequence>
+              </xsd:complexType>
+              <xsd:complexType name="type_2">
+                <xsd:sequence>
+                  <xsd:element name="subitem_1" type="tns:type_1"/>
+                  <xsd:element name="subitem_2" type="xsd:string"/>
+                </xsd:sequence>
+              </xsd:complexType>
+              <xsd:complexType name="type_3" nillable="true">
+                <xsd:sequence>
+                </xsd:sequence>
+              </xsd:complexType>
+            </xsd:schema>
+          </wsdl:types>
+
+          <wsdl:message name="TestOperationRequest">
+            <wsdl:part name="response" element="tns:input"/>
+          </wsdl:message>
+
+          <wsdl:message name="TestOperationResponse">
+            <wsdl:part name="response" element="tns:output"/>
+          </wsdl:message>
+
+          <wsdl:portType name="TestPortType">
+            <wsdl:operation name="TestOperation">
+              <wsdl:input message="TestOperationRequest"/>
+              <wsdl:output message="TestOperationResponse"/>
+            </wsdl:operation>
+          </wsdl:portType>
+
+          <wsdl:binding name="TestBinding" type="tns:TestPortType">
+            <soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>
+            <wsdl:operation name="TestOperation">
+              <soap:operation soapAction=""/>
+              <wsdl:input name="TestOperationRequest">
+                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
+              </wsdl:input>
+              <wsdl:output name="TestOperationResponse">
+                <soap:body use="encoded" encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" />
+              </wsdl:output>
+            </wsdl:operation>
+          </wsdl:binding>
+          <wsdl:service name="TestService">
+            <wsdl:documentation>Test service</wsdl:documentation>
+            <wsdl:port name="TestPortType" binding="tns:TestBinding">
+              <soap:address location="http://tests.python-zeep.org/test"/>
+            </wsdl:port>
+          </wsdl:service>
+        </wsdl:definitions>
+    """.strip())
+
+    content = """
+        <?xml version="1.0"?>
+        <soapenv:Envelope
+            xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:tns="http://tests.python-zeep.org/"
+            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+           <soapenv:Body>
+              <tns:TestOperationResponse>
+                <tns:output>
+                  <tns:item_1 href="#id0"/>
+                  <tns:item_2>
+                    <tns:subitem_1>
+                      <tns:subitem_1>foo</tns:subitem_1>
+                      <tns:subitem_2>bar</tns:subitem_2>
+                    </tns:subitem_1>
+                  <tns:subitem_2>bar</tns:subitem_2>
+                  </tns:item_2>
+                </tns:output>
+              </tns:TestOperationResponse>
+
+              <multiRef id="id0">
+                <tns:subitem_1>foo</tns:subitem_1>
+                <tns:subitem_2>bar</tns:subitem_2>
+                <tns:subitem_3 xmlns:tns2="http://tests.python-zeep.org/" xsi:type="tns2:type_3"></tns:subitem_3>
+              </multiRef>
+           </soapenv:Body>
+        </soapenv:Envelope>
+    """.strip()
+
+    client = Client(wsdl_file, transport=Transport(),)
+    response = stub(
+        status_code=200,
+        headers={},
+        content=content)
+
+    operation = client.service._binding._operations['TestOperation']
+    result = client.service._binding.process_reply(
+        client, operation, response)
+
+    assert result.item_1.subitem_1 == 'foo'
+    assert result.item_1.subitem_2 == 'bar'
+    assert result.item_2.subitem_1.subitem_1 == 'foo'
+    assert result.item_2.subitem_1.subitem_2 == 'bar'
+    assert result.item_2.subitem_2 == 'bar'
+
diff --git a/tests/test_soap_xop.py b/tests/test_soap_xop.py
new file mode 100644
index 0000000..03f5ebd
--- /dev/null
+++ b/tests/test_soap_xop.py
@@ -0,0 +1,253 @@
+import io
+from requests_toolbelt.multipart.decoder import MultipartDecoder
+from pretend import stub
+from lxml import etree
+from tests.utils import load_xml, assert_nodes_equal
+from zeep.wsdl.attachments import MessagePack
+
+
+from zeep.wsdl.messages import xop
+
+
+def test_rebuild_xml():
+    data = '\r\n'.join(line.strip() for line in """
+        --MIME_boundary
+        Content-Type: application/soap+xml; charset=UTF-8
+        Content-Transfer-Encoding: 8bit
+        Content-ID: <claim at insurance.com>
+
+        <soap:Envelope
+        xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
+        xmlns:xop='http://www.w3.org/2004/08/xop/include'
+        xmlns:xop-mime='http://www.w3.org/2005/05/xmlmime'>
+        <soap:Body>
+        <submitClaim>
+        <accountNumber>5XJ45-3B2</accountNumber>
+        <eventType>accident</eventType>
+        <image xop-mime:content-type='image/jpeg'><xop:Include href="cid:image at insurance.com"/></image>
+        </submitClaim>
+        </soap:Body>
+        </soap:Envelope>
+
+        --MIME_boundary
+        Content-Type: image/jpeg
+        Content-Transfer-Encoding: binary
+        Content-ID: <image at insurance.com>
+
+        ...binary JPG image...
+
+        --MIME_boundary--
+    """.splitlines()).encode('utf-8')
+
+    response = stub(
+        status_code=200,
+        content=data,
+        encoding=None,
+        headers={
+            'Content-Type': 'multipart/related; boundary=MIME_boundary; type="application/soap+xml"; start="<claim at insurance.com>" 1'
+        }
+    )
+    client = stub(
+        transport=None,
+        wsdl=stub(strict=True),
+        xml_huge_tree=False)
+
+
+    decoder = MultipartDecoder(
+        response.content, response.headers['Content-Type'], 'utf-8')
+
+    document = etree.fromstring(decoder.parts[0].content)
+    message_pack = MessagePack(parts=decoder.parts[1:])
+    xop.process_xop(document, message_pack)
+
+    expected = """
+        <soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xop="http://www.w3.org/2004/08/xop/include" xmlns:xop-mime="http://www.w3.org/2005/05/xmlmime">
+          <soap:Body>
+            <submitClaim>
+            <accountNumber>5XJ45-3B2</accountNumber>
+            <eventType>accident</eventType>
+            <image xop-mime:content-type="image/jpeg">Li4uYmluYXJ5IEpQRyBpbWFnZS4uLg==</image>
+            </submitClaim>
+          </soap:Body>
+        </soap:Envelope>
+    """
+    assert_nodes_equal(etree.tostring(document), expected)
+
+
+
+import pytest
+import requests_mock
+
+from six import StringIO
+
+from zeep import Client
+from zeep.transports import Transport
+
+
+def test_xop():
+    wsdl_main = StringIO("""
+        <?xml version="1.0"?>
+        <wsdl:definitions
+          xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+          xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+          xmlns:tns="http://tests.python-zeep.org/xsd-main"
+          xmlns:sec="http://tests.python-zeep.org/wsdl-secondary"
+          xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+          xmlns:wsdlsoap="http://schemas.xmlsoap.org/wsdl/soap/"
+          targetNamespace="http://tests.python-zeep.org/xsd-main">
+          <wsdl:types>
+            <xsd:schema
+                targetNamespace="http://tests.python-zeep.org/xsd-main"
+                xmlns:tns="http://tests.python-zeep.org/xsd-main">
+              <xsd:complexType name="responseTypeSimple">
+                <xsd:sequence>
+                  <xsd:element name="BinaryData" type="xsd:base64Binary"/>
+                </xsd:sequence>
+              </xsd:complexType>
+              <xsd:complexType name="BinaryDataType">
+                <xsd:simpleContent>
+                  <xsd:extension base="xsd:base64Binary">
+                    <xsd:anyAttribute namespace="##other" processContents="lax"/>
+                  </xsd:extension>
+                </xsd:simpleContent>
+              </xsd:complexType>
+              <xsd:complexType name="responseTypeComplex">
+                <xsd:sequence>
+                  <xsd:element name="BinaryData" type="tns:BinaryDataType"/>
+                </xsd:sequence>
+              </xsd:complexType>
+              <xsd:element name="input" type="xsd:string"/>
+              <xsd:element name="resultSimple" type="tns:responseTypeSimple"/>
+              <xsd:element name="resultComplex" type="tns:responseTypeComplex"/>
+            </xsd:schema>
+          </wsdl:types>
+
+          <wsdl:message name="dummyRequest">
+            <wsdl:part name="response" element="tns:input"/>
+          </wsdl:message>
+          <wsdl:message name="dummyResponseSimple">
+            <wsdl:part name="response" element="tns:resultSimple"/>
+          </wsdl:message>
+          <wsdl:message name="dummyResponseComplex">
+            <wsdl:part name="response" element="tns:resultComplex"/>
+          </wsdl:message>
+
+          <wsdl:portType name="TestPortType">
+            <wsdl:operation name="TestOperation1">
+              <wsdl:input message="dummyRequest"/>
+              <wsdl:output message="dummyResponseSimple"/>
+            </wsdl:operation>
+            <wsdl:operation name="TestOperation2">
+              <wsdl:input message="dummyRequest"/>
+              <wsdl:output message="dummyResponseComplex"/>
+            </wsdl:operation>
+          </wsdl:portType>
+
+          <wsdl:binding name="TestBinding" type="tns:TestPortType">
+            <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+            <wsdl:operation name="TestOperation1">
+              <soap:operation soapAction="urn:dummyRequest"/>
+              <wsdl:input>
+                <soap:body use="literal"/>
+              </wsdl:input>
+              <wsdl:output>
+                <soap:body use="literal"/>
+              </wsdl:output>
+            </wsdl:operation>
+            <wsdl:operation name="TestOperation2">
+              <soap:operation soapAction="urn:dummyRequest"/>
+              <wsdl:input>
+                <soap:body use="literal"/>
+              </wsdl:input>
+              <wsdl:output>
+                <soap:body use="literal"/>
+              </wsdl:output>
+            </wsdl:operation>
+          </wsdl:binding>
+          <wsdl:service name="TestService">
+            <wsdl:documentation>Test service</wsdl:documentation>
+            <wsdl:port name="TestPortType" binding="tns:TestBinding">
+              <soap:address location="http://tests.python-zeep.org/test"/>
+            </wsdl:port>
+          </wsdl:service>
+        </wsdl:definitions>
+    """.strip())
+
+    client = Client(wsdl_main, transport=Transport())
+    service = client.create_service(
+        "{http://tests.python-zeep.org/xsd-main}TestBinding",
+        "http://tests.python-zeep.org/test")
+
+    content_type = 'multipart/related; boundary="boundary"; type="application/xop+xml"; start="<soap:Envelope>"; start-info="application/soap+xml; charset=utf-8"'
+
+    response1 = '\r\n'.join(line.strip() for line in """
+        Content-Type: application/xop+xml; charset=utf-8; type="application/soap+xml"
+        Content-Transfer-Encoding: binary
+        Content-ID: <soap:Envelope>
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <soap:Envelope
+            xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:xop="http://www.w3.org/2004/08/xop/include"
+            xmlns:test="http://tests.python-zeep.org/xsd-main">
+            <soap:Body>
+                <test:resultSimple>
+                    <test:BinaryData>
+                        <xop:Include href="cid:id4"/>
+                    </test:BinaryData>
+                </test:resultSimple>
+            </soap:Body>
+        </soap:Envelope>
+        --boundary
+        Content-Type: application/binary
+        Content-Transfer-Encoding: binary
+        Content-ID: <id4>
+
+        BINARYDATA
+        --boundary--
+    """.splitlines())
+
+    response2 = '\r\n'.join(line.strip() for line in """
+        Content-Type: application/xop+xml; charset=utf-8; type="application/soap+xml"
+        Content-Transfer-Encoding: binary
+        Content-ID: <soap:Envelope>
+
+        <?xml version="1.0" encoding="UTF-8"?>
+        <soap:Envelope
+            xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
+            xmlns:xop="http://www.w3.org/2004/08/xop/include"
+            xmlns:test="http://tests.python-zeep.org/xsd-main">
+            <soap:Body>
+                <test:resultComplex>
+                    <test:BinaryData>
+                        <xop:Include href="cid:id4"/>
+                    </test:BinaryData>
+                </test:resultComplex>
+            </soap:Body>
+        </soap:Envelope>
+        --boundary
+        Content-Type: application/binary
+        Content-Transfer-Encoding: binary
+        Content-ID: <id4>
+
+        BINARYDATA
+
+        --boundary--
+    """.splitlines())
+
+    print(response1)
+    with requests_mock.mock() as m:
+        m.post('http://tests.python-zeep.org/test',
+            content=response2.encode("utf-8"),
+            headers={"Content-Type": content_type})
+        result = service.TestOperation2("")
+        assert result["_value_1"] == "BINARYDATA".encode()
+
+        m.post(
+            'http://tests.python-zeep.org/test',
+            content=response1.encode("utf-8"),
+            headers={"Content-Type": content_type})
+        result = service.TestOperation1("")
+        assert result == "BINARYDATA".encode()
+
+
diff --git a/tests/test_tornado_transport.py b/tests/test_tornado_transport.py
new file mode 100644
index 0000000..0362589
--- /dev/null
+++ b/tests/test_tornado_transport.py
@@ -0,0 +1,57 @@
+import pytest
+from pretend import stub
+from lxml import etree
+from tornado.httpclient import HTTPResponse, HTTPRequest
+from tornado.testing import gen_test, AsyncTestCase
+from tornado.concurrent import Future
+
+from mock import patch
+from zeep.tornado import TornadoAsyncTransport
+
+
+class TornadoAsyncTransportTest(AsyncTestCase):
+    @pytest.mark.requests
+    def test_no_cache(self):
+        transport = TornadoAsyncTransport()
+        assert transport.cache is None
+
+    @pytest.mark.requests
+    @patch('tornado.httpclient.HTTPClient.fetch')
+    @gen_test
+    def test_load(self, mock_httpclient_fetch):
+        cache = stub(get=lambda url: None, add=lambda url, content: None)
+        response = HTTPResponse(HTTPRequest('http://tests.python-zeep.org/test.xml'), 200)
+        response.buffer = True
+        response._body = 'x'
+        mock_httpclient_fetch.return_value = response
+
+        transport = TornadoAsyncTransport(cache=cache)
+
+        result = transport.load('http://tests.python-zeep.org/test.xml')
+
+        assert result == 'x'
+
+    @pytest.mark.requests
+    @patch('tornado.httpclient.AsyncHTTPClient.fetch')
+    @gen_test
+    def test_post(self, mock_httpclient_fetch):
+        cache = stub(get=lambda url: None, add=lambda url, content: None)
+
+        response = HTTPResponse(HTTPRequest('http://tests.python-zeep.org/test.xml'), 200)
+        response.buffer = True
+        response._body = 'x'
+        http_fetch_future = Future()
+        http_fetch_future.set_result(response)
+        mock_httpclient_fetch.return_value = http_fetch_future
+
+        transport = TornadoAsyncTransport(cache=cache)
+
+        envelope = etree.Element('Envelope')
+
+        result = yield transport.post_xml(
+            'http://tests.python-zeep.org/test.xml',
+            envelope=envelope,
+            headers={})
+
+        assert result.content == 'x'
+        assert result.status_code == 200
diff --git a/tests/test_wsdl_messages_document.py b/tests/test_wsdl_messages_document.py
index 151aef3..0b9f91c 100644
--- a/tests/test_wsdl_messages_document.py
+++ b/tests/test_wsdl_messages_document.py
@@ -1267,3 +1267,68 @@ def test_serialize_any_type():
     deserialized = operation.input.deserialize(serialized.content)
 
     assert deserialized == 'ah1'
+
+
+def test_empty_input_parse():
+    wsdl_content = StringIO("""
+    <wsdl:definitions
+        xmlns:tns="http://tests.python-zeep.org/"
+        xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+        xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+        xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+        targetNamespace="http://tests.python-zeep.org/">
+      <wsdl:types>
+        <schema xmlns="http://www.w3.org/2001/XMLSchema"
+            elementFormDefault="qualified"
+            targetNamespace="http://tests.python-zeep.org/">
+        <element name="Result">
+            <complexType>
+            <sequence>
+                <element name="item" type="xsd:string"/>
+            </sequence>
+            </complexType>
+        </element>
+        </schema>
+      </wsdl:types>
+      <wsdl:message name="Request"></wsdl:message>
+      <wsdl:message name="Response">
+        <wsdl:part element="tns:Result" name="Result"/>
+      </wsdl:message>
+      <wsdl:portType name="PortType">
+        <wsdl:operation name="getResult">
+          <wsdl:input message="tns:Request" name="getResultRequest"/>
+          <wsdl:output message="tns:Response" name="getResultResponse"/>
+        </wsdl:operation>
+      </wsdl:portType>
+      <wsdl:binding name="Binding" type="tns:PortType">
+        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
+        <wsdl:operation name="getResult">
+          <soap:operation soapAction=""/>
+          <wsdl:input name="Result">
+            <soap:body use="literal"/>
+          </wsdl:input>
+          </wsdl:operation>
+      </wsdl:binding>
+      <wsdl:service name="Service">
+        <wsdl:port binding="tns:Binding" name="ActiveStations">
+        <soap:address location="https://opendap.co-ops.nos.noaa.gov/axis/services/ActiveStations"/>
+        </wsdl:port>
+      </wsdl:service>
+    </wsdl:definitions>
+    """.strip())
+
+    root = wsdl.Document(wsdl_content, None)
+
+    binding = root.bindings['{http://tests.python-zeep.org/}Binding']
+    operation = binding.get('getResult')
+    assert operation.input.signature() == ''
+
+    serialized = operation.input.serialize()
+    expected = """
+        <?xml version="1.0"?>
+        <soap-env:Envelope
+            xmlns:soap-env="http://schemas.xmlsoap.org/soap/envelope/">
+          <soap-env:Body/>
+        </soap-env:Envelope>
+    """
+    assert_nodes_equal(expected, serialized.content)
diff --git a/tests/test_wsdl_soap.py b/tests/test_wsdl_soap.py
index e80c152..506f603 100644
--- a/tests/test_wsdl_soap.py
+++ b/tests/test_wsdl_soap.py
@@ -183,9 +183,11 @@ def test_wrong_content():
         headers={}
     )
 
-    with pytest.raises(TransportError):
+    with pytest.raises(TransportError) as exc:
         binding.process_reply(
             client, binding.get('GetLastTradePrice'), response)
+    assert 200 == exc.value.status_code
+    assert data == exc.value.content
 
 
 def test_wrong_no_unicode_content():
@@ -204,10 +206,35 @@ def test_wrong_no_unicode_content():
         headers={}
     )
 
-    with pytest.raises(TransportError):
+    with pytest.raises(TransportError) as exc:
         binding.process_reply(
             client, binding.get('GetLastTradePrice'), response)
 
+    assert 200 == exc.value.status_code
+    assert data == exc.value.content
+
+
+def test_http_error():
+    data = """
+        Unauthorized!
+    """.strip()
+
+    client = Client('tests/wsdl_files/soap.wsdl')
+    binding = client.service._binding
+
+    response = stub(
+        status_code=401,
+        content=data,
+        encoding='utf-8',
+        headers={}
+    )
+
+    with pytest.raises(TransportError) as exc:
+        binding.process_reply(
+            client, binding.get('GetLastTradePrice'), response)
+    assert 401 == exc.value.status_code
+    assert data == exc.value.content
+
 
 def test_mime_multipart():
     data = '\r\n'.join(line.strip() for line in """
diff --git a/tests/test_wsse_signature.py b/tests/test_wsse_signature.py
index 503e90e..9b910d1 100644
--- a/tests/test_wsse_signature.py
+++ b/tests/test_wsse_signature.py
@@ -2,10 +2,11 @@ import os
 import sys
 
 import pytest
+from lxml import etree
 
 from tests.utils import load_xml
-from zeep.exceptions import SignatureVerificationFailed
 from zeep import wsse
+from zeep.exceptions import SignatureVerificationFailed
 from zeep.wsse import signature
 
 DS_NS = 'http://www.w3.org/2000/09/xmldsig#'
diff --git a/tests/test_xsd_builtins.py b/tests/test_xsd_builtins.py
index 17ae68e..6164732 100644
--- a/tests/test_xsd_builtins.py
+++ b/tests/test_xsd_builtins.py
@@ -151,6 +151,7 @@ class TestTime:
         instance = builtins.Time()
         value = datetime.time(21, 14, 42)
         assert instance.xmlvalue(value) == '21:14:42'
+        assert instance.xmlvalue("21:14:42") == '21:14:42'
 
     def test_pythonvalue(self):
         instance = builtins.Time()
diff --git a/tests/test_xsd_indicators_group.py b/tests/test_xsd_indicators_group.py
index 68d5924..4f35eb0 100644
--- a/tests/test_xsd_indicators_group.py
+++ b/tests/test_xsd_indicators_group.py
@@ -415,3 +415,39 @@ def test_xml_group_methods():
         '{http://tests.python-zeep.org/}Group(city: xsd:string, country: xsd:string)')
 
     assert len(list(Group)) == 2
+
+
+def test_xml_group_extension():
+    schema = xsd.Schema(load_xml("""
+        <?xml version="1.0"?>
+        <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
+                   xmlns:tns="http://tests.python-zeep.org/"
+                   targetNamespace="http://tests.python-zeep.org/"
+                   elementFormDefault="unqualified">
+
+          <xs:group name="Group">
+            <xs:sequence>
+              <xs:element name="item_2" type="xs:string" />
+              <xs:element name="item_3" type="xs:string" />
+            </xs:sequence>
+          </xs:group>
+
+          <xs:complexType name="base">
+            <xs:sequence>
+              <xs:element name="item_1" type="xs:string" minOccurs="0"/>
+            </xs:sequence>
+          </xs:complexType>
+
+          <xs:complexType name="SubGroup">
+            <xs:complexContent>
+              <xs:extension base="tns:base">
+                <xs:group ref="tns:Group"/>
+              </xs:extension>
+            </xs:complexContent>
+          </xs:complexType>
+        </xs:schema>
+    """))
+    SubGroup = schema.get_type('{http://tests.python-zeep.org/}SubGroup')
+    assert SubGroup.signature(schema) == (
+        'ns0:SubGroup(item_1: xsd:string, item_2: xsd:string, item_3: xsd:string)')
+    SubGroup(item_1='een', item_2='twee', item_3='drie')
-- 
python-zeep



More information about the tryton-debian-vcs mailing list