Merging upstream version 2.4.0.
This commit is contained in:
parent
eb954e9b3f
commit
9ee5ee5a7c
26
CHANGES
26
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
|
||||
|
|
|
@ -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
|
||||
* Julien Marechal
|
||||
* Mike Fiedler
|
||||
* Dave Wapstra
|
||||
* OrangGeeGee
|
||||
* Stefano Parmesan
|
||||
* Ondřej Lanč
|
||||
* Jan Murre
|
||||
* Ben Tucker
|
||||
* Stefano Parmesan
|
||||
* Julien Marechal
|
||||
* Dave Wapstra
|
||||
* 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
|
||||
|
||||
|
||||
|
|
6
PKG-INFO
6
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[bumpversion]
|
||||
current_version = 2.1.1
|
||||
current_version = 2.4.0
|
||||
commit = true
|
||||
tag = true
|
||||
tag_name = {new_version}
|
||||
|
|
17
setup.py
17
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',
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
from .transport import * # noqa
|
||||
from .bindings import * # noqa
|
|
@ -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
|
|
@ -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
|
|
@ -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):
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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/'
|
||||
]
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 []
|
||||
|
@ -384,7 +402,6 @@ class SchemaDocument(object):
|
|||
"Unable to resolve %(item_name)s %(qname)s in "
|
||||
"%(file)s. (via %(parent)s)"
|
||||
) % {
|
||||
'item_name': exc.item_name,
|
||||
'item_name': exc.item_name,
|
||||
'qname': exc.qname,
|
||||
'file': exc.location,
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
@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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
||||
|
||||
@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'
|
||||
|
||||
|
|
|
@ -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@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@insurance.com"/></image>
|
||||
</submitClaim>
|
||||
</soap:Body>
|
||||
</soap:Envelope>
|
||||
|
||||
--MIME_boundary
|
||||
Content-Type: image/jpeg
|
||||
Content-Transfer-Encoding: binary
|
||||
Content-ID: <image@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@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()
|
||||
|
||||
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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 """
|
||||
|
|
|
@ -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#'
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in New Issue