[tryton-debian-vcs] python-zeep branch debian updated. debian/2.1.1-1-10-gcae95fb
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Mon Sep 4 19:01:37 UTC 2017
The following commit has been merged in the debian branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/python-zeep.git;a=commitdiff;h=debian/2.1.1-1-10-gcae95fb
commit cae95fb08b92dd39d8fcc228c4c52adf04750a8d
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Sep 4 20:45:36 2017 +0200
Releasing debian version 2.4.0-1.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/debian/changelog b/debian/changelog
index 040fcaf..16b05f5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,18 @@
+python-zeep (2.4.0-1) unstable; urgency=medium
+
+ * Update the signing-key to the current valid key.
+ * Change the maintainer address to tryton-debian at lists.alioth.debian.org
+ (Closes: #865109).
+ * Use the preferred https URL format in the copyright file.
+ * Bump the Standards-Version to 4.0.1.
+ * Put the minimal version constraint as well for python3-six.
+ * Bump the Standards-Version to 4.1.0, no changes needed.
+ * Merging upstream version 2.4.0 (Closes: #874215).
+ * Add new Build-Depends.
+ * Refresh 01-remove-failing-tests.patch.
+
+ -- Mathias Behrle <mbehrle at debian.org> Mon, 04 Sep 2017 19:01:26 +0200
+
python-zeep (2.1.1-1) unstable; urgency=medium
* Merging upstream version 0.24.0.
commit 9e676427d4ade58d63b3dca76c0d7b7f29f360cb
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Sep 4 19:01:07 2017 +0200
Refresh 01-remove-failing-tests.patch.
diff --git a/debian/patches/01-remove-failing-tests.patch b/debian/patches/01-remove-failing-tests.patch
index d5f8ebf..f7b0fd5 100644
--- a/debian/patches/01-remove-failing-tests.patch
+++ b/debian/patches/01-remove-failing-tests.patch
@@ -7,15 +7,16 @@ Forwarded: not needed
--- a/tests/test_wsse_signature.py
+++ /dev/null
-@@ -1,112 +0,0 @@
+@@ -1,113 +0,0 @@
-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#'
@@ -122,14 +123,14 @@ Forwarded: not needed
- plugin.verify(envelope)
--- a/tests/test_asyncio_transport.py
+++ /dev/null
-@@ -1,60 +0,0 @@
+@@ -1,76 +0,0 @@
-import pytest
-from pretend import stub
-from lxml import etree
-import aiohttp
-from aioresponses import aioresponses
-
--from zeep import cache, asyncio
+-from zeep import cache, asyncio, exceptions
-
-
- at pytest.mark.requests
@@ -183,3 +184,19 @@ Forwarded: not needed
- 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
commit 32d330831304b310449499edc26618e8eed11c07
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Sep 4 18:32:05 2017 +0200
Add new Build-Depends.
diff --git a/debian/control b/debian/control
index 857db4f..2c1a4b5 100644
--- a/debian/control
+++ b/debian/control
@@ -22,6 +22,7 @@ Build-Depends:
python-pretend,
python-pytest,
python-pytest-cov,
+ python-pytest-tornado,
python-requests,
python-requests-mock,
python-requests-toolbelt,
@@ -42,6 +43,7 @@ Build-Depends:
python3-pretend,
python3-pytest,
python3-pytest-cov,
+ python3-pytest-tornado,
python3-requests,
python3-requests-mock,
python3-requests-toolbelt,
commit 9ee5ee5a7cf1ee09a4c94a36001dff5fdb1424f3
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Sep 4 16:23:14 2017 +0200
Merging upstream version 2.4.0.
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')
commit eb954e9b3f68ca2afc4b3e2e250c4b3c70e452e7
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Sep 4 16:20:07 2017 +0200
Bump the Standards-Version to 4.1.0, no changes needed.
diff --git a/debian/control b/debian/control
index e0e2036..857db4f 100644
--- a/debian/control
+++ b/debian/control
@@ -48,7 +48,7 @@ Build-Depends:
python3-setuptools,
python3-six (>= 1.9.0-3~),
python3-tz,
-Standards-Version: 4.0.1
+Standards-Version: 4.1.0
Homepage: https://github.com/mvantellingen/python-zeep
Vcs-Browser: https://anonscm.debian.org/cgit/tryton/python-zeep.git
Vcs-Git: https://anonscm.debian.org/cgit/tryton/python-zeep.git
--
python-zeep
More information about the tryton-debian-vcs
mailing list