[tryton-debian-vcs] python-zeep branch upstream updated. debian/0.23.0-1-3-gd5a4b86
Mathias Behrle
tryton-debian-vcs at alioth.debian.org
Tue Jun 13 12:01:03 UTC 2017
The following commit has been merged in the upstream branch:
https://alioth.debian.org/plugins/scmgit/cgi-bin/gitweb.cgi/?p=tryton/python-zeep.git;a=commitdiff;h=debian/0.23.0-1-3-gd5a4b86
commit d5a4b860ebcd1e2ccf720b6ee929af8e2539c72d
Author: Mathias Behrle <mathiasb at m9s.biz>
Date: Mon Jun 12 18:31:41 2017 +0200
Adding upstream version 2.1.1.
Signed-off-by: Mathias Behrle <mathiasb at m9s.biz>
diff --git a/CHANGES b/CHANGES
index 95e55c4..8bbebc5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,119 @@
+2.1.1 (2017-06-11)
+------------------
+ - Fix previous release, it contained an incorrect dependency (Mock 2.1.) due
+ to bumpversion :-(
+
+
+2.1.0 (2017-06-11)
+------------------
+ - Fix recursion error while creating the signature for a global element when
+ it references itself (via ref attribute).
+ - Update Client.create_message() to apply plugins and wsse (#465)
+ - Fix handling unknown xsi types when parsing elements using xsd:anyType (#455)
+
+
+2.0.0 (2017-05-22)
+------------------
+
+This is a major release, and contains a number of backwards incompatible
+changes to the API.
+
+ - Default values of optional elements are not set by default anymore (#423)
+ - Refactor the implementation of wsdl:arrayType too make the API more
+ pythonic (backwards incompatible).
+ - The call signature for Client.create_message() was changed. It now requires
+ the service argument:
+
+ ``Client.create_message(service, operation_name, *args, **kwargs)``
+
+ - Choice elements now only work with keyword arguments and raise an exception
+ if positional arguments are passed (#439)
+ - Implement initial multiref support for SOAP RPC (#326). This was done using
+ really good real-world tests from vstoykov (thanks!)
+ - Fix exception on empty SOAP response (#442)
+ - Fix XSD default values for boolean types (Bartek Wójcicki, #386)
+
+
+1.6.0 (2017-04-27)
+------------------
+ - Implement ValueObject.__json__ for json serialization (#258)
+ - Improve handling of unexpected elements for soap:header (#378)
+ - Accept unexpected elements in complexTypes when strict is False
+ - Fix elementFormDefault/attributeFormDefault for xsd:includes (#426)
+
+
+1.5.0 (2017-04-22)
+------------------
+ - Fix issue where values of indicators (sequence/choice/all) would
+ write to the same internal dict. (#425)
+ - Set default XML parse mode to strict as was intended (#332)
+ - Add support for pickling value objects (#417)
+ - Add explicit Nil value via ``zeep.xsd.Nil`` (#424)
+ - Add xml_huge_tree kwarg to the Client() to enable lxml's huge_tree mode,
+ this is disabled by default (#332)
+ - Add support to pass base-types to type extensions (#416)
+ - Handle wsdl errors more gracefully by disabling the invalid operations
+ instead of raising an exception (#407, #387)
+
+
+1.4.1 (2017-04-01)
+------------------
+ - The previous release (1.4.0) contained an incorrect dependency due to
+ bumpversion moving all 1.3.0 versions to 1.4.0. This fixes it.
+
+
+1.4.0 (2017-04-01)
+------------------
+ - Hardcode the xml prefix to the xml namespace as defined in the specs (#367)
+ - Fix parsing of unbound sequences within xsd choices (#380)
+ - Use logger.debug() for debug related logging (#369)
+ - Add the ``Client.raw_response`` option to let zeep return the raw
+ transport response (requests.Response) instead of trying to parse it.
+ - Handle minOccurs/maxOccurs properlhy for xsd:Group elements. This also
+ fixes a bug in the xsd:Choice handling for multiple elements (#374, #410)
+ - Fix raising XMLSyntaxError when loading invalid XML (Antanas Sinica, #396)
+
+
+1.3.0 (2017-03-14)
+------------------
+ - Add support for nested xsd:choice elements (#370)
+ - Fix unresolved elements for xsd:extension, this was a regression introduced
+ in 1.2.0 (#377)
+
+
+1.2.0 (2017-03-12)
+------------------
+ - Add flag to disable strict mode in the Client. This allows zeep to better
+ work with non standard compliant SOAP Servers. See the documentation for
+ usage and potential downsides.
+ - Minor refactor of resolving of elements for improved performance
+ - Support the SOAP 1.2 'http://www.w3.org/2003/05/soap/bindings/HTTP/'
+ transport uri (#355)
+ - Fallback to matching wsdl lookups to matching when the target namespace is
+ empty (#356)
+ - Improve the handling of xsd:includes, the default namespace of the parent
+ schema is now also used during resolving of the included schema. (#360)
+ - Properly propagate the global flag for complex types when an xsd:restriction
+ is used (#360)
+ - Filter out duplicate types and elements when dump the wsdl schema (#360)
+ - Add ``zeep.CachingClient()`` which enables the SqliteCache by default
+
+
+1.1.0 (2017-02-18)
+------------------
+ - Fix an attribute error when an complexType used xsd:anyType as base
+ restriction (#352)
+ - Update asyncio transport to return requests.Response objects (#335)
+
+
+1.0.0 (2017-01-31)
+------------------
+ - Use cgi.parse_header() to extract media_type for multipart/related checks
+ (#327)
+ - Don't ignore nil elements, instead return None when parsing xml (#328)
+ - Fix regression when using WSA with an older lxml version (#197)
+
+
0.27.0 (2017-01-28)
-------------------
- Add support for SOAP attachments (multipart responses). (Dave Wapstra, #302)
@@ -11,13 +127,13 @@
This release again introduces some backwords incompatibilties. The next release
will hopefully be 1.0 which will introduce semver.
- - **backwards-incompatible**: The Transport class now accepts a
+ - **backwards-incompatible**: The Transport class now accepts a
``requests.Session()`` object instead of ``http_auth`` and ``verify``. This
allows for more flexibility.
- **backwards-incompatible**: Zeep no longer sets a default cache backend.
Please see http://docs.python-zeep.org/en/master/transport.html#caching for
information about how to configure a cache.
- - Add ``zeep.xsd.SkipValue`` which instructs the serialize to ignore the
+ - Add ``zeep.xsd.SkipValue`` which instructs the serialize to ignore the
element.
- Support duplicate target namespaces in the wsdl definition (#320)
- Fix resolving element/types for xsd schema's with duplicate tns (#319)
@@ -40,12 +156,12 @@ will hopefully be 1.0 which will introduce semver.
type. Instead log a message (#273)
- Fix serializing etree.Element instances in the helpers.serialize function
(#255)
- - Fix infinite loop during parsing of xsd.Sequence where max_occurs is
+ - Fix infinite loop during parsing of xsd.Sequence where max_occurs is
unbounded (#256)
- Make the xsd.Element name kwarg required
- - Improve handling of the xsd:anyType element when passed instances of
+ - Improve handling of the xsd:anyType element when passed instances of
complexType's (#252)
- - Silently ignore unsupported binding transports instead of an hard error
+ - Silently ignore unsupported binding transports instead of an hard error
(#277)
- Support microseconds for xsd.dateTime and xsd.Time (#280)
- Don't mutate passed values to the zeep operations (#280)
@@ -56,7 +172,7 @@ will hopefully be 1.0 which will introduce semver.
- Add Client.set_default_soapheaders() to set soapheaders which are to be used
on all operations done via the client object.
- Add basic support for asyncio using aiohttp. Many thanks to chrisimcevoy
- for the initial implementation! Please see
+ for the initial implementation! Please see
https://github.com/mvantellingen/python-zeep/pull/207 and
https://github.com/mvantellingen/python-zeep/pull/251 for more information
- Fix recursion error when generating the call signature (jaceksnet, #264)
@@ -65,7 +181,7 @@ will hopefully be 1.0 which will introduce semver.
0.22.1 (2016-11-22)
-------------------
- Fix reversed() error (jaceksnet) (#260)
- - Better error message when unexpected xml elements are encountered in
+ - Better error message when unexpected xml elements are encountered in
sequences.
@@ -74,13 +190,13 @@ will hopefully be 1.0 which will introduce semver.
- Force the soap:address / http:address to HTTPS when the wsdl is loaded from
a https url (#228)
- Improvements to the xsd:union handling. The matching base class is now used
- for serializing/deserializing the values. If there is no matching base class
+ for serializing/deserializing the values. If there is no matching base class
then the raw value is returned. (#195)
- Fix handling of xsd:any with maxOccurs > 1 in xsd:choice elements (#253)
- - Add workaround for schema's importing the xsd from
+ - Add workaround for schema's importing the xsd from
http://www.w3.org/XML/1998/namespace (#220)
- Add new Client.type_factory(namespace) method which returns a factory to
- simplify creation of types.
+ simplify creation of types.
@@ -88,15 +204,15 @@ will hopefully be 1.0 which will introduce semver.
-------------------
- Don't error on empty xml namespaces declarations in inline schema's (#186)
- Wrap importing of sqlite3 in try..except for Google App Engine (#243)
- - Don't use pkg_resources to determine the zeep version, use __version__
+ - Don't use pkg_resources to determine the zeep version, use __version__
instead (#243).
- - Fix SOAP arrays by wrapping children in the appropriate element
+ - Fix SOAP arrays by wrapping children in the appropriate element
(joeribekker, #236)
- Add ``operation_timeout`` kwarg to the Transport class to set timeouts for
operations. The default is still no timeout (#140)
- Introduce client.options context manager to temporarily override various
options (only timeout for now) (#140)
- - Wrap the parsing of xml values in a try..except block and log an error
+ - Wrap the parsing of xml values in a try..except block and log an error
instead of throwing an exception (#137)
- Fix xsd:choice xml rendering with nested choice/sequence structure (#221)
- Correctly resolve header elements of which the message part defines the
@@ -106,7 +222,7 @@ will hopefully be 1.0 which will introduce semver.
0.20.0 (2016-10-24)
-------------------
- Major performance improvements / lower memory usage. Zeep now no longer
- copies data and alters it in place but instead uses a set to keep track of
+ copies data and alters it in place but instead uses a set to keep track of
modified data.
- Fix parsing empty soap response (#223)
- Major refactor of the xsd:extension / xsd:restriction implementation.
@@ -118,12 +234,12 @@ will hopefully be 1.0 which will introduce semver.
-------------------
- **backwards-incompatible**: If the WSDL defines that the endpoint returns
soap:header elements and/or multple soap:body messages then the return
- signature of the operation is changed. You can now explcitly access the
+ signature of the operation is changed. You can now explcitly access the
body and header elements.
- Fix parsing HTTP bindings when there are no message elements (#185)
- Fix deserializing RPC responses (#219
- Add support for SOAP 1.2 Fault subcodes (#210, vashek)
- - Don't alter the _soapheaders elements during rendering, instead create a
+ - Don't alter the _soapheaders elements during rendering, instead create a
deepcopy first. (#188)
- Add the SOAPAction to the Content-Type header in SOAP 1.2 bindings (#211)
- Fix issue when mixing elements and any elements in a choice type (#192)
@@ -139,7 +255,7 @@ will hopefully be 1.0 which will introduce semver.
0.18.0 (2016-09-23)
-------------------
- - Fix parsing Any elements by using the namespace map of the response node
+ - Fix parsing Any elements by using the namespace map of the response node
instead of the namespace map of the wsdl. (#184, #164)
- Improve handling of nested choice elements (choice>sequence>choice)
@@ -149,14 +265,14 @@ will hopefully be 1.0 which will introduce semver.
- Add support for xsd:notation (#183)
- Add improvements to resolving phase so that all objects are resolved.
- Improve implementation of xsd.attributeGroup and xsd.UniqueType
- - Create a deepcopy of the args and kwargs passed to objects so that the
+ - Create a deepcopy of the args and kwargs passed to objects so that the
original are unmodified.
- Improve handling of wsdl:arrayType
0.16.0 (2016-09-06)
-------------------
- - Fix error when rendering choice elements with have sequences as children,
+ - Fix error when rendering choice elements with have sequences as children,
see #150
- Re-use credentials passed to python -mzeep <wsdl> (#130)
- Workaround invalid usage of qualified vs non-qualified element tags in the
@@ -183,7 +299,7 @@ will hopefully be 1.0 which will introduce semver.
0.14.0 (2016-08-03)
-------------------
- Global attributes are now always correctly handled as qualified. (#129)
- - Fix parsing xml data containing simpleContent types (#136).
+ - Fix parsing xml data containing simpleContent types (#136).
- Set xsi:nil attribute when serializing objects to xml (#141)
- Fix rendering choice elements when the element is mixed with other elements
in a sequence (#150)
@@ -199,8 +315,8 @@ will hopefully be 1.0 which will introduce semver.
exception. This better matches with what lxml does.
- **backwards-incompatible**: The ``persistent`` kwarg is removed from the
SqliteCache.__init__() call. Use the new InMemoryCache() instead when you
- don't want to persist data. This was required to make the SqliteCache
- backend thread-safe since we now open/close the db when writing/reading
+ don't want to persist data. This was required to make the SqliteCache
+ backend thread-safe since we now open/close the db when writing/reading
from it (with an additional lock).
- Fix zeep.helpers.serialize_object() for nested objects (#123)
- Remove fallback between soap 1.1 and soap 1.2 namespaces during the parsing
@@ -209,30 +325,30 @@ will hopefully be 1.0 which will introduce semver.
0.12.0 (2016-07-09)
-------------------
- - **backwards-incompatible**: Choice elements are now unwrapped if
+ - **backwards-incompatible**: Choice elements are now unwrapped if
maxOccurs=1. This results in easier operation definitions when choices are
- used.
+ used.
- **backwards-incompatible**: The _soapheader kwarg is renamed to _soapheaders
and now requires a nested dictionary with the header name as key or a list
- of values (value object or lxml.etree.Element object). Please see the
+ of values (value object or lxml.etree.Element object). Please see the
call signature of the function using ``python -mzeep <wsdl>``.
- Support the element ref's to xsd:schema elements.
- Improve the signature() output of element and type definitions
- Accept lxml.etree.Element objects as value for Any elements.
- And various other fixes
-
+
0.11.0 (2016-07-03)
-------------------
- **backwards-incompatible**: The kwarg name for Any and Choice elements are
renamed to generic ``_value_N`` names.
- - **backwards-incompatible**: Client.set_address() is replaced with the
+ - **backwards-incompatible**: Client.set_address() is replaced with the
Client.create_service() call
- Auto-load the http://schemas.xmlsoap.org/soap/encoding/ schema if it is
referenced but not imported. Too many XSD's assume that the schema is always
available.
- - Major refactoring of the XSD handling to correctly support nested
- xsd:sequence elements.
+ - Major refactoring of the XSD handling to correctly support nested
+ xsd:sequence elements.
- Add ``logger.debug()`` calls around Transport.post() to allow capturing the
content send/received from the server
- Add proper support for default values on attributes and elements.
@@ -240,12 +356,12 @@ will hopefully be 1.0 which will introduce semver.
0.10.0 (2016-06-22)
-------------------
- - Make global elements / types truly global by refactoring the Schema
+ - Make global elements / types truly global by refactoring the Schema
parsing. Previously the lookups where non-transitive, but this should only
be the case during parsing of the xml schema.
- Properly unwrap XML responses in soap.DocumentMessage when a choice is the
root element. (#80)
- - Update exceptions structure, all zeep exceptions are now using
+ - Update exceptions structure, all zeep exceptions are now using
zeep.exceptions.Error() as base class.
@@ -253,12 +369,12 @@ will hopefully be 1.0 which will introduce semver.
------------------
- Quote the SOAPAction header value (Derek Harland)
- Undo fallback for SOAPAction if it is empty (#83)
-
+
0.9.0 (2016-06-14)
------------------
- Use the appdirs module to retrieve the OS cache path. Note that this results
- in an other default cache path then previous releases! See
+ in an other default cache path then previous releases! See
https://github.com/ActiveState/appdirs for more information.
- Fix regression when initializing soap objects with invalid kwargs.
- Update wsse.UsernameToken to set encoding type on nonce (Antonio Cuni)
@@ -273,7 +389,7 @@ will hopefully be 1.0 which will introduce semver.
0.8.1 (2016-06-08)
------------------
- - Use the operation name for the xml element which wraps the parameters in
+ - Use the operation name for the xml element which wraps the parameters in
for soap RPC messages (#60)
@@ -281,7 +397,7 @@ will hopefully be 1.0 which will introduce semver.
------------------
- Add ability to override the soap endpoint via `Client.set_address()`
- Fix parsing ComplexTypes which have no child elements (#50)
- - Handle xsi:type attributes on anyType's correctly when deserializing
+ - Handle xsi:type attributes on anyType's correctly when deserializing
responses (#17)
- Fix xsd:restriction on xsd:simpleType's when the base type wasn't defined
yet. (#59)
@@ -298,9 +414,9 @@ will hopefully be 1.0 which will introduce semver.
------------------
- Add support HTTP authentication (mcordes). This adds a new attribute to the
Transport client() which passes the http_auth value to requests. (#31)
- - Fix issue where setting cache=None to Transport class didn't disable
+ - Fix issue where setting cache=None to Transport class didn't disable
caching.
- - Refactor handling of wsdl:imports, don't merge definitions but instead
+ - Refactor handling of wsdl:imports, don't merge definitions but instead
lookup values in child definitions. (#40)
- Remove unused namespace declarations from the generated SOAP messages.
- Update requirement of six>=1.0.0 to six>=1.9.0 (#39)
@@ -323,14 +439,14 @@ will hopefully be 1.0 which will introduce semver.
------------------
- Handle attributes during parsing of the response values>
- Don't create empty soap objects when the root element is empty.
- - Implement support for WSSE usernameToken profile including
+ - Implement support for WSSE usernameToken profile including
passwordText/passwordDigest.
- Improve XSD date/time related builtins.
- - Various minor XSD handling fixes
+ - Various minor XSD handling fixes
- Use the correct soap-envelope XML namespace for the Soap 1.2 binding
- Use `application/soap+xml` as content-type in the Soap 1.2 binding
- - **backwards incompatible**: Make cache part of the transport object
- instead of the client. This changes the call signature of the Client()
+ - **backwards incompatible**: Make cache part of the transport object
+ instead of the client. This changes the call signature of the Client()
class. (Marek Wywiał)
- Add the `verify` kwarg to the Transport object to disable ssl certificate
verification. (Marek Wywiał)
@@ -361,7 +477,7 @@ will hopefully be 1.0 which will introduce semver.
------------------
- Improve xsd.DateTime, xsd.Date and xsd.Time implementations by using the
isodate module.
- - Implement xsd.Duration
+ - Implement xsd.Duration
0.2.3 (2016-04-03)
diff --git a/CONTRIBUTORS.rst b/CONTRIBUTORS.rst
new file mode 100644
index 0000000..44a9546
--- /dev/null
+++ b/CONTRIBUTORS.rst
@@ -0,0 +1,45 @@
+Authors
+=======
+* Michael van Tellingen
+
+Contributors
+============
+* vashek
+* Marco Vellinga
+* jaceksnet
+* Andrew Serong
+* Joeri Bekker
+* Eric Wong
+* Jacek Stępniewski
+* Alexey Stepanov
+* Julien Delasoie
+* bjarnagin
+* mcordes
+* Sam Denton
+* David Baumgold
+* fiebiga
+* Antonio Cuni
+* Alexandre de Mari
+* Jason Vertrees
+* Nicolas Evrard
+* Matt Grimm (mgrimm)
+* Marek Wywiał
+* Falldog
+* btmanm
+* Caleb Salt
+* Julien Marechal
+* Mike Fiedler
+* Dave Wapstra
+* OrangGeeGee
+* Stefano Parmesan
+* Jan Murre
+* Ben Tucker
+* Bruno Duyé
+* Christoph Heuel
+* Derek Harland
+* Eric Waller
+* Falk Schuetzenmeister
+* Jon Jenkins
+* Raymond Piller
+* Zoltan Benedek
+* Øyvind Heddeland Instefjord
diff --git a/LICENSE b/LICENSE
index 75eecda..1229d20 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 Michael van Tellingen
+Copyright (c) 2016-2017 Michael van Tellingen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..2f6e6ce
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,19 @@
+# Exclude everything by default
+exclude *
+recursive-exclude * *
+
+include MANIFEST.in
+include CHANGES
+include CONTRIBUTORS.rst
+include LICENSE
+include README.rst
+include setup.cfg
+include setup.py
+
+graft examples
+graft src
+graft tests
+
+global-exclude __pycache__
+global-exclude *.py[co]
+global-exclude .DS_Store
diff --git a/PKG-INFO b/PKG-INFO
index 0dcc777..6f21657 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: zeep
-Version: 0.27.0
+Version: 2.1.1
Summary: A modern/fast Python SOAP client based on lxml / requests
Home-page: http://docs.python-zeep.org
Author: Michael van Tellingen
@@ -57,18 +57,16 @@ Description: ========================
Support
=======
- If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
- possible would be most helpful.
+ If you want to report a bug then please first read
+ http://docs.python-zeep.org/en/master/reporting_bugs.html
I'm also able to offer commercial support. As in contracting work. Please
- contact me at info at mvantellingen.nl for more information. If you just have a
- random question and don't intent to actually pay me for my support then please
- DO NOT email me at that e-mail address but just use stackoverflow or something..
-
- .. _let me know: https://github.com/mvantellingen/python-zeep/issues
+ contact me at info at mvantellingen.nl for more information. Note that asking
+ questions or reporting bugs via this e-mail address will be ignored. Pleae use
+ the appropriate channels for that (e.g. stackoverflow)
Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
diff --git a/README.rst b/README.rst
index c71c9c6..647b78c 100644
--- a/README.rst
+++ b/README.rst
@@ -72,12 +72,10 @@ information.
Support
=======
-If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
-possible would be most helpful.
+If you want to report a bug then please first read
+http://docs.python-zeep.org/en/master/reporting_bugs.html
I'm also able to offer commercial support. As in contracting work. Please
-contact me at info at mvantellingen.nl for more information. If you just have a
-random question and don't intent to actually pay me for my support then please
-DO NOT email me at that e-mail address but just use stackoverflow or something..
-
-.. _let me know: https://github.com/mvantellingen/python-zeep/issues
+contact me at info at mvantellingen.nl for more information. Note that asking
+questions or reporting bugs via this e-mail address will be ignored. Pleae use
+the appropriate channels for that (e.g. stackoverflow)
diff --git a/examples/http_basic_auth.py b/examples/http_basic_auth.py
index e48f6b5..0fe84dd 100644
--- a/examples/http_basic_auth.py
+++ b/examples/http_basic_auth.py
@@ -1,10 +1,16 @@
from __future__ import print_function
+
+from requests import Session
+from requests.auth import HTTPBasicAuth
+
import zeep
from zeep.transports import Transport
# Example using basic authentication with a webservice
-transport_with_basic_auth = Transport(http_auth=('username', 'password'))
+session = Session()
+session.auth = HTTPBasicAuth('username', 'password')
+transport_with_basic_auth = Transport(session=session)
client = zeep.Client(
wsdl='http://nonexistent?WSDL',
diff --git a/setup.cfg b/setup.cfg
index 3128c8e..e77a765 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,5 +1,5 @@
[bumpversion]
-current_version = 0.27.0
+current_version = 2.1.1
commit = true
tag = true
tag_name = {new_version}
@@ -19,6 +19,8 @@ max-line-length = 99
[bumpversion:file:docs/conf.py]
+[bumpversion:file:docs/index.rst]
+
[bumpversion:file:src/zeep/__init__.py]
[coverage:run]
@@ -38,5 +40,4 @@ show_missing = True
[egg_info]
tag_build =
tag_date = 0
-tag_svn_revision = 0
diff --git a/setup.py b/setup.py
index d75137a..dff575e 100755
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,16 @@
import re
+import sys
from setuptools import find_packages, setup
install_requires = [
'appdirs>=1.4.0',
- 'cached-property>=1.0.0',
+ 'cached-property>=1.3.0',
'defusedxml>=0.4.1',
'isodate>=0.5.4',
'lxml>=3.0.0',
'requests>=2.7.0',
- 'requests-toolbelt>=0.7.0',
+ 'requests-toolbelt>=0.7.1',
'six>=1.9.0',
'pytz',
]
@@ -18,9 +19,7 @@ docs_require = [
'sphinx>=1.4.0',
]
-async_require = [
- 'aiohttp>=1.0',
-]
+async_require = [] # see below
xmlsec_require = [
'xmlsec>=0.6.1',
@@ -41,13 +40,19 @@ tests_require = [
'flake8-debugger==1.4.0',
]
+
+if sys.version_info > (3, 4, 2):
+ async_require.append('aiohttp>=1.0')
+ tests_require.append('aioresponses>=0.1.3')
+
+
with open('README.rst') as fh:
long_description = re.sub(
'^.. start-no-pypi.*^.. end-no-pypi', '', fh.read(), flags=re.M | re.S)
setup(
name='zeep',
- version='0.27.0',
+ version='2.1.1',
description='A modern/fast Python SOAP client based on lxml / requests',
long_description=long_description,
author="Michael van Tellingen",
@@ -69,7 +74,7 @@ setup(
license='MIT',
classifiers=[
- 'Development Status :: 4 - Beta',
+ 'Development Status :: 5 - Production/Stable',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
diff --git a/src/zeep.egg-info/PKG-INFO b/src/zeep.egg-info/PKG-INFO
index 0dcc777..6f21657 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: 0.27.0
+Version: 2.1.1
Summary: A modern/fast Python SOAP client based on lxml / requests
Home-page: http://docs.python-zeep.org
Author: Michael van Tellingen
@@ -57,18 +57,16 @@ Description: ========================
Support
=======
- If you encounter bugs then please `let me know`_ . A copy of the WSDL file if
- possible would be most helpful.
+ If you want to report a bug then please first read
+ http://docs.python-zeep.org/en/master/reporting_bugs.html
I'm also able to offer commercial support. As in contracting work. Please
- contact me at info at mvantellingen.nl for more information. If you just have a
- random question and don't intent to actually pay me for my support then please
- DO NOT email me at that e-mail address but just use stackoverflow or something..
-
- .. _let me know: https://github.com/mvantellingen/python-zeep/issues
+ contact me at info at mvantellingen.nl for more information. Note that asking
+ questions or reporting bugs via this e-mail address will be ignored. Pleae use
+ the appropriate channels for that (e.g. stackoverflow)
Platform: UNKNOWN
-Classifier: Development Status :: 4 - Beta
+Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.7
diff --git a/src/zeep.egg-info/SOURCES.txt b/src/zeep.egg-info/SOURCES.txt
index af3d3dd..ab2e621 100644
--- a/src/zeep.egg-info/SOURCES.txt
+++ b/src/zeep.egg-info/SOURCES.txt
@@ -1,5 +1,7 @@
CHANGES
+CONTRIBUTORS.rst
LICENSE
+MANIFEST.in
README.rst
setup.cfg
setup.py
@@ -16,8 +18,8 @@ src/zeep/cache.py
src/zeep/client.py
src/zeep/exceptions.py
src/zeep/helpers.py
+src/zeep/loader.py
src/zeep/ns.py
-src/zeep/parser.py
src/zeep/plugins.py
src/zeep/transports.py
src/zeep/utils.py
@@ -45,6 +47,7 @@ src/zeep/wsdl/messages/__init__.py
src/zeep/wsdl/messages/base.py
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/wsse/__init__.py
src/zeep/wsse/compose.py
@@ -74,6 +77,7 @@ src/zeep/xsd/types/builtins.py
src/zeep/xsd/types/collection.py
src/zeep/xsd/types/complex.py
src/zeep/xsd/types/simple.py
+src/zeep/xsd/types/unresolved.py
tests/__init__.py
tests/cert_valid.pem
tests/cert_valid_pw.pem
@@ -83,9 +87,11 @@ tests/test_cache.py
tests/test_client.py
tests/test_client_factory.py
tests/test_helpers.py
+tests/test_loader.py
tests/test_main.py
tests/test_pprint.py
tests/test_response.py
+tests/test_soap_multiref.py
tests/test_transports.py
tests/test_wsa.py
tests/test_wsdl.py
@@ -101,9 +107,12 @@ tests/test_xsd.py
tests/test_xsd_any.py
tests/test_xsd_attributes.py
tests/test_xsd_builtins.py
-tests/test_xsd_choice.py
tests/test_xsd_complex_types.py
tests/test_xsd_extension.py
+tests/test_xsd_indicators_all.py
+tests/test_xsd_indicators_choice.py
+tests/test_xsd_indicators_group.py
+tests/test_xsd_indicators_sequence.py
tests/test_xsd_integration.py
tests/test_xsd_parse.py
tests/test_xsd_schemas.py
diff --git a/src/zeep.egg-info/requires.txt b/src/zeep.egg-info/requires.txt
index fe4a79f..bbcba39 100644
--- a/src/zeep.egg-info/requires.txt
+++ b/src/zeep.egg-info/requires.txt
@@ -1,10 +1,10 @@
appdirs>=1.4.0
-cached-property>=1.0.0
+cached-property>=1.3.0
defusedxml>=0.4.1
isodate>=0.5.4
lxml>=3.0.0
requests>=2.7.0
-requests-toolbelt>=0.7.0
+requests-toolbelt>=0.7.1
six>=1.9.0
pytz
@@ -25,6 +25,7 @@ isort==4.2.5
flake8==3.2.1
flake8-blind-except==0.1.1
flake8-debugger==1.4.0
+aioresponses>=0.1.3
[xmlsec]
xmlsec>=0.6.1
diff --git a/src/zeep/__init__.py b/src/zeep/__init__.py
index 0cb7e04..faa5a67 100644
--- a/src/zeep/__init__.py
+++ b/src/zeep/__init__.py
@@ -1,6 +1,6 @@
-from zeep.client import Client # noqa
+from zeep.client import CachingClient, Client # noqa
from zeep.transports import Transport # noqa
from zeep.plugins import Plugin # noqa
from zeep.xsd.valueobjects import AnyObject # noqa
-__version__ = '0.27.0'
+__version__ = '2.1.1'
diff --git a/src/zeep/__main__.py b/src/zeep/__main__.py
index ff3d509..72afadd 100644
--- a/src/zeep/__main__.py
+++ b/src/zeep/__main__.py
@@ -7,6 +7,7 @@ import time
import requests
from six.moves.urllib.parse import urlparse
+
from zeep.cache import SqliteCache
from zeep.client import Client
from zeep.transports import Transport
@@ -27,6 +28,9 @@ def parse_arguments(args=None):
'--verbose', action='store_true', help='Enable verbose output')
parser.add_argument(
'--profile', help="Enable profiling and save output to given file")
+ parser.add_argument(
+ '--no-strict', action='store_true', default=False,
+ help="Disable strict mode")
return parser.parse_args(args)
@@ -72,7 +76,9 @@ def main(args):
transport = Transport(cache=cache, session=session)
st = time.time()
- client = Client(args.wsdl_file, transport=transport)
+
+ strict = not args.no_strict
+ client = Client(args.wsdl_file, transport=transport, strict=strict)
logger.debug("Loading WSDL took %sms", (time.time() - st) * 1000)
if args.profile:
diff --git a/src/zeep/asyncio/transport.py b/src/zeep/asyncio/transport.py
index 6968c2a..ec3cc40 100644
--- a/src/zeep/asyncio/transport.py
+++ b/src/zeep/asyncio/transport.py
@@ -6,6 +6,8 @@ import asyncio
import logging
import aiohttp
+from requests import Response
+
from zeep.transports import Transport
from zeep.utils import get_version
from zeep.wsdl.utils import etree_to_string
@@ -27,9 +29,14 @@ class AsyncTransport(Transport):
self.logger = logging.getLogger(__name__)
self.session = session or aiohttp.ClientSession(loop=self.loop)
+ self._close_session = session is None
self.session._default_headers['User-Agent'] = (
'Zeep/%s (www.python-zeep.org)' % (get_version()))
+ def __del__(self):
+ if self._close_session:
+ self.session.close()
+
def _load_remote_data(self, url):
result = None
@@ -56,20 +63,21 @@ class AsyncTransport(Transport):
async def post_xml(self, address, envelope, headers):
message = etree_to_string(envelope)
response = await self.post(address, message, headers)
-
- from pretend import stub
- return stub(
- content=await response.read(),
- status_code=response.status,
- headers=response.headers)
+ return await self.new_response(response)
async def get(self, address, params, headers):
with aiohttp.Timeout(self.operation_timeout):
response = await self.session.get(
address, params=params, headers=headers)
- from pretend import stub
- return await stub(
- content=await response.read(),
- status_code=response.status,
- headers=response.headers)
+ return await self.new_response(response)
+
+ async def new_response(self, response):
+ """Convert an aiohttp.Response object to a requests.Response object"""
+ new = Response()
+ new._content = await response.read()
+ new.status_code = response.status
+ new.headers = response.headers
+ new.cookies = response.cookies
+ new.encoding = response.charset
+ return new
diff --git a/src/zeep/client.py b/src/zeep/client.py
index 9c7f30a..cad16ac 100644
--- a/src/zeep/client.py
+++ b/src/zeep/client.py
@@ -4,7 +4,7 @@ from contextlib import contextmanager
from zeep.transports import Transport
from zeep.wsdl import Document
-
+from zeep.xsd.const import NotSet
logger = logging.getLogger(__name__)
@@ -15,6 +15,12 @@ class OperationProxy(object):
self._op_name = operation_name
def __call__(self, *args, **kwargs):
+ """Call the operation with the given args and kwargs.
+
+ :rtype: zeep.xsd.CompoundValue
+
+ """
+
if self._proxy._client._default_soapheaders:
op_soapheaders = kwargs.get('_soapheaders')
if op_soapheaders:
@@ -42,9 +48,19 @@ class ServiceProxy(object):
self._binding = binding
def __getattr__(self, key):
+ """Return the OperationProxy for the given key.
+
+ :rtype: OperationProxy()
+
+ """
return self[key]
def __getitem__(self, key):
+ """Return the OperationProxy for the given key.
+
+ :rtype: OperationProxy()
+
+ """
try:
self._binding.get(key)
except ValueError:
@@ -62,9 +78,19 @@ class Factory(object):
self._ns = types.get_ns_prefix(namespace)
def __getattr__(self, key):
+ """Return the complexType or simpleType for the given localname.
+
+ :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+ """
return self[key]
def __getitem__(self, key):
+ """Return the complexType or simpleType for the given localname.
+
+ :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+ """
return self._method('{%s}%s' % (self._ns, key))
@@ -81,19 +107,26 @@ class Client(object):
first port defined in the service element in the WSDL
document.
:param plugins: a list of Plugin instances
+ :param xml_huge_tree: disable lxml/libxml2 security restrictions and
+ support very deep trees and very long text content
"""
def __init__(self, wsdl, wsse=None, transport=None,
- service_name=None, port_name=None, plugins=None):
+ service_name=None, port_name=None, plugins=None,
+ strict=True, xml_huge_tree=False):
if not wsdl:
raise ValueError("No URL given for the wsdl")
- self.transport = transport or Transport()
- self.wsdl = Document(wsdl, self.transport)
+ self.transport = transport if transport is not None else Transport()
+ self.wsdl = Document(wsdl, self.transport, strict=strict)
self.wsse = wsse
self.plugins = plugins if plugins is not None else []
+ self.xml_huge_tree = xml_huge_tree
+
+ # options
+ self.raw_response = False
self._default_service = None
self._default_service_name = service_name
@@ -102,7 +135,11 @@ class Client(object):
@property
def service(self):
- """The default ServiceProxy instance"""
+ """The default ServiceProxy instance
+
+ :rtype: ServiceProxy
+
+ """
if self._default_service:
return self._default_service
@@ -116,7 +153,7 @@ class Client(object):
return self._default_service
@contextmanager
- def options(self, timeout):
+ def options(self, timeout=NotSet, raw_response=NotSet):
"""Context manager to temporarily overrule various options.
:param timeout: Set the timeout for POST/GET operations (not used for
@@ -130,8 +167,22 @@ class Client(object):
"""
- with self.transport._options(timeout=timeout):
- yield
+ # Store current options
+ old_raw_raw_response = self.raw_response
+
+ # Set new options
+ self.raw_response = raw_response
+
+ if timeout is not NotSet:
+ timeout_ctx = self.transport._options(timeout=timeout)
+ timeout_ctx.__enter__()
+
+ yield
+
+ self.raw_response = old_raw_raw_response
+
+ if timeout is not NotSet:
+ timeout_ctx.__exit__(None, None, None)
def bind(self, service_name=None, port_name=None):
"""Create a new ServiceProxy for the given service_name and port_name.
@@ -163,15 +214,14 @@ class Client(object):
"are: %s" % (', '.join(self.wsdl.bindings.keys())))
return ServiceProxy(self, binding, address=address)
- def create_message(self, operation, service_name=None, port_name=None,
- args=None, kwargs=None):
- """Create the payload for the given operation."""
- service = self._get_service(service_name)
- port = self._get_port(service, port_name)
+ def create_message(self, service, operation_name, *args, **kwargs):
+ """Create the payload for the given operation.
- args = args or tuple()
- kwargs = kwargs or {}
- envelope, http_headers = port.binding._create(operation, args, kwargs)
+ :rtype: lxml.etree._Element
+
+ """
+ envelope, http_headers = service._binding._create(
+ operation_name, args, kwargs, client=self)
return envelope
def type_factory(self, namespace):
@@ -182,15 +232,25 @@ class Client(object):
factory = client.type_factory('ns0')
user = factory.User(name='John')
+ :rtype: Factory
+
"""
return Factory(self.wsdl.types, 'type', namespace)
def get_type(self, name):
- """Return the type for the given qualified name."""
+ """Return the type for the given qualified name.
+
+ :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+ """
return self.wsdl.types.get_type(name)
def get_element(self, name):
- """Return the element for the given qualified name."""
+ """Return the element for the given qualified name.
+
+ :rtype: zeep.xsd.Element
+
+ """
return self.wsdl.types.get_element(name)
def set_ns_prefix(self, prefix, namespace):
@@ -227,3 +287,20 @@ class Client(object):
else:
service = next(iter(self.wsdl.services.values()), None)
return service
+
+
+class CachingClient(Client):
+ """Shortcut to create a caching client, for the lazy people.
+
+ This enables the SqliteCache by default in the transport as was the default
+ in earlier versions of zeep.
+
+ """
+ def __init__(self, *args, **kwargs):
+
+ # Don't use setdefault since we want to lazily init the Transport cls
+ from zeep.cache import SqliteCache
+ kwargs['transport'] = (
+ kwargs.get('transport') or Transport(cache=SqliteCache()))
+
+ super(CachingClient, self).__init__(*args, **kwargs)
diff --git a/src/zeep/exceptions.py b/src/zeep/exceptions.py
index 0056057..a7a4c1d 100644
--- a/src/zeep/exceptions.py
+++ b/src/zeep/exceptions.py
@@ -39,7 +39,11 @@ class TransportError(Error):
class LookupError(Error):
- pass
+ def __init__(self, *args, **kwargs):
+ self.qname = kwargs.pop('qname', None)
+ self.item_name = kwargs.pop('item_name', None)
+ self.location = kwargs.pop('location', None)
+ super(LookupError, self).__init__(*args, **kwargs)
class NamespaceError(Error):
@@ -74,3 +78,11 @@ class ValidationError(Error):
class SignatureVerificationFailed(Error):
pass
+
+
+class IncompleteMessage(Error):
+ pass
+
+
+class IncompleteOperation(Error):
+ pass
diff --git a/src/zeep/loader.py b/src/zeep/loader.py
new file mode 100644
index 0000000..58ea81e
--- /dev/null
+++ b/src/zeep/loader.py
@@ -0,0 +1,113 @@
+import os.path
+
+from defusedxml.lxml import fromstring
+from lxml import etree
+from six.moves.urllib.parse import urljoin, urlparse
+
+from zeep.exceptions import XMLSyntaxError
+
+
+class ImportResolver(etree.Resolver):
+ """Custom lxml resolve to use the transport object"""
+ def __init__(self, transport):
+ self.transport = transport
+
+ def resolve(self, url, pubid, context):
+ if urlparse(url).scheme in ('http', 'https'):
+ content = self.transport.load(url)
+ return self.resolve_string(content, context)
+
+
+def parse_xml(content, transport, base_url=None, strict=True,
+ xml_huge_tree=False):
+ """Parse an XML string and return the root Element.
+
+ :param content: The XML string
+ :type content: str
+ :param transport: The transport instance to load imported documents
+ :type transport: zeep.transports.Transport
+ :param base_url: The base url of the document, used to make relative
+ lookups absolute.
+ :type base_url: str
+ :param strict: boolean to indicate if the lxml should be parsed a 'strict'.
+ If false then the recover mode is enabled which tries to parse invalid
+ XML as best as it can.
+ :param xml_huge_tree: boolean to indicate if lxml should process very
+ large XML content.
+ :type strict: boolean
+ :returns: The document root
+ :rtype: lxml.etree._Element
+
+ """
+ recover = not strict
+ parser = etree.XMLParser(remove_comments=True, resolve_entities=False,
+ recover=recover, huge_tree=xml_huge_tree)
+ parser.resolvers.add(ImportResolver(transport))
+ try:
+ return fromstring(content, parser=parser, base_url=base_url)
+ except etree.XMLSyntaxError as exc:
+ raise XMLSyntaxError("Invalid XML content received (%s)" % exc.msg)
+
+
+def load_external(url, transport, base_url=None, strict=True):
+ """Load an external XML document.
+
+ :param url:
+ :param transport:
+ :param base_url:
+ :param strict: boolean to indicate if the lxml should be parsed a 'strict'.
+ If false then the recover mode is enabled which tries to parse invalid
+ XML as best as it can.
+ :type strict: boolean
+
+ """
+ if hasattr(url, 'read'):
+ content = url.read()
+ else:
+ if base_url:
+ url = absolute_location(url, base_url)
+ content = transport.load(url)
+ return parse_xml(content, transport, base_url, strict=strict)
+
+
+def absolute_location(location, base):
+ """Make an url absolute (if it is optional) via the passed base url.
+
+ :param location: The (relative) url
+ :type location: str
+ :param base: The base location
+ :type base: str
+ :returns: An absolute URL
+ :rtype: str
+
+ """
+ if location == base:
+ return location
+
+ if urlparse(location).scheme in ('http', 'https', 'file'):
+ return location
+
+ if base and urlparse(base).scheme in ('http', 'https', 'file'):
+ return urljoin(base, location)
+ else:
+ if os.path.isabs(location):
+ return location
+ if base:
+ return os.path.realpath(
+ os.path.join(os.path.dirname(base), location))
+ return location
+
+
+def is_relative_path(value):
+ """Check if the given value is a relative path
+
+ :param value: The value
+ :type value: str
+ :returns: Boolean indicating if the url is relative. If it is absolute then
+ False is returned.
+ :rtype: boolean
+
+ """
+ if urlparse(value).scheme in ('http', 'https', 'file'):
+ return False
+ return not os.path.isabs(value)
diff --git a/src/zeep/ns.py b/src/zeep/ns.py
index 66e1470..b78ebdf 100644
--- a/src/zeep/ns.py
+++ b/src/zeep/ns.py
@@ -4,6 +4,7 @@ SOAP_12 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
SOAP_ENV_11 = 'http://schemas.xmlsoap.org/soap/envelope/'
SOAP_ENV_12 = 'http://www.w3.org/2003/05/soap-envelope'
+XSI = 'http://www.w3.org/2001/XMLSchema-instance'
XSD = 'http://www.w3.org/2001/XMLSchema'
WSDL = 'http://schemas.xmlsoap.org/wsdl/'
@@ -16,3 +17,7 @@ WSA = 'http://www.w3.org/2005/08/addressing'
DS = 'http://www.w3.org/2000/09/xmldsig#'
WSSE = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
WSU = 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'
+
+NAMESPACE_TO_PREFIX = {
+ XSD: 'xsd',
+}
diff --git a/src/zeep/parser.py b/src/zeep/parser.py
deleted file mode 100644
index 4d6082f..0000000
--- a/src/zeep/parser.py
+++ /dev/null
@@ -1,49 +0,0 @@
-import os
-
-from defusedxml.lxml import fromstring
-from lxml import etree
-
-from six.moves.urllib.parse import urljoin, urlparse
-from zeep.exceptions import XMLSyntaxError
-
-
-def parse_xml(content, base_url=None, recover=False):
- parser = etree.XMLParser(
- remove_comments=True, recover=recover, resolve_entities=False)
- try:
- return fromstring(content, parser=parser, base_url=base_url)
- except etree.XMLSyntaxError as exc:
- raise XMLSyntaxError("Invalid XML content received (%s)" % exc)
-
-
-def load_external(url, transport, base_url=None):
- if base_url:
- url = absolute_location(url, base_url)
-
- response = transport.load(url)
- return parse_xml(response, base_url)
-
-
-def absolute_location(location, base):
- if location == base:
- return location
-
- if urlparse(location).scheme in ('http', 'https'):
- return location
-
- if base and urlparse(base).scheme in ('http', 'https'):
- return urljoin(base, location)
- else:
- if os.path.isabs(location):
- return location
- if base:
- return os.path.realpath(
- os.path.join(os.path.dirname(base), location))
- return location
-
-
-def is_relative_path(value):
- """Check if the given value is a relative path"""
- if urlparse(value).scheme in ('http', 'https', 'file'):
- return False
- return not os.path.isabs(value)
diff --git a/src/zeep/transports.py b/src/zeep/transports.py
index 89fd183..29a4453 100644
--- a/src/zeep/transports.py
+++ b/src/zeep/transports.py
@@ -3,9 +3,9 @@ import os
from contextlib import contextmanager
import requests
-
from six.moves.urllib.parse import urlparse
-from zeep.utils import get_version
+
+from zeep.utils import get_media_type, get_version
from zeep.wsdl.utils import etree_to_string
@@ -16,7 +16,7 @@ class Transport(object):
:param timeout: The timeout for loading wsdl and xsd documents.
:param operation_timeout: The timeout for operations (POST/GET). By
default this is None (no timeout).
- :param session: A request.Session() object (optional)
+ :param session: A :py:class:`request.Session()` object (optional)
"""
supports_async = False
@@ -68,7 +68,10 @@ class Transport(object):
timeout=self.operation_timeout)
if self.logger.isEnabledFor(logging.DEBUG):
- if 'multipart/related' in response.headers.get('Content-Type'):
+ media_type = get_media_type(
+ response.headers.get('Content-Type', 'text/xml'))
+
+ if media_type == 'multipart/related':
log_message = response.content
else:
log_message = response.content
diff --git a/src/zeep/utils.py b/src/zeep/utils.py
index d7efe90..2d15c51 100644
--- a/src/zeep/utils.py
+++ b/src/zeep/utils.py
@@ -1,7 +1,11 @@
+import cgi
import inspect
from lxml import etree
+from zeep.exceptions import XMLParseError
+from zeep.ns import XSD
+
def qname_attr(node, attr_name, target_namespace=None):
value = node.get(attr_name)
@@ -9,11 +13,25 @@ def qname_attr(node, attr_name, target_namespace=None):
return as_qname(value, node.nsmap, target_namespace)
-def as_qname(value, nsmap, target_namespace):
+def as_qname(value, nsmap, target_namespace=None):
"""Convert the given value to a QName"""
if ':' in value:
prefix, local = value.split(':')
- namespace = nsmap.get(prefix, prefix)
+
+ # The xml: prefix is always bound to the XML namespace, see
+ # https://www.w3.org/TR/xml-names/
+ if prefix == 'xml':
+ namespace = 'http://www.w3.org/XML/1998/namespace'
+ else:
+ namespace = nsmap.get(prefix)
+
+ if not namespace:
+ raise XMLParseError("No namespace defined for %r" % prefix)
+
+ # Workaround for https://github.com/mvantellingen/python-zeep/issues/349
+ if not local:
+ return etree.QName(XSD, 'anyType')
+
return etree.QName(namespace, local)
if target_namespace:
@@ -61,3 +79,9 @@ def get_base_class(objects):
def detect_soap_env(envelope):
root_tag = etree.QName(envelope)
return root_tag.namespace
+
+
+def get_media_type(value):
+ """Parse a HTTP content-type header and return the media-type"""
+ main_value, parameters = cgi.parse_header(value)
+ return main_value
diff --git a/src/zeep/wsa.py b/src/zeep/wsa.py
index 3c3e653..a4ff68e 100644
--- a/src/zeep/wsa.py
+++ b/src/zeep/wsa.py
@@ -37,7 +37,5 @@ class WsAddressingPlugin(Plugin):
keep_ns_prefixes=header.nsmap,
top_nsmap=self.nsmap)
else:
- etree.cleanup_namespaces(
- header,
- keep_ns_prefixes=header.nsmap)
+ etree.cleanup_namespaces(header)
return envelope, http_headers
diff --git a/src/zeep/wsdl/__init__.py b/src/zeep/wsdl/__init__.py
index 2dbac73..f24188e 100644
--- a/src/zeep/wsdl/__init__.py
+++ b/src/zeep/wsdl/__init__.py
@@ -1 +1,16 @@
+"""
+ zeep.wsdl
+ ---------
+
+ The wsdl module is responsible for parsing the WSDL document. This includes
+ the bindings and messages.
+
+ The structure and naming of the modules and classses closely follows the
+ WSDL 1.1 specification.
+
+ The serialization and deserialization of the SOAP/HTTP messages is done
+ by the zeep.wsdl.messages modules.
+
+
+"""
from zeep.wsdl.wsdl import Document # noqa
diff --git a/src/zeep/wsdl/attachments.py b/src/zeep/wsdl/attachments.py
index 408ae4d..505980c 100644
--- a/src/zeep/wsdl/attachments.py
+++ b/src/zeep/wsdl/attachments.py
@@ -3,8 +3,8 @@
See https://www.w3.org/TR/SOAP-attachments
"""
+
import base64
-from io import BytesIO
from cached_property import cached_property
from requests.structures import CaseInsensitiveDict
@@ -27,9 +27,21 @@ class MessagePack(object):
@cached_property
def attachments(self):
+ """Return a list of attachments.
+
+ :rtype: list of Attachment
+
+ """
return [Attachment(part) for part in self._parts]
def get_by_content_id(self, content_id):
+ """get_by_content_id
+
+ :param content_id: The content-id to return
+ :type content_id: str
+ :rtype: Attachment
+
+ """
for attachment in self.attachments:
if attachment.content_id == content_id:
return attachment
@@ -37,9 +49,9 @@ class MessagePack(object):
class Attachment(object):
def __init__(self, part):
-
+ encoding = part.encoding or 'utf-8'
self.headers = CaseInsensitiveDict({
- k.decode(part.encoding): v.decode(part.encoding)
+ k.decode(encoding): v.decode(encoding)
for k, v in part.headers.items()
})
self.content_type = self.headers.get('Content-Type', None)
@@ -52,6 +64,11 @@ class Attachment(object):
@cached_property
def content(self):
+ """Return the content of the attachment
+
+ :rtype: bytes or str
+
+ """
encoding = self.headers.get('Content-Transfer-Encoding', None)
content = self._part.content
diff --git a/src/zeep/wsdl/bindings/soap.py b/src/zeep/wsdl/bindings/soap.py
index 6ba92e1..ae08ad7 100644
--- a/src/zeep/wsdl/bindings/soap.py
+++ b/src/zeep/wsdl/bindings/soap.py
@@ -5,8 +5,8 @@ from requests_toolbelt.multipart.decoder import MultipartDecoder
from zeep import ns, plugins, wsa
from zeep.exceptions import Fault, TransportError, XMLSyntaxError
-from zeep.parser import parse_xml
-from zeep.utils import as_qname, qname_attr
+from zeep.loader import parse_xml
+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
@@ -97,9 +97,9 @@ class SoapBinding(Binding):
:type options: dict
:param operation: The operation object from which this is a reply
:type operation: zeep.wsdl.definitions.Operation
- :param args: The *args to pass to the operation
+ :param args: The args to pass to the operation
:type args: tuple
- :param kwargs: The **kwargs to pass to the operation
+ :param kwargs: The kwargs to pass to the operation
:type kwargs: dict
"""
@@ -112,6 +112,11 @@ class SoapBinding(Binding):
options['address'], envelope, http_headers)
operation_obj = self.get(operation)
+
+ # If the client wants to return the raw data then let's do that.
+ if client.raw_response:
+ return response
+
return self.process_reply(client, operation_obj, response)
def process_reply(self, client, operation, response):
@@ -131,20 +136,27 @@ class SoapBinding(Binding):
% response.status_code)
content_type = response.headers.get('Content-Type', 'text/xml')
- if 'multipart/related' in content_type:
- decoder = MultipartDecoder(response.content, content_type, 'utf-8')
+ media_type = get_media_type(content_type)
+ message_pack = None
+
+ 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:])
else:
content = response.content
- message_pack = None
try:
- doc = parse_xml(content)
+ doc = parse_xml(
+ content, self.transport,
+ strict=client.wsdl.strict,
+ xml_huge_tree=client.xml_huge_tree)
except XMLSyntaxError:
raise TransportError(
- u'Server returned HTTP status %d (%s)'
+ 'Server returned HTTP status %d (%s)'
% (response.status_code, response.content))
if client.wsse:
@@ -187,6 +199,9 @@ class SoapBinding(Binding):
@classmethod
def parse(cls, definitions, xmlelement):
"""
+
+ Definition::
+
<wsdl:binding name="nmtoken" type="qname"> *
<-- extensibility element (1) --> *
<wsdl:operation name="nmtoken"> *
@@ -210,7 +225,13 @@ class SoapBinding(Binding):
# default style attribute for the operations.
soap_node = xmlelement.find('soap:binding', namespaces=cls.nsmap)
transport = soap_node.get('transport')
- if transport != 'http://schemas.xmlsoap.org/soap/http':
+
+ supported_transports = [
+ 'http://schemas.xmlsoap.org/soap/http',
+ 'http://www.w3.org/2003/05/soap/bindings/HTTP/',
+ ]
+
+ if transport not in supported_transports:
raise NotImplementedError(
"The binding transport %s is not supported (only soap/http)" % (
transport))
@@ -328,12 +349,15 @@ class SoapOperation(Operation):
"{%s}Envelope root element. The root element found is %s "
) % (envelope_qname.namespace, envelope.tag))
- return self.output.deserialize(envelope)
+ if self.output:
+ return self.output.deserialize(envelope)
@classmethod
def parse(cls, definitions, xmlelement, binding, nsmap):
"""
+ Definition::
+
<wsdl:operation name="nmtoken"> *
<soap:operation soapAction="uri"? style="rpc|document"?>?
<wsdl:input name="nmtoken"? > ?
diff --git a/src/zeep/wsdl/definitions.py b/src/zeep/wsdl/definitions.py
index b01e1c2..41194b8 100644
--- a/src/zeep/wsdl/definitions.py
+++ b/src/zeep/wsdl/definitions.py
@@ -1,7 +1,27 @@
+"""
+ zeep.wsdl.definitions
+ ~~~~~~~~~~~~~~~~~~~~~
+
+ A WSDL document exists out of a number of definitions. There are 6 major
+ definitions, these are:
+
+ - types
+ - message
+ - portType
+ - binding
+ - port
+ - service
+
+ This module defines the definitions which occur within a WSDL document,
+
+"""
+import warnings
from collections import OrderedDict, namedtuple
from six import python_2_unicode_compatible
+from zeep.exceptions import IncompleteOperation
+
MessagePart = namedtuple('MessagePart', ['element', 'type'])
@@ -13,8 +33,8 @@ class AbstractMessage(object):
extensible. WSDL defines several such message-typing attributes for use
with XSD:
- element: Refers to an XSD element using a QName.
- type: Refers to an XSD simpleType or complexType using a QName.
+ - element: Refers to an XSD element using a QName.
+ - type: Refers to an XSD simpleType or complexType using a QName.
"""
def __init__(self, name):
@@ -72,6 +92,8 @@ class PortType(object):
class Binding(object):
"""Base class for the various bindings (SoapBinding / HttpBinding)
+ .. raw:: ascii
+
Binding
|
+-> Operation
@@ -100,8 +122,13 @@ class Binding(object):
def resolve(self, definitions):
self.port_type = definitions.get('port_types', self.port_name.text)
- for operation in self._operations.values():
- operation.resolve(definitions)
+
+ for name, operation in list(self._operations.items()):
+ try:
+ operation.resolve(definitions)
+ except IncompleteOperation as exc:
+ warnings.warn(str(exc))
+ del self._operations[name]
def _operation_add(self, operation):
# XXX: operation name is not unique
@@ -146,7 +173,12 @@ class Operation(object):
self.faults = {}
def resolve(self, definitions):
- self.abstract = self.binding.port_type.operations[self.name]
+ try:
+ self.abstract = self.binding.port_type.operations[self.name]
+ except KeyError:
+ raise IncompleteOperation(
+ "The wsdl:operation %r was not found in the wsdl:portType %r" % (
+ self.name, self.binding.port_type.name.text))
def __repr__(self):
return '<%s(name=%r, style=%r)>' % (
@@ -170,6 +202,9 @@ class Operation(object):
@classmethod
def parse(cls, wsdl, xmlelement, binding):
"""
+
+ Definition::
+
<wsdl:operation name="nmtoken"> *
<-- extensibility element (2) --> *
<wsdl:input name="nmtoken"? > ?
@@ -182,12 +217,17 @@ class Operation(object):
<-- extensibility element (5) --> *
</wsdl:fault>
</wsdl:operation>
+
"""
raise NotImplementedError()
@python_2_unicode_compatible
class Port(object):
+ """Specifies an address for a binding, thus defining a single communication
+ endpoint.
+
+ """
def __init__(self, name, binding_name, xmlelement):
self.name = name
self._resolve_context = {
@@ -231,7 +271,9 @@ class Port(object):
@python_2_unicode_compatible
class Service(object):
+ """Used to aggregate a set of related ports.
+ """
def __init__(self, name):
self.ports = OrderedDict()
self.name = name
diff --git a/src/zeep/wsdl/messages/__init__.py b/src/zeep/wsdl/messages/__init__.py
index 70a5b5a..f77f710 100644
--- a/src/zeep/wsdl/messages/__init__.py
+++ b/src/zeep/wsdl/messages/__init__.py
@@ -1,3 +1,20 @@
+"""
+ zeep.wsdl.messages
+ ~~~~~~~~~~~~~~~~~~
+
+ The messages are responsible for serializing and deserializing
+
+ .. inheritance-diagram::
+ zeep.wsdl.messages.soap.DocumentMessage
+ zeep.wsdl.messages.soap.RpcMessage
+ zeep.wsdl.messages.http.UrlEncoded
+ zeep.wsdl.messages.http.UrlReplacement
+ zeep.wsdl.messages.mime.MimeContent
+ zeep.wsdl.messages.mime.MimeXML
+ zeep.wsdl.messages.mime.MimeMultipart
+ :parts: 1
+
+"""
from .http import * # noqa
from .mime import * # noqa
from .soap import * # noqa
diff --git a/src/zeep/wsdl/messages/base.py b/src/zeep/wsdl/messages/base.py
index cc71e93..eebb840 100644
--- a/src/zeep/wsdl/messages/base.py
+++ b/src/zeep/wsdl/messages/base.py
@@ -1,3 +1,8 @@
+"""
+ zeep.wsdl.messages.base
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
from collections import namedtuple
from zeep import xsd
@@ -31,15 +36,17 @@ class ConcreteMessage(object):
if isinstance(self.body.type, xsd.ComplexType):
try:
if len(self.body.type.elements) == 1:
- return self.body.type.elements[0][1].type.signature()
+ return self.body.type.elements[0][1].type.signature(
+ schema=self.wsdl.types, standalone=False)
except AttributeError:
return None
- return self.body.type.signature()
+ return self.body.type.signature(schema=self.wsdl.types, standalone=False)
- parts = [self.body.type.signature()]
+ parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
if getattr(self, 'header', None):
- parts.append('_soapheaders={%s}' % self.header.signature())
+ parts.append('_soapheaders={%s}' % self.header.signature(
+ schema=self.wsdl.types), standalone=False)
return ', '.join(part for part in parts if part)
@classmethod
diff --git a/src/zeep/wsdl/messages/http.py b/src/zeep/wsdl/messages/http.py
index 6264dca..30ac121 100644
--- a/src/zeep/wsdl/messages/http.py
+++ b/src/zeep/wsdl/messages/http.py
@@ -1,3 +1,8 @@
+"""
+ zeep.wsdl.messages.http
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
from zeep import xsd
from zeep.wsdl.messages.base import ConcreteMessage, SerializedMessage
diff --git a/src/zeep/wsdl/messages/mime.py b/src/zeep/wsdl/messages/mime.py
index d509212..acc00a7 100644
--- a/src/zeep/wsdl/messages/mime.py
+++ b/src/zeep/wsdl/messages/mime.py
@@ -1,3 +1,8 @@
+"""
+ zeep.wsdl.messages.mime
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
import six
from defusedxml.lxml import fromstring
from lxml import etree
@@ -79,6 +84,14 @@ class MimeContent(MimeMessage):
The set of defined MIME types is both large and evolving, so it is not a
goal for WSDL to exhaustively define XML grammar for each MIME type.
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param part_name:
+ :type type: str
+
"""
def __init__(self, wsdl, name, operation, content_type, part_name):
super(MimeContent, self).__init__(wsdl, name, operation, part_name)
@@ -131,6 +144,14 @@ class MimeXML(MimeMessage):
only a single part. The part references a concrete schema using the element
attribute for simple parts or type attribute for composite parts
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param part_name:
+ :type type: str
+
"""
def serialize(self, *args, **kwargs):
raise NotImplementedError()
@@ -170,5 +191,13 @@ class MimeMultipart(MimeMessage):
the part. If more than one MIME element appears inside a mime:part, they
are alternatives.
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param part_name:
+ :type type: str
+
"""
pass
diff --git a/src/zeep/wsdl/messages/multiref.py b/src/zeep/wsdl/messages/multiref.py
new file mode 100644
index 0000000..907049e
--- /dev/null
+++ b/src/zeep/wsdl/messages/multiref.py
@@ -0,0 +1,81 @@
+import copy
+
+from lxml import etree
+
+
+def process_multiref(node):
+ """Iterate through the tree and replace the referened elements.
+
+ This method replaces the nodes with an href attribute and replaces it
+ with the elements it's referencing to (which have an id attribute).abs
+
+ """
+ multiref_objects = {
+ elm.attrib['id']: elm for elm in node.xpath('*[@id]')
+ }
+ if not multiref_objects:
+ return
+
+ used_nodes = []
+
+ def process(node):
+ # TODO (In Soap 1.2 this is 'ref')
+ href = node.attrib.get('href')
+
+ if href and href.startswith('#'):
+ 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
+
+ for child in node:
+ process(child)
+
+ process(node)
+
+ # Remove the old dereferenced nodes from the tree
+ for node in used_nodes:
+ parent = node.getparent()
+ if parent is not None:
+ parent.remove(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}
+
+ new = etree.Element(target.tag, nsmap=specific_nsmap)
+
+ # 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
+
+ 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
+
+ if not setted:
+ new.set(key, value)
+
+ # Copy the children and the text content
+ for child in source:
+ new.append(copy.deepcopy(child))
+ new.text = source.text
+
+ return new
diff --git a/src/zeep/wsdl/messages/soap.py b/src/zeep/wsdl/messages/soap.py
index 17a2d2d..940a2e6 100644
--- a/src/zeep/wsdl/messages/soap.py
+++ b/src/zeep/wsdl/messages/soap.py
@@ -1,12 +1,19 @@
+"""
+ zeep.wsdl.messages.soap
+ ~~~~~~~~~~~~~~~~~~~~~~~
+
+"""
import copy
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
+from zeep.wsdl.messages.multiref import process_multiref
__all__ = [
'DocumentMessage',
@@ -15,8 +22,19 @@ __all__ = [
class SoapMessage(ConcreteMessage):
- """Base class for the SOAP Document and RPC messages"""
+ """Base class for the SOAP Document and RPC messages
+
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param type: 'input' or 'output'
+ :type type: str
+ :param nsmap: The namespace mapping
+ :type nsmap: dict
+ """
def __init__(self, wsdl, name, operation, type, nsmap):
super(SoapMessage, self).__init__(wsdl, name, operation)
self.nsmap = nsmap
@@ -71,6 +89,7 @@ class SoapMessage(ConcreteMessage):
if not self.envelope:
return None
+
body = envelope.find('soap-env:Body', namespaces=self.nsmap)
body_result = self._deserialize_body(body)
@@ -96,7 +115,8 @@ class SoapMessage(ConcreteMessage):
result = next(iter(result.__values__.values()))
if isinstance(result, xsd.CompoundValue):
children = result._xsd_type.elements
- if len(children) == 1:
+ attributes = result._xsd_type.attributes
+ if len(children) == 1 and len(attributes) == 0:
item_name, item_element = children[0]
retval = getattr(result, item_name)
return retval
@@ -110,14 +130,16 @@ class SoapMessage(ConcreteMessage):
if isinstance(self.envelope.type, xsd.ComplexType):
try:
if len(self.envelope.type.elements) == 1:
- return self.envelope.type.elements[0][1].type.signature()
+ return self.envelope.type.elements[0][1].type.signature(
+ schema=self.wsdl.types, standalone=False)
except AttributeError:
return None
- return self.envelope.type.signature()
+ return self.envelope.type.signature(schema=self.wsdl.types, standalone=False)
- parts = [self.body.type.signature()]
+ parts = [self.body.type.signature(schema=self.wsdl.types, standalone=False)]
if self.header.type._element:
- parts.append('_soapheaders={%s}' % self.header.signature())
+ parts.append('_soapheaders={%s}' % self.header.type.signature(
+ schema=self.wsdl.types, standalone=False))
return ', '.join(part for part in parts if part)
@classmethod
@@ -156,6 +178,8 @@ class SoapMessage(ConcreteMessage):
body_data = None
header_data = None
+ # After some profiling it turns out that .find() and .findall() in this
+ # case are twice as fast as the xpath method
body = xmlelement.find('soap:body', namespaces=operation.binding.nsmap)
if body is not None:
body_data = cls._parse_body(body)
@@ -270,15 +294,16 @@ class SoapMessage(ConcreteMessage):
elements from the body and the headers.
"""
- all_elements = xsd.Sequence([
- xsd.Element('body', self.body.type),
- ])
+ all_elements = xsd.Sequence([])
if self.header.type._element:
all_elements.append(
- xsd.Element('header', self.header.type))
+ xsd.Element('{%s}header' % self.nsmap['soap-env'], self.header.type))
- return xsd.Element('envelope', xsd.ComplexType(all_elements))
+ all_elements.append(
+ xsd.Element('{%s}body' % self.nsmap['soap-env'], self.body.type))
+
+ return xsd.Element('{%s}envelope' % self.nsmap['soap-env'], xsd.ComplexType(all_elements))
def _serialize_header(self, headers_value, nsmap):
if not headers_value:
@@ -327,9 +352,9 @@ class SoapMessage(ConcreteMessage):
def _resolve_header(self, info, definitions, parts):
name = etree.QName(self.nsmap['soap-env'], 'Header')
- sequence = xsd.Sequence()
+ container = xsd.All(consume_other=True)
if not info:
- return xsd.Element(name, xsd.ComplexType(sequence))
+ return xsd.Element(name, xsd.ComplexType(container))
for item in info:
message_name = item['message'].text
@@ -345,14 +370,28 @@ class SoapMessage(ConcreteMessage):
element.attr_name = part_name
else:
element = xsd.Element(part_name, part.type)
- sequence.append(element)
- return xsd.Element(name, xsd.ComplexType(sequence))
+ container.append(element)
+ return xsd.Element(name, xsd.ComplexType(container))
class DocumentMessage(SoapMessage):
"""In the document message there are no additional wrappers, and the
message parts appear directly under the SOAP Body element.
+ .. inheritance-diagram:: zeep.wsdl.messages.soap.DocumentMessage
+ :parts: 1
+
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param type: 'input' or 'output'
+ :type type: str
+ :param nsmap: The namespace mapping
+ :type nsmap: dict
+
+
"""
def __init__(self, *args, **kwargs):
@@ -407,6 +446,20 @@ class RpcMessage(SoapMessage):
identically to the corresponding parameter of the call. Parts are arranged
in the same order as the parameters of the call.
+ .. inheritance-diagram:: zeep.wsdl.messages.soap.DocumentMessage
+ :parts: 1
+
+
+ :param wsdl: The main wsdl document
+ :type wsdl: zeep.wsdl.Document
+ :param name:
+ :param operation: The operation to which this message belongs
+ :type operation: zeep.wsdl.bindings.soap.SoapOperation
+ :param type: 'input' or 'output'
+ :type type: str
+ :param nsmap: The namespace mapping
+ :type nsmap: dict
+
"""
def _resolve_body(self, info, definitions, parts):
@@ -444,6 +497,8 @@ class RpcMessage(SoapMessage):
element.
"""
+ process_multiref(body_element)
+
response_element = body_element.getchildren()[0]
if self.body:
result = self.body.parse(response_element, self.wsdl.types)
diff --git a/src/zeep/wsdl/parse.py b/src/zeep/wsdl/parse.py
index 865ce15..f2b1506 100644
--- a/src/zeep/wsdl/parse.py
+++ b/src/zeep/wsdl/parse.py
@@ -1,5 +1,11 @@
+"""
+ zeep.wsdl.parse
+ ~~~~~~~~~~~~~~~
+
+"""
from lxml import etree
+from zeep.exceptions import IncompleteMessage, LookupError, NamespaceError
from zeep.utils import qname_attr
from zeep.wsdl import definitions
@@ -12,11 +18,20 @@ NSMAP = {
def parse_abstract_message(wsdl, xmlelement):
"""Create an AbstractMessage object from a xml element.
+ Definition::
+
<definitions .... >
<message name="nmtoken"> *
<part name="nmtoken" element="qname"? type="qname"?/> *
</message>
</definitions>
+
+ :param wsdl: The parent definition instance
+ :type wsdl: zeep.wsdl.wsdl.Definition
+ :param xmlelement: The XML node
+ :type xmlelement: lxml.etree._Element
+ :rtype: zeep.wsdl.definitions.AbstractMessage
+
"""
tns = wsdl.target_namespace
parts = []
@@ -26,10 +41,17 @@ def parse_abstract_message(wsdl, xmlelement):
part_element = qname_attr(part, 'element', tns)
part_type = qname_attr(part, 'type', tns)
- if part_element is not None:
- part_element = wsdl.types.get_element(part_element)
- if part_type is not None:
- part_type = wsdl.types.get_type(part_type)
+ try:
+ if part_element is not None:
+ part_element = wsdl.types.get_element(part_element)
+ if part_type is not None:
+ part_type = wsdl.types.get_type(part_type)
+
+ except (NamespaceError, LookupError):
+ raise IncompleteMessage((
+ "The wsdl:message for %r contains "
+ "invalid xsd types or elements"
+ ) % part_name)
part = definitions.MessagePart(part_element, part_type)
parts.append((part_name, part))
@@ -48,6 +70,8 @@ def parse_abstract_operation(wsdl, xmlelement):
This is called from the parse_port_type function since the abstract
operations are part of the port type element.
+ Definition::
+
<wsdl:operation name="nmtoken">*
<wsdl:documentation .... /> ?
<wsdl:input name="nmtoken"? message="qname">?
@@ -61,6 +85,12 @@ def parse_abstract_operation(wsdl, xmlelement):
</wsdl:fault>
</wsdl:operation>
+ :param wsdl: The parent definition instance
+ :type wsdl: zeep.wsdl.wsdl.Definition
+ :param xmlelement: The XML node
+ :type xmlelement: lxml.etree._Element
+ :rtype: zeep.wsdl.definitions.AbstractOperation
+
"""
name = xmlelement.get('name')
kwargs = {
@@ -75,7 +105,11 @@ def parse_abstract_operation(wsdl, xmlelement):
param_msg = qname_attr(
msg_node, 'message', wsdl.target_namespace)
param_name = msg_node.get('name')
- param_value = wsdl.get('messages', param_msg.text)
+
+ try:
+ param_value = wsdl.get('messages', param_msg.text)
+ except IndexError:
+ return
if tag_name == 'input':
kwargs['input_message'] = param_value
@@ -95,18 +129,27 @@ def parse_abstract_operation(wsdl, xmlelement):
def parse_port_type(wsdl, xmlelement):
"""Create a PortType object from a xml element.
+ Definition::
+
<wsdl:definitions .... >
<wsdl:portType name="nmtoken">
<wsdl:operation name="nmtoken" .... /> *
</wsdl:portType>
</wsdl:definitions>
+ :param wsdl: The parent definition instance
+ :type wsdl: zeep.wsdl.wsdl.Definition
+ :param xmlelement: The XML node
+ :type xmlelement: lxml.etree._Element
+ :rtype: zeep.wsdl.definitions.PortType
+
"""
name = qname_attr(xmlelement, 'name', wsdl.target_namespace)
operations = {}
for elm in xmlelement.findall('wsdl:operation', namespaces=NSMAP):
operation = parse_abstract_operation(wsdl, elm)
- operations[operation.name] = operation
+ if operation:
+ operations[operation.name] = operation
return definitions.PortType(name, operations)
@@ -116,11 +159,19 @@ def parse_port(wsdl, xmlelement):
This is called via the parse_service function since ports are part of the
service xml elements.
+ Definition::
+
<wsdl:port name="nmtoken" binding="qname"> *
<wsdl:documentation .... /> ?
<-- extensibility element -->
</wsdl:port>
+ :param wsdl: The parent definition instance
+ :type wsdl: zeep.wsdl.wsdl.Definition
+ :param xmlelement: The XML node
+ :type xmlelement: lxml.etree._Element
+ :rtype: zeep.wsdl.definitions.Port
+
"""
name = xmlelement.get('name')
binding_name = qname_attr(xmlelement, 'binding', wsdl.target_namespace)
@@ -130,7 +181,7 @@ def parse_port(wsdl, xmlelement):
def parse_service(wsdl, xmlelement):
"""
- Syntax::
+ Definition::
<wsdl:service name="nmtoken"> *
<wsdl:documentation .... />?
@@ -150,6 +201,12 @@ def parse_service(wsdl, xmlelement):
</port>
</service>
+ :param wsdl: The parent definition instance
+ :type wsdl: zeep.wsdl.wsdl.Definition
+ :param xmlelement: The XML node
+ :type xmlelement: lxml.etree._Element
+ :rtype: zeep.wsdl.definitions.Service
+
"""
name = xmlelement.get('name')
ports = []
diff --git a/src/zeep/wsdl/utils.py b/src/zeep/wsdl/utils.py
index 6785d56..e73ac20 100644
--- a/src/zeep/wsdl/utils.py
+++ b/src/zeep/wsdl/utils.py
@@ -1,3 +1,8 @@
+"""
+ zeep.wsdl.utils
+ ~~~~~~~~~~~~~~~
+
+"""
from lxml import etree
from six.moves.urllib.parse import urlparse, urlunparse
diff --git a/src/zeep/wsdl/wsdl.py b/src/zeep/wsdl/wsdl.py
index 23a81cb..bc43806 100644
--- a/src/zeep/wsdl/wsdl.py
+++ b/src/zeep/wsdl/wsdl.py
@@ -1,15 +1,21 @@
+"""
+ zeep.wsdl.wsdl
+ ~~~~~~~~~~~~~~
+
+"""
from __future__ import print_function
import logging
import operator
import os
+import warnings
from collections import OrderedDict
import six
from lxml import etree
-from zeep.parser import (
- absolute_location, is_relative_path, load_external, parse_xml)
+from zeep.exceptions import IncompleteMessage
+from zeep.loader import absolute_location, is_relative_path, load_external
from zeep.utils import findall_multiple_ns
from zeep.wsdl import parse
from zeep.xsd import Schema
@@ -34,18 +40,23 @@ class Document(object):
resolves references which were not yet available during the initial
parsing phase.
+
+ :param location: Location of this WSDL
+ :type location: string
+ :param transport: The transport object to be used
+ :type transport: zeep.transports.Transport
+ :param base: The base location of this document
+ :type base: str
+ :param strict: Indicates if strict mode is enabled
+ :type strict: bool
+
"""
- def __init__(self, location, transport, base=None):
+ def __init__(self, location, transport, base=None, strict=True):
"""Initialize a WSDL document.
The root definition properties are exposed as entry points.
- :param location: Location of this WSDL
- :type location: string
- :param transport: The transport object to be used
- :type transport: zeep.transports.Transport
-
"""
if isinstance(location, six.string_types):
if is_relative_path(location):
@@ -55,12 +66,17 @@ class Document(object):
self.location = base
self.transport = transport
+ self.strict = strict
# Dict with all definition objects within this WSDL
self._definitions = {}
- self.types = Schema([], transport=self.transport, location=self.location)
+ self.types = Schema(
+ node=None,
+ transport=self.transport,
+ location=self.location,
+ strict=self.strict)
- document = self._load_content(location)
+ document = self._get_xml_document(location)
root_definitions = Definition(self, document, self.location)
root_definitions.resolve_imports()
@@ -75,8 +91,6 @@ class Document(object):
return '<WSDL(location=%r)>' % self.location
def dump(self):
- namespaces = {v: k for k, v in self.types.prefix_map.items()}
-
print('')
print("Prefixes:")
for prefix, namespace in self.types.prefix_map.items():
@@ -84,18 +98,14 @@ class Document(object):
print('')
print("Global elements:")
- for elm_obj in sorted(self.types.elements, key=lambda k: six.text_type(k)):
- value = six.text_type(elm_obj)
- if hasattr(elm_obj, 'qname') and elm_obj.qname.namespace:
- value = '%s:%s' % (namespaces[elm_obj.qname.namespace], value)
+ for elm_obj in sorted(self.types.elements, key=lambda k: k.qname):
+ value = elm_obj.signature(schema=self.types)
print(' ' * 4, value)
print('')
print("Global types:")
for type_obj in sorted(self.types.types, key=lambda k: k.qname or ''):
- value = six.text_type(type_obj)
- if getattr(type_obj, 'qname', None) and type_obj.qname.namespace:
- value = '%s:%s' % (namespaces[type_obj.qname.namespace], value)
+ value = type_obj.signature(schema=self.types)
print(' ' * 4, value)
print('')
@@ -118,7 +128,7 @@ class Document(object):
print('%s%s' % (' ' * 12, six.text_type(operation)))
print('')
- def _load_content(self, location):
+ def _get_xml_document(self, location):
"""Load the XML content from the given location and return an
lxml.Element object.
@@ -126,9 +136,8 @@ class Document(object):
:type location: string
"""
- if hasattr(location, 'read'):
- return parse_xml(location.read())
- return load_external(location, self.transport, self.location)
+ return load_external(
+ location, self.transport, self.location, strict=self.strict)
def _add_definition(self, definition):
key = (definition.target_namespace, definition.location)
@@ -136,9 +145,18 @@ class Document(object):
class Definition(object):
- """The Definition represents one wsdl:definition within a Document."""
+ """The Definition represents one wsdl:definition within a Document.
+
+ :param wsdl: The wsdl
+
+ """
def __init__(self, wsdl, doc, location):
+ """fo
+
+ :param wsdl: The wsdl
+
+ """
logger.debug("Creating definition for %s", location)
self.wsdl = wsdl
self.location = location
@@ -183,7 +201,16 @@ class Definition(object):
try:
return definition.get(name, key, _processed)
except IndexError:
- pass
+ # Try to see if there is an item which has no namespace
+ # but where the localname matches. This is basically for
+ # #356 but in the future we should also ignore mismatching
+ # namespaces as last fallback
+ fallback_key = etree.QName(key).localname
+ try:
+ return definition.get(name, fallback_key, _processed)
+ except IndexError:
+ pass
+
raise IndexError("No definition %r in %r found" % (key, name))
def resolve_imports(self):
@@ -234,7 +261,7 @@ class Definition(object):
if key in self.wsdl._definitions:
self.imports[key] = self.wsdl._definitions[key]
else:
- document = self.wsdl._load_content(location)
+ document = self.wsdl._get_xml_document(location)
if etree.QName(document.tag).localname == 'schema':
self.types.add_documents([document], location)
else:
@@ -251,6 +278,8 @@ class Definition(object):
If the wsdl:types doesn't container an xml schema then an empty schema
is returned instead.
+ Definition::
+
<definitions .... >
<types>
<xsd:schema .... />*
@@ -279,6 +308,9 @@ class Definition(object):
def parse_messages(self, doc):
"""
+
+ Definition::
+
<definitions .... >
<message name="nmtoken"> *
<part name="nmtoken" element="qname"? type="qname"?/> *
@@ -291,14 +323,20 @@ class Definition(object):
"""
result = {}
for msg_node in doc.findall("wsdl:message", namespaces=NSMAP):
- msg = parse.parse_abstract_message(self, msg_node)
- result[msg.name.text] = msg
- logger.debug("Adding message: %s", msg.name.text)
+ try:
+ msg = parse.parse_abstract_message(self, msg_node)
+ except IncompleteMessage as exc:
+ warnings.warn(str(exc))
+ else:
+ result[msg.name.text] = msg
+ logger.debug("Adding message: %s", msg.name.text)
return result
def parse_ports(self, doc):
"""Return dict with `PortType` instances as values
+ Definition::
+
<wsdl:definitions .... >
<wsdl:portType name="nmtoken">
<wsdl:operation name="nmtoken" .... /> *
@@ -323,7 +361,7 @@ class Definition(object):
HTTP Post. The detection of the type of bindings is done by the
bindings themselves using the introspection of the xml nodes.
- XML Structure::
+ Definition::
<wsdl:definitions .... >
<wsdl:binding name="nmtoken" type="qname"> *
@@ -345,6 +383,9 @@ class Definition(object):
:param doc: The source document
:type doc: lxml.etree._Element
+ :returns: Dictionary with binding name as key and Binding instance as
+ value
+ :rtype: dict
"""
result = {}
@@ -382,6 +423,9 @@ class Definition(object):
def parse_service(self, doc):
"""
+
+ Definition::
+
<wsdl:definitions .... >
<wsdl:service .... > *
<wsdl:port name="nmtoken" binding="qname"> *
diff --git a/src/zeep/wsse/signature.py b/src/zeep/wsse/signature.py
index 120619a..ccc8e18 100644
--- a/src/zeep/wsse/signature.py
+++ b/src/zeep/wsse/signature.py
@@ -11,38 +11,63 @@ module.
from lxml import etree
from lxml.etree import QName
+from zeep import ns
+from zeep.exceptions import SignatureVerificationFailed
+from zeep.utils import detect_soap_env
+from zeep.wsse.utils import ensure_id, get_security_header
+
try:
import xmlsec
except ImportError:
xmlsec = None
-from zeep import ns
-from zeep.utils import detect_soap_env
-from zeep.exceptions import SignatureVerificationFailed
-from zeep.wsse.utils import ensure_id, get_security_header
# 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)
+ return key
+
+def _make_verify_key(cert_data):
+ key = xmlsec.Key.from_memory(cert_data,
+ xmlsec.KeyFormat.CERT_PEM, None)
+ return key
-class Signature(object):
+class MemorySignature(object):
"""Sign given SOAP envelope with WSSE sig using given key and cert."""
- def __init__(self, key_file, certfile, password=None):
+ def __init__(self, key_data, cert_data, password=None):
check_xmlsec_import()
- self.key_file = key_file
- self.certfile = certfile
+ self.key_data = key_data
+ self.cert_data = cert_data
self.password = password
def apply(self, envelope, headers):
- sign_envelope(envelope, self.key_file, self.certfile, self.password)
+ key = _make_sign_key(self.key_data, self.cert_data, self.password)
+ _sign_envelope_with_key(envelope, key)
return envelope, headers
def verify(self, envelope):
- verify_envelope(envelope, self.certfile)
+ key = _make_verify_key(self.cert_data)
+ _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)
def check_xmlsec_import():
if xmlsec is None:
@@ -141,6 +166,12 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
</soap:Envelope>
"""
+ # Load the signing key and certificate.
+ 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):
+
# Create the Signature node.
signature = xmlsec.template.create(
envelope,
@@ -155,10 +186,6 @@ def sign_envelope(envelope, keyfile, certfile, password=None):
xmlsec.template.x509_data_add_issuer_serial(x509_data)
xmlsec.template.x509_data_add_certificate(x509_data)
- # Load the signing key and certificate.
- key = xmlsec.Key.from_file(keyfile, xmlsec.KeyFormat.PEM, password=password)
- key.load_cert_from_file(certfile, xmlsec.KeyFormat.PEM)
-
# Insert the Signature node in the wsse:Security header.
security = get_security_header(envelope)
security.insert(0, signature)
@@ -190,12 +217,19 @@ def verify_envelope(envelope, certfile):
Expects a document like that found in the sample XML in the ``sign()``
docstring.
- Raise SignatureValidationFailed on failure, silent on success.
+ Raise SignatureVerificationFailed on failure, silent on success.
"""
+ 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:
+ raise SignatureVerificationFailed()
+
security = header.find(QName(ns.WSSE, 'Security'))
signature = security.find(QName(ns.DS, 'Signature'))
@@ -213,7 +247,6 @@ def verify_envelope(envelope, certfile):
)[0]
ctx.register_id(referenced, 'Id', ns.WSU)
- key = xmlsec.Key.from_file(certfile, xmlsec.KeyFormat.CERT_PEM, None)
ctx.key = key
try:
diff --git a/src/zeep/wsse/utils.py b/src/zeep/wsse/utils.py
index d08cfc1..4f96a38 100644
--- a/src/zeep/wsse/utils.py
+++ b/src/zeep/wsse/utils.py
@@ -1,8 +1,8 @@
-from uuid import uuid4
-from lxml import etree
import datetime
+from uuid import uuid4
import pytz
+from lxml import etree
from lxml.builder import ElementMaker
from zeep import ns
diff --git a/src/zeep/xsd/__init__.py b/src/zeep/xsd/__init__.py
index 1fc975d..dfe6358 100644
--- a/src/zeep/xsd/__init__.py
+++ b/src/zeep/xsd/__init__.py
@@ -1,4 +1,9 @@
-from zeep.xsd.const import SkipValue # noqa
+"""
+ zeep.xsd
+ --------
+
+"""
+from zeep.xsd.const import Nil, SkipValue # noqa
from zeep.xsd.elements import * # noqa
from zeep.xsd.schema import Schema # noqa
from zeep.xsd.types import * # noqa
diff --git a/src/zeep/xsd/const.py b/src/zeep/xsd/const.py
index 7ba420c..75b4097 100644
--- a/src/zeep/xsd/const.py
+++ b/src/zeep/xsd/const.py
@@ -1,15 +1,13 @@
from lxml import etree
-NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
-NS_XSD = 'http://www.w3.org/2001/XMLSchema'
-
+from zeep import ns
def xsi_ns(localname):
- return etree.QName(NS_XSI, localname)
+ return etree.QName(ns.XSI, localname)
def xsd_ns(localname):
- return etree.QName(NS_XSD, localname)
+ return etree.QName(ns.XSD, localname)
class _StaticIdentity(object):
@@ -22,3 +20,4 @@ class _StaticIdentity(object):
NotSet = _StaticIdentity('NotSet')
SkipValue = _StaticIdentity('SkipValue')
+Nil = _StaticIdentity('Nil')
diff --git a/src/zeep/xsd/elements/any.py b/src/zeep/xsd/elements/any.py
index d91c506..e799191 100644
--- a/src/zeep/xsd/elements/any.py
+++ b/src/zeep/xsd/elements/any.py
@@ -2,9 +2,9 @@ import logging
from lxml import etree
-from zeep import exceptions
+from zeep import exceptions, ns
from zeep.utils import qname_attr
-from zeep.xsd.const import xsi_ns, NotSet
+from zeep.xsd.const import NotSet, xsi_ns
from zeep.xsd.elements.base import Base
from zeep.xsd.utils import max_occurs_iter
from zeep.xsd.valueobjects import AnyObject
@@ -84,7 +84,19 @@ class Any(Base):
return {}
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
- """Consume matching xmlelements and call parse() on each of them"""
+ """Consume matching xmlelements and call parse() on each of them
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :return: dict or None
+
+ """
result = []
for _unused in max_occurs_iter(self.max_occurs):
@@ -169,9 +181,10 @@ class Any(Base):
# Check if we received a proper value object. If we receive the wrong
# type then return a nice error message
if self.restrict:
- expected_types = (etree._Element,) + self.restrict.accepted_types
+ expected_types = (etree._Element, dict,) + self.restrict.accepted_types
else:
- expected_types = (etree._Element, AnyObject)
+ expected_types = (etree._Element, dict,AnyObject)
+
if not isinstance(value, expected_types):
type_names = [
'%s.%s' % (t.__module__, t.__name__) for t in expected_types
@@ -188,7 +201,7 @@ class Any(Base):
def resolve(self):
return self
- def signature(self, depth=()):
+ def signature(self, schema=None, standalone=True):
if self.restrict:
base = self.restrict.name
else:
@@ -201,23 +214,30 @@ class Any(Base):
class AnyAttribute(Base):
name = None
+ _ignore_attributes = [
+ etree.QName(ns.XSI, 'type')
+ ]
def __init__(self, process_contents='strict'):
self.qname = None
self.process_contents = process_contents
def parse(self, attributes, context=None):
- return attributes
+ result = {}
+ for key, value in attributes.items():
+ if key not in self._ignore_attributes:
+ result[key] = value
+ return result
def resolve(self):
return self
def render(self, parent, value, render_path=None):
- if value is None:
+ if value in (None, NotSet):
return
for name, val in value.items():
parent.set(name, val)
- def signature(self, depth=()):
+ def signature(self, schema=None, standalone=True):
return '{}'
diff --git a/src/zeep/xsd/elements/attribute.py b/src/zeep/xsd/elements/attribute.py
index 04ec751..5ca075a 100644
--- a/src/zeep/xsd/elements/attribute.py
+++ b/src/zeep/xsd/elements/attribute.py
@@ -88,5 +88,5 @@ class AttributeGroup(object):
self._attributes = resolved
return self
- def signature(self, depth=()):
- return ', '.join(attr.signature() for attr in self._attributes)
+ def signature(self, schema=None, standalone=True):
+ return ', '.join(attr.signature(schema) for attr in self._attributes)
diff --git a/src/zeep/xsd/elements/base.py b/src/zeep/xsd/elements/base.py
index 64ff835..499ccac 100644
--- a/src/zeep/xsd/elements/base.py
+++ b/src/zeep/xsd/elements/base.py
@@ -25,8 +25,20 @@ class Base(object):
raise NotImplementedError()
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
- """Consume matching xmlelements and call parse() on each of them"""
+ """Consume matching xmlelements and call parse() on each of them
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :return: dict or None
+
+ """
raise NotImplementedError()
- def signature(self, depth=()):
+ def signature(self, schema=None, standalone=False):
return ''
diff --git a/src/zeep/xsd/elements/builtins.py b/src/zeep/xsd/elements/builtins.py
index 5eca4c6..eedf460 100644
--- a/src/zeep/xsd/elements/builtins.py
+++ b/src/zeep/xsd/elements/builtins.py
@@ -35,6 +35,6 @@ class Schema(Base):
return self
-default_elements = {
- xsd_ns('schema'): Schema(),
-}
+_elements = [
+ Schema
+]
diff --git a/src/zeep/xsd/elements/element.py b/src/zeep/xsd/elements/element.py
index ebb1739..204e532 100644
--- a/src/zeep/xsd/elements/element.py
+++ b/src/zeep/xsd/elements/element.py
@@ -6,10 +6,10 @@ from lxml import etree
from zeep import exceptions
from zeep.exceptions import UnexpectedElementError
from zeep.utils import qname_attr
-from zeep.xsd.const import NotSet, xsi_ns
+from zeep.xsd.const import Nil, NotSet, xsi_ns
from zeep.xsd.context import XmlParserContext
from zeep.xsd.elements.base import Base
-from zeep.xsd.utils import max_occurs_iter
+from zeep.xsd.utils import create_prefixed_name, max_occurs_iter
logger = logging.getLogger(__name__)
@@ -38,7 +38,10 @@ class Element(Base):
def __str__(self):
if self.type:
- return '%s(%s)' % (self.name, self.type.signature())
+ if self.type.is_global:
+ return '%s(%s)' % (self.name, self.type.qname)
+ else:
+ return '%s(%s)' % (self.name, self.type.signature())
return '%s()' % self.name
def __call__(self, *args, **kwargs):
@@ -57,10 +60,16 @@ class Element(Base):
self.__class__ == other.__class__ and
self.__dict__ == other.__dict__)
+ def get_prefixed_name(self, schema):
+ return create_prefixed_name(self.qname, schema)
+
@property
def default_value(self):
- value = [] if self.accepts_multiple else self.default
- return value
+ if self.accepts_multiple:
+ return []
+ if self.is_optional:
+ return None
+ return self.default
def clone(self, name=None, min_occurs=1, max_occurs=1):
new = copy.copy(self)
@@ -81,6 +90,18 @@ class Element(Base):
use that for further processing. This should only be done for subtypes
of the defined type but for now we just accept everything.
+ This is the entrypoint for parsing an xml document.
+
+ :param xmlelement: The XML element to parse
+ :type xmlelements: lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param allow_none: Allow none
+ :type allow_none: bool
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :return: dict or None
+
"""
context = context or XmlParserContext()
instance_type = qname_attr(xmlelement, xsi_ns('type'))
@@ -89,14 +110,27 @@ class Element(Base):
xsd_type = schema.get_type(instance_type, fail_silently=True)
xsd_type = xsd_type or self.type
return xsd_type.parse_xmlelement(
- xmlelement, schema, allow_none=allow_none, context=context)
+ xmlelement, schema, allow_none=allow_none, context=context,
+ schema_type=self.type)
def parse_kwargs(self, kwargs, name, available_kwargs):
return self.type.parse_kwargs(
kwargs, name or self.attr_name, available_kwargs)
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
- """Consume matching xmlelements and call parse() on each of them"""
+ """Consume matching xmlelements and call parse() on each of them
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :return: dict or None
+
+ """
result = []
num_matches = 0
for _unused in max_occurs_iter(self.max_occurs):
@@ -113,7 +147,8 @@ class Element(Base):
element_tag = etree.QName(xmlelements[0].tag)
if (
element_tag.namespace and self.qname.namespace and
- element_tag.namespace != self.qname.namespace
+ element_tag.namespace != self.qname.namespace and
+ schema.strict
):
break
@@ -123,8 +158,7 @@ class Element(Base):
num_matches += 1
item = self.parse(
xmlelement, schema, allow_none=True, context=context)
- if item is not None:
- result.append(item)
+ result.append(item)
else:
# If the element passed doesn't match and the current one is
# not optional then throw an error
@@ -158,6 +192,12 @@ class Element(Base):
def _render_value_item(self, parent, value, render_path):
"""Render the value on the parent lxml.Element"""
+
+ if value is Nil:
+ elm = etree.SubElement(parent, self.qname)
+ elm.set(xsi_ns('nil'), 'true')
+ return
+
if value is None or value is NotSet:
if self.is_optional:
return
@@ -215,11 +255,19 @@ class Element(Base):
self.resolve_type()
return self
- def signature(self, depth=()):
- if len(depth) > 0 and self.is_global:
- return self.name + '()'
+ def signature(self, schema=None, standalone=True):
+ from zeep.xsd import ComplexType
+ if self.type.is_global or (not standalone and self.is_global):
+ value = self.type.get_prefixed_name(schema)
+ else:
+ value = self.type.signature(schema, standalone=False)
+
+ if not standalone and isinstance(self.type, ComplexType):
+ value = '{%s}' % value
+
+ if standalone:
+ value = '%s(%s)' % (self.get_prefixed_name(schema), value)
- value = self.type.signature(depth)
if self.accepts_multiple:
return '%s[]' % value
return value
diff --git a/src/zeep/xsd/elements/indicators.py b/src/zeep/xsd/elements/indicators.py
index 282b13a..10ccf00 100644
--- a/src/zeep/xsd/elements/indicators.py
+++ b/src/zeep/xsd/elements/indicators.py
@@ -1,36 +1,56 @@
+"""
+zeep.xsd.elements.indicators
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Indicators are a collection of elements. There are four available, these are
+All, Choice, Group and Sequence.
+
+ Indicator -> OrderIndicator -> All
+ -> Choice
+ -> Sequence
+ -> Group
+
+"""
import copy
import operator
from collections import OrderedDict, defaultdict, deque
from cached_property import threaded_cached_property
-from zeep.exceptions import UnexpectedElementError
+from zeep.exceptions import UnexpectedElementError, ValidationError
from zeep.xsd.const import NotSet, SkipValue
from zeep.xsd.elements import Any, Element
from zeep.xsd.elements.base import Base
from zeep.xsd.utils import (
- NamePrefixGenerator, UniqueNameGenerator, max_occurs_iter)
+ NamePrefixGenerator, UniqueNameGenerator, create_prefixed_name,
+ max_occurs_iter)
__all__ = ['All', 'Choice', 'Group', 'Sequence']
class Indicator(Base):
+ """Base class for the other indicators"""
def __repr__(self):
return '<%s(%s)>' % (
self.__class__.__name__, super(Indicator, self).__repr__())
- @threaded_cached_property
+ @property
def default_value(self):
- return OrderedDict([
+ values = OrderedDict([
(name, element.default_value) for name, element in self.elements
])
+ if self.accepts_multiple:
+ return {'_value_1': values}
+ return values
+
def clone(self, name, min_occurs=1, max_occurs=1):
raise NotImplementedError()
class OrderIndicator(Indicator, list):
+ """Base class for All, Choice and Sequence classes."""
name = None
def __init__(self, elements=None, min_occurs=1, max_occurs=1):
@@ -85,16 +105,30 @@ class OrderIndicator(Indicator, list):
If not all required elements are available then 0 is returned.
"""
- num = 0
- for name, element in self.elements_nested:
- if isinstance(element, Element):
- if element.name in values and values[element.name] is not None:
- num += 1
- else:
- num += element.accept(values)
- return num
+ if not self.accepts_multiple:
+ values = [values]
+
+ results = set()
+ for value in values:
+ num = 0
+ for name, element in self.elements_nested:
+ if isinstance(element, Element):
+ if element.name in value and value[element.name] is not None:
+ num += 1
+ else:
+ num += element.accept(value)
+ results.add(num)
+ return max(results)
def parse_args(self, args, index=0):
+
+ # If the sequence contains an choice element then we can't convert
+ # the args to kwargs since Choice elements don't work with position
+ # arguments
+ for name, elm in self.elements_nested:
+ if isinstance(elm, Choice):
+ raise TypeError("Choice elements only work with keyword arguments")
+
result = {}
for name, element in self.elements:
if index >= len(args):
@@ -110,11 +144,24 @@ class OrderIndicator(Indicator, list):
The available_kwargs is modified in-place. Returns a dict with the
result.
+ :param kwargs: The kwargs
+ :type kwargs: dict
+ :param name: The name as which this type is registered in the parent
+ :type name: str
+ :param available_kwargs: The kwargs keys which are still available,
+ modified in place
+ :type available_kwargs: set
+ :rtype: dict
+
"""
if self.accepts_multiple:
assert name
- if name and name in available_kwargs:
+ if name:
+ if name not in available_kwargs:
+ return {}
+
+ assert self.accepts_multiple
# Make sure we have a list, lame lame
item_kwargs = kwargs.get(name)
@@ -123,19 +170,26 @@ class OrderIndicator(Indicator, list):
result = []
for item_value in max_occurs_iter(self.max_occurs, item_kwargs):
- item_kwargs = set(item_value.keys())
+ try:
+ item_kwargs = set(item_value.keys())
+ except AttributeError:
+ raise TypeError(
+ "A list of dicts is expected for unbounded Sequences")
+
subresult = OrderedDict()
for item_name, element in self.elements:
value = element.parse_kwargs(item_value, item_name, item_kwargs)
if value is not None:
subresult.update(value)
+ if item_kwargs:
+ raise TypeError((
+ "%s() got an unexpected keyword argument %r."
+ ) % (self, list(item_kwargs)[0]))
+
result.append(subresult)
- if self.accepts_multiple:
- result = {name: result}
- else:
- result = result[0] if result else None
+ result = {name: result}
# All items consumed
if not any(filter(None, item_kwargs)):
@@ -144,15 +198,13 @@ class OrderIndicator(Indicator, list):
return result
else:
+ assert not self.accepts_multiple
result = OrderedDict()
for elm_name, element in self.elements_nested:
sub_result = element.parse_kwargs(kwargs, elm_name, available_kwargs)
if sub_result:
result.update(sub_result)
- if name:
- result = {name: result}
-
return result
def resolve(self):
@@ -167,6 +219,8 @@ class OrderIndicator(Indicator, list):
else:
values = value
+ self.validate(values, render_path)
+
for value in max_occurs_iter(self.max_occurs, values):
for name, element in self.elements_nested:
if name:
@@ -186,22 +240,20 @@ class OrderIndicator(Indicator, list):
if element_value is not None or not element.is_optional:
element.render(parent, element_value, child_path)
- def signature(self, depth=()):
- """
- Use a tuple of element names as depth indicator, so that when an element is repeated,
- do not try to create its signature, as it would lead to infinite recursion
- """
- depth += (self.name,)
+ def validate(self, value, render_path):
+ for item in value:
+ if item is NotSet:
+ raise ValidationError("No value set", path=render_path)
+
+ def signature(self, schema=None, standalone=True):
parts = []
for name, element in self.elements_nested:
- if hasattr(element, 'type') and element.type.name and element.type.name in depth:
- parts.append('{}: {}'.format(name, element.type.name))
- elif name:
- parts.append('%s: %s' % (name, element.signature(depth)))
- elif isinstance(element, Indicator):
- parts.append('%s' % (element.signature(depth)))
+ if isinstance(element, Indicator):
+ parts.append(element.signature(schema, standalone=False))
else:
- parts.append('%s: %s' % (name, element.signature(depth)))
+ value = element.signature(schema, standalone=False)
+ parts.append('%s: %s' % (name, value))
+
part = ', '.join(parts)
if self.accepts_multiple:
@@ -215,7 +267,25 @@ class All(OrderIndicator):
"""
+ def __init__(self, elements=None, min_occurs=1, max_occurs=1,
+ consume_other=False):
+ super(All, self).__init__(elements, min_occurs, max_occurs)
+ self._consume_other = consume_other
+
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+ """Consume matching xmlelements
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :rtype: dict or None
+
+ """
result = OrderedDict()
expected_tags = {element.qname for __, element in self.elements}
consumed_tags = set()
@@ -236,10 +306,18 @@ class All(OrderIndicator):
result[name] = element.parse_xmlelements(
sub_elements, schema, context=context)
+ if self._consume_other and xmlelements:
+ result['_raw_elements'] = list(xmlelements)
+ xmlelements.clear()
return result
class Choice(OrderIndicator):
+ """Permits one and only one of the elements contained in the group."""
+
+ def parse_args(self, args, index=0):
+ if args:
+ raise TypeError("Choice elements only work with keyword arguments")
@property
def is_optional(self):
@@ -250,49 +328,59 @@ class Choice(OrderIndicator):
return OrderedDict()
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
- """Return a dictionary"""
+ """Consume matching xmlelements
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :rtype: dict or None
+
+ """
result = []
for _unused in max_occurs_iter(self.max_occurs):
if not xmlelements:
break
- for node in list(xmlelements):
-
- # Choose out of multiple
- options = []
- for element_name, element in self.elements_nested:
+ # Choose out of multiple
+ options = []
+ for element_name, element in self.elements_nested:
- local_xmlelements = copy.copy(xmlelements)
+ local_xmlelements = copy.copy(xmlelements)
- try:
- sub_result = element.parse_xmlelements(
- local_xmlelements, schema, context=context)
- except UnexpectedElementError:
- continue
+ try:
+ sub_result = element.parse_xmlelements(
+ xmlelements=local_xmlelements,
+ schema=schema,
+ name=element_name,
+ context=context)
+ except UnexpectedElementError:
+ continue
- if isinstance(element, OrderIndicator):
- if element.accepts_multiple:
- sub_result = {element_name: sub_result}
- else:
- sub_result = {element_name: sub_result}
+ if isinstance(element, Element):
+ sub_result = {element_name: sub_result}
- num_consumed = len(xmlelements) - len(local_xmlelements)
- if num_consumed:
- options.append((num_consumed, sub_result))
+ num_consumed = len(xmlelements) - len(local_xmlelements)
+ if num_consumed:
+ options.append((num_consumed, sub_result))
- if not options:
- xmlelements = []
- break
+ if not options:
+ xmlelements = []
+ break
- # Sort on least left
- options = sorted(options, key=operator.itemgetter(0), reverse=True)
- if options:
- result.append(options[0][1])
- for i in range(options[0][0]):
- xmlelements.popleft()
- else:
- break
+ # Sort on least left
+ options = sorted(options, key=operator.itemgetter(0), reverse=True)
+ if options:
+ result.append(options[0][1])
+ for i in range(options[0][0]):
+ xmlelements.popleft()
+ else:
+ break
if self.accepts_multiple:
result = {name: result}
@@ -308,7 +396,7 @@ class Choice(OrderIndicator):
This handles two distinct initialization methods:
1. Passing the choice elements directly to the kwargs (unnested)
- 2. Passing the choice elements into the `name` kwarg (_alue_1) (nested).
+ 2. Passing the choice elements into the `name` kwarg (_value_1) (nested).
This case is required when multiple choice elements are given.
:param name: Name of the choice element (_value_1)
@@ -320,6 +408,8 @@ class Choice(OrderIndicator):
"""
if name and name in available_kwargs:
+ assert self.accepts_multiple
+
values = kwargs[name] or []
available_kwargs.remove(name)
result = []
@@ -327,9 +417,9 @@ class Choice(OrderIndicator):
if isinstance(values, dict):
values = [values]
+ # TODO: Use most greedy choice instead of first matching
for value in values:
for element in self:
- # TODO: Use most greedy choice instead of first matching
if isinstance(element, OrderIndicator):
choice_value = value[name] if name in value else value
if element.accept(choice_value):
@@ -356,9 +446,9 @@ class Choice(OrderIndicator):
# When choice elements are specified directly in the kwargs
found = False
- for i, choice in enumerate(self):
+ for name, choice in self.elements_nested:
temp_kwargs = copy.copy(available_kwargs)
- subresult = choice.parse_kwargs(kwargs, None, temp_kwargs)
+ subresult = choice.parse_kwargs(kwargs, name, temp_kwargs)
if subresult:
if not any(subresult.values()):
@@ -388,12 +478,24 @@ class Choice(OrderIndicator):
if not self.accepts_multiple:
value = [value]
+ self.validate(value, render_path)
+
for item in value:
result = self._find_element_to_render(item)
if result:
element, choice_value = result
element.render(parent, choice_value, render_path)
+ def validate(self, value, render_path):
+ found = 0
+ for item in value:
+ result = self._find_element_to_render(item)
+ if result:
+ found += 1
+
+ if not found and not self.is_optional:
+ raise ValidationError("Missing choice values", path=render_path)
+
def accept(self, values):
"""Return the number of values which are accepted by this choice.
@@ -403,15 +505,24 @@ class Choice(OrderIndicator):
nums = set()
for name, element in self.elements_nested:
if isinstance(element, Element):
- if name in values and values[name]:
- nums.add(1)
+ if self.accepts_multiple:
+ if all(name in item and item[name] for item in values):
+ nums.add(1)
+ else:
+ if name in values and values[name]:
+ nums.add(1)
else:
num = element.accept(values)
nums.add(num)
return max(nums) if nums else 0
def _find_element_to_render(self, value):
- """Return a tuple (element, value) for the best matching choice"""
+ """Return a tuple (element, value) for the best matching choice.
+
+ This is used to decide which choice child is best suitable for
+ rendering the available data.
+
+ """
matches = []
for name, element in self.elements_nested:
@@ -441,13 +552,13 @@ class Choice(OrderIndicator):
matches = sorted(matches, key=operator.itemgetter(0), reverse=True)
return matches[0][1:]
- def signature(self, depth=()):
+ def signature(self, schema=None, standalone=True):
parts = []
for name, element in self.elements_nested:
if isinstance(element, OrderIndicator):
- parts.append('{%s}' % (element.signature(depth)))
+ parts.append('{%s}' % (element.signature(schema, standalone=False)))
else:
- parts.append('{%s: %s}' % (name, element.signature(depth)))
+ parts.append('{%s: %s}' % (name, element.signature(schema, standalone=False)))
part = '(%s)' % ' | '.join(parts)
if self.accepts_multiple:
return '%s[]' % (part,)
@@ -455,17 +566,42 @@ class Choice(OrderIndicator):
class Sequence(OrderIndicator):
+ """Requires the elements in the group to appear in the specified sequence
+ within the containing element.
+ """
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+ """Consume matching xmlelements
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :rtype: dict or None
+
+ """
result = []
+
+ if self.accepts_multiple:
+ assert name
+
for _unused in max_occurs_iter(self.max_occurs):
if not xmlelements:
break
item_result = OrderedDict()
for elm_name, element in self.elements:
- item_subresult = element.parse_xmlelements(
- xmlelements, schema, name, context=context)
+ try:
+ item_subresult = element.parse_xmlelements(
+ xmlelements, schema, name, context=context)
+ except UnexpectedElementError:
+ if schema.strict:
+ raise
+ item_subresult = None
# Unwrap if allowed
if isinstance(element, OrderIndicator):
@@ -480,7 +616,6 @@ class Sequence(OrderIndicator):
if not self.accepts_multiple:
return result[0] if result else None
-
return {name: result}
@@ -498,15 +633,8 @@ class Group(Indicator):
self.max_occurs = max_occurs
self.min_occurs = min_occurs
- def clone(self, name, min_occurs=1, max_occurs=1):
- return self.__class__(
- name=self.qname,
- child=self.child,
- min_occurs=min_occurs,
- max_occurs=max_occurs)
-
def __str__(self):
- return '%s(%s)' % (self.name, self.signature())
+ return self.signature()
def __iter__(self, *args, **kwargs):
for item in self.child:
@@ -518,13 +646,28 @@ class Group(Indicator):
return [('_value_1', self.child)]
return self.child.elements
+ def clone(self, name, min_occurs=1, max_occurs=1):
+ return self.__class__(
+ name=name,
+ child=self.child,
+ min_occurs=min_occurs,
+ max_occurs=max_occurs)
+
+ def accept(self, values):
+ """Return the number of values which are accepted by this choice.
+
+ If not all required elements are available then 0 is returned.
+
+ """
+ return self.child.accept(values)
+
def parse_args(self, args, index=0):
return self.child.parse_args(args, index)
def parse_kwargs(self, kwargs, name, available_kwargs):
if self.accepts_multiple:
if name not in kwargs:
- return {}, kwargs
+ return {}
available_kwargs.remove(name)
item_kwargs = kwargs[name]
@@ -536,6 +679,11 @@ class Group(Indicator):
subresult = self.child.parse_kwargs(
sub_kwargs, sub_name, available_sub_kwargs)
+ if available_sub_kwargs:
+ raise TypeError((
+ "%s() got an unexpected keyword argument %r."
+ ) % (self, list(available_sub_kwargs)[0]))
+
if subresult:
result.append(subresult)
if result:
@@ -545,6 +693,19 @@ class Group(Indicator):
return result
def parse_xmlelements(self, xmlelements, schema, name=None, context=None):
+ """Consume matching xmlelements
+
+ :param xmlelements: Dequeue of XML element objects
+ :type xmlelements: collections.deque of lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param name: The name of the parent element
+ :type name: str
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :rtype: dict or None
+
+ """
result = []
for _unused in max_occurs_iter(self.max_occurs):
@@ -552,16 +713,29 @@ class Group(Indicator):
self.child.parse_xmlelements(
xmlelements, schema, name, context=context)
)
+ if not xmlelements:
+ break
if not self.accepts_multiple and result:
return result[0]
return {name: result}
- def render(self, *args, **kwargs):
- return self.child.render(*args, **kwargs)
+ def render(self, parent, value, render_path):
+ if not isinstance(value, list):
+ values = [value]
+ else:
+ values = value
+
+ for value in values:
+ self.child.render(parent, value, render_path)
def resolve(self):
self.child = self.child.resolve()
return self
- def signature(self, depth=()):
- return self.child.signature(depth)
+ def signature(self, schema=None, standalone=True):
+ name = create_prefixed_name(self.qname, schema)
+ if standalone:
+ return '%s(%s)' % (
+ name, self.child.signature(schema, standalone=False))
+ else:
+ return self.child.signature(schema, standalone=False)
diff --git a/src/zeep/xsd/elements/references.py b/src/zeep/xsd/elements/references.py
index d3eb1ce..4aa30b5 100644
--- a/src/zeep/xsd/elements/references.py
+++ b/src/zeep/xsd/elements/references.py
@@ -1,8 +1,15 @@
+"""
+zeep.xsd.elements.references
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Ref* objecs are only used temporarily between parsing the schema and resolving
+all the elements.
+
+"""
__all__ = ['RefElement', 'RefAttribute', 'RefAttributeGroup', 'RefGroup']
class RefElement(object):
-
def __init__(self, tag, ref, schema, is_qualified=False,
min_occurs=1, max_occurs=1):
self._ref = ref
@@ -37,4 +44,7 @@ class RefAttributeGroup(RefElement):
class RefGroup(RefElement):
def resolve(self):
- return self._schema.get_group(self._ref)
+ elm = self._schema.get_group(self._ref)
+ elm = elm.clone(
+ elm.qname, min_occurs=self.min_occurs, max_occurs=self.max_occurs)
+ return elm
diff --git a/src/zeep/xsd/schema.py b/src/zeep/xsd/schema.py
index 93993c1..8026009 100644
--- a/src/zeep/xsd/schema.py
+++ b/src/zeep/xsd/schema.py
@@ -3,8 +3,7 @@ from collections import OrderedDict
from lxml import etree
-from zeep import exceptions
-from zeep.xsd import const
+from zeep import exceptions, ns
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
@@ -15,13 +14,24 @@ logger = logging.getLogger(__name__)
class Schema(object):
"""A schema is a collection of schema documents."""
- def __init__(self, node=None, transport=None, location=None):
+ def __init__(self, node=None, transport=None, location=None, strict=True):
+ """
+ :param node:
+ :param transport:
+ :param location:
+ :param strict: Boolean to indicate if the parsing is strict (default)
+
+ """
+ self.strict = strict
+
self._transport = transport
self._documents = OrderedDict()
self._prefix_map_auto = {}
self._prefix_map_custom = {}
+ self._load_default_documents()
+
if not isinstance(node, list):
nodes = [node] if node is not None else []
else:
@@ -45,6 +55,12 @@ class Schema(object):
return retval
@property
+ def root_document(self):
+ return next(
+ (doc for doc in self.documents if not doc._is_internal),
+ None)
+
+ @property
def is_empty(self):
"""Boolean to indicate if this schema contains any types or elements"""
return all(document.is_empty for document in self.documents)
@@ -55,25 +71,38 @@ class Schema(object):
@property
def elements(self):
- """Yield all globla xsd.Type objects"""
+ """Yield all globla xsd.Type objects
+
+ :rtype: Iterable of zeep.xsd.Element
+
+ """
+ seen = set()
for document in self.documents:
for element in document._elements.values():
- yield element
+ if element.qname not in seen:
+ yield element
+ seen.add(element.qname)
@property
def types(self):
- """Yield all globla xsd.Type objects"""
+ """Yield all global xsd.Type objects
+
+ :rtype: Iterable of zeep.xsd.ComplexType
+
+ """
+ seen = set()
for document in self.documents:
for type_ in document._types.values():
- yield type_
+ if type_.qname not in seen:
+ yield type_
+ seen.add(type_.qname)
def __repr__(self):
- if self._documents:
- main_doc = next(self.documents)
- location = main_doc._location
- else:
- location = '<none>'
- return '<Schema(location=%r)>' % location
+ main_doc = self.root_document
+ if main_doc:
+ return '<Schema(location=%r, tns=%r)>' % (
+ main_doc._location, main_doc._target_namespace)
+ return '<Schema()>'
def add_documents(self, schema_nodes, location):
documents = []
@@ -87,49 +116,51 @@ class Schema(object):
self._prefix_map_auto = self._create_prefix_map()
def get_element(self, qname):
- """Return a global xsd.Element object with the given qname"""
- qname = self._create_qname(qname)
- if qname.text in xsd_builtins_elements.default_elements:
- return xsd_builtins_elements.default_elements[qname]
+ """Return a global xsd.Element object with the given qname
- # Handle XSD namespace items
- if qname.namespace == const.NS_XSD:
- try:
- return xsd_builtins_elements.default_elements[qname]
- except KeyError:
- raise exceptions.LookupError("No such type %r" % qname.text)
+ :rtype: zeep.xsd.Group
+ """
+ qname = self._create_qname(qname)
return self._get_instance(qname, 'get_element', 'element')
def get_type(self, qname, fail_silently=False):
- """Return a global xsd.Type object with the given qname"""
- qname = self._create_qname(qname)
+ """Return a global xsd.Type object with the given qname
- # Handle XSD namespace items
- if qname.namespace == const.NS_XSD:
- try:
- return xsd_builtins_types.default_types[qname]
- except KeyError:
- raise exceptions.LookupError("No such type %r" % qname.text)
+ :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+ """
+ qname = self._create_qname(qname)
try:
return self._get_instance(qname, 'get_type', 'type')
except exceptions.NamespaceError as exc:
if fail_silently:
- logger.info(str(exc))
+ logger.debug(str(exc))
else:
raise
def get_group(self, qname):
- """Return a global xsd.Group object with the given qname"""
+ """Return a global xsd.Group object with the given qname.
+
+ :rtype: zeep.xsd.Group
+
+ """
return self._get_instance(qname, 'get_group', 'group')
def get_attribute(self, qname):
- """Return a global xsd.attributeGroup object with the given qname"""
+ """Return a global xsd.attributeGroup object with the given qname
+
+ :rtype: zeep.xsd.Attribute
+
+ """
return self._get_instance(qname, 'get_attribute', 'attribute')
def get_attribute_group(self, qname):
- """Return a global xsd.attributeGroup object with the given qname"""
+ """Return a global xsd.attributeGroup object with the given qname
+
+ :rtype: zeep.xsd.AttributeGroup
+
+ """
return self._get_instance(qname, 'get_attribute_group', 'attributeGroup')
def set_ns_prefix(self, prefix, namespace):
@@ -144,6 +175,18 @@ class Schema(object):
except KeyError:
raise ValueError("No such prefix %r" % prefix)
+ def get_shorthand_for_ns(self, namespace):
+ for prefix, other_namespace in self._prefix_map_auto.items():
+ if namespace == other_namespace:
+ return prefix
+ for prefix, other_namespace in self._prefix_map_custom.items():
+ if namespace == other_namespace:
+ return prefix
+
+ if namespace == 'http://schemas.xmlsoap.org/soap/envelope/':
+ return 'soap-env'
+ return namespace
+
def create_new_document(self, node, url, base_url=None):
namespace = node.get('targetNamespace') if node is not None else None
if base_url is None:
@@ -160,6 +203,21 @@ class Schema(object):
self._add_schema_document(document)
self._prefix_map_auto = self._create_prefix_map()
+ def _load_default_documents(self):
+ schema = SchemaDocument(ns.XSD, None, None)
+
+ for cls in xsd_builtins_types._types:
+ instance = cls(is_global=True)
+ schema.register_type(cls._default_qname, instance)
+
+ for cls in xsd_builtins_elements._elements:
+ instance = cls()
+ schema.register_element(cls.qname, instance)
+
+ schema._is_internal = True
+ self._add_schema_document(schema)
+ return schema
+
def _get_instance(self, qname, method_name, name):
"""Return an object from one of the SchemaDocument's"""
qname = self._create_qname(qname)
@@ -185,6 +243,8 @@ class Schema(object):
This also expands the shorthand notation.
+ :rtype: lxml.etree.QNaame
+
"""
if isinstance(name, etree.QName):
return name
@@ -205,26 +265,45 @@ class Schema(object):
prefix_map = {
'xsd': 'http://www.w3.org/2001/XMLSchema',
}
- for i, namespace in enumerate(self._documents.keys()):
- if namespace is None:
+ i = 0
+ for namespace in self._documents.keys():
+ if namespace is None or namespace in prefix_map.values():
continue
+
prefix_map['ns%d' % i] = namespace
+ i += 1
return prefix_map
def _has_schema_document(self, namespace):
+ """Return a boolean if there is a SchemaDocumnet for the namespace.
+
+ :rtype: boolean
+
+ """
return namespace in self._documents
def _add_schema_document(self, document):
- logger.info("Add document with tns %s to schema %s", document.namespace, id(self))
+ logger.debug("Add document with tns %s to schema %s", document.namespace, id(self))
documents = self._documents.setdefault(document.namespace, [])
documents.append(document)
def _get_schema_document(self, namespace, location):
+ """Return a list of SchemaDocument's for the given namespace AND
+ location.
+
+ :rtype: SchemaDocument
+
+ """
for document in self._documents.get(namespace, []):
if document._location == location:
return document
def _get_schema_documents(self, namespace, fail_silently=False):
+ """Return a list of SchemaDocument's for the given namespace.
+
+ :rtype: list of SchemaDocument
+
+ """
if namespace not in self._documents:
if fail_silently:
return []
@@ -241,7 +320,7 @@ class SchemaDocument(object):
self._base_url = base_url or location
self._location = location
self._target_namespace = namespace
- self._elm_instances = []
+ self._is_internal = False
self._attribute_groups = {}
self._attributes = {}
@@ -283,7 +362,7 @@ class SchemaDocument(object):
visitor.visit_schema(node)
def resolve(self):
- logger.info("Resolving in schema %s", self)
+ logger.debug("Resolving in schema %s", self)
if self._resolved:
return
@@ -294,10 +373,23 @@ class SchemaDocument(object):
schema.resolve()
def _resolve_dict(val):
- for key, obj in val.items():
- new = obj.resolve()
- assert new is not None, "resolve() should return an object"
- val[key] = new
+ try:
+ for key, obj in val.items():
+ new = obj.resolve()
+ assert new is not None, "resolve() should return an object"
+ val[key] = new
+ except exceptions.LookupError as exc:
+ raise exceptions.LookupError(
+ (
+ "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,
+ 'parent': obj.qname,
+ })
_resolve_dict(self._attribute_groups)
_resolve_dict(self._attributes)
@@ -305,10 +397,6 @@ class SchemaDocument(object):
_resolve_dict(self._groups)
_resolve_dict(self._types)
- for element in self._elm_instances:
- element.resolve()
- self._elm_instances = []
-
def register_import(self, namespace, schema):
schemas = self._imports.setdefault(namespace, [])
schemas.append(schema)
@@ -350,23 +438,43 @@ class SchemaDocument(object):
self._attribute_groups[name] = value
def get_type(self, qname):
- """Return a xsd.Type object from this schema"""
+ """Return a xsd.Type object from this schema
+
+ :rtype: zeep.xsd.ComplexType or zeep.xsd.AnySimpleType
+
+ """
return self._get_instance(qname, self._types, 'type')
def get_element(self, qname):
- """Return a xsd.Element object from this schema"""
+ """Return a xsd.Element object from this schema
+
+ :rtype: zeep.xsd.Element
+
+ """
return self._get_instance(qname, self._elements, 'element')
def get_group(self, qname):
- """Return a xsd.Group object from this schema"""
+ """Return a xsd.Group object from this schema.
+
+ :rtype: zeep.xsd.Group
+
+ """
return self._get_instance(qname, self._groups, 'group')
def get_attribute(self, qname):
- """Return a xsd.Attribute object from this schema"""
+ """Return a xsd.Attribute object from this schema
+
+ :rtype: zeep.xsd.Attribute
+
+ """
return self._get_instance(qname, self._attributes, 'attribute')
def get_attribute_group(self, qname):
- """Return a xsd.AttributeGroup object from this schema"""
+ """Return a xsd.AttributeGroup object from this schema
+
+ :rtype: zeep.xsd.AttributeGroup
+
+ """
return self._get_instance(qname, self._attribute_groups, 'attributeGroup')
def _get_instance(self, qname, items, item_name):
@@ -377,10 +485,13 @@ class SchemaDocument(object):
raise exceptions.LookupError((
"No %(item_name)s '%(localname)s' in namespace %(namespace)s. " +
"Available %(item_name_plural)s are: %(known_items)s"
- ) % {
- 'item_name': item_name,
- 'item_name_plural': item_name + 's',
- 'localname': qname.localname,
- 'namespace': qname.namespace,
- 'known_items': known_items or ' - '
- })
+ ) % {
+ 'item_name': item_name,
+ 'item_name_plural': item_name + 's',
+ 'localname': qname.localname,
+ 'namespace': qname.namespace,
+ 'known_items': known_items or ' - '
+ },
+ qname=qname,
+ item_name=item_name,
+ location=self._location)
diff --git a/src/zeep/xsd/types/any.py b/src/zeep/xsd/types/any.py
index a667b61..c5e994d 100644
--- a/src/zeep/xsd/types/any.py
+++ b/src/zeep/xsd/types/any.py
@@ -12,6 +12,11 @@ __all__ = ['AnyType']
class AnyType(Type):
_default_qname = xsd_ns('anyType')
+ _attributes_unwrapped = []
+ _element = None
+
+ def __call__(self, value=None):
+ return value or ''
def render(self, parent, value, xsd_type=None, render_path=None):
if isinstance(value, AnyObject):
@@ -27,7 +32,22 @@ class AnyType(Type):
parent.text = self.xmlvalue(value)
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
- context=None):
+ context=None, schema_type=None):
+ """Consume matching xmlelements and call parse() on each
+
+ :param xmlelement: XML element objects
+ :type xmlelement: lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param allow_none: Allow none
+ :type allow_none: bool
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :param schema_type: The original type (not overriden via xsi:type)
+ :type schema_type: zeep.xsd.types.base.Type
+ :rtype: dict or None
+
+ """
xsi_type = qname_attr(xmlelement, xsi_ns('type'))
xsi_nil = xmlelement.get(xsi_ns('nil'))
children = list(xmlelement.getchildren())
@@ -42,8 +62,14 @@ class AnyType(Type):
xsd_type = schema.get_type(xsi_type, fail_silently=True)
# If we were unable to resolve a type for the xsi:type (due to
- # buggy soap servers) then we just return the lxml element.
+ # buggy soap servers) then we just return the text or lxml element.
if not xsd_type:
+ logger.debug(
+ "Unable to resolve type for %r, returning raw data",
+ xsi_type.text)
+
+ if xmlelement.text:
+ return xmlelement.text
return children
# If the xsd_type is xsd:anyType then we will recurs so ignore
@@ -72,3 +98,6 @@ class AnyType(Type):
def pythonvalue(self, value, schema=None):
return value
+
+ def signature(self, schema=None, standalone=True):
+ return 'xsd:anyType'
diff --git a/src/zeep/xsd/types/base.py b/src/zeep/xsd/types/base.py
index d67cb85..f8a2233 100644
--- a/src/zeep/xsd/types/base.py
+++ b/src/zeep/xsd/types/base.py
@@ -1,3 +1,8 @@
+from zeep.xsd.utils import create_prefixed_name
+
+__all__ = ['Type']
+
+
class Type(object):
def __init__(self, qname=None, is_global=False):
@@ -6,9 +11,16 @@ class Type(object):
self._resolved = False
self.is_global = is_global
+ def get_prefixed_name(self, schema):
+ return create_prefixed_name(self.qname, schema)
+
def accept(self, value):
raise NotImplementedError
+ @property
+ def accepted_types(self):
+ return tuple()
+
def validate(self, value, required=False):
return
@@ -23,7 +35,7 @@ class Type(object):
return {}
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
- context=None):
+ context=None, schema_type=None):
raise NotImplementedError(
'%s.parse_xmlelement() is not implemented' % self.__class__.__name__)
@@ -51,61 +63,5 @@ class Type(object):
return []
@classmethod
- def signature(cls, depth=()):
+ def signature(cls, schema=None, standalone=True):
return ''
-
-
-class UnresolvedType(Type):
- def __init__(self, qname, schema):
- self.qname = qname
- assert self.qname.text != 'None'
- self.schema = schema
-
- def __repr__(self):
- return '<%s(qname=%r)>' % (self.__class__.__name__, self.qname)
-
- def render(self, parent, value, xsd_type=None, render_path=None):
- raise RuntimeError(
- "Unable to render unresolved type %s. This is probably a bug." % (
- self.qname))
-
- def resolve(self):
- retval = self.schema.get_type(self.qname)
- return retval.resolve()
-
-
-class UnresolvedCustomType(Type):
-
- def __init__(self, qname, base_type, schema):
- assert qname is not None
- self.qname = qname
- self.name = str(qname.localname)
- self.schema = schema
- self.base_type = base_type
-
- def __repr__(self):
- return '<%s(qname=%r, base_type=%r)>' % (
- self.__class__.__name__, self.qname.text, self.base_type)
-
- def resolve(self):
- base = self.base_type
- base = base.resolve()
-
- cls_attributes = {
- '__module__': 'zeep.xsd.dynamic_types',
- }
-
- from zeep.xsd.types.collection import UnionType # FIXME
- from zeep.xsd.types.simple import AnySimpleType # FIXME
-
- if issubclass(base.__class__, UnionType):
- xsd_type = type(self.name, (base.__class__,), cls_attributes)
- return xsd_type(base.item_types)
-
- elif issubclass(base.__class__, AnySimpleType):
- xsd_type = type(self.name, (base.__class__,), cls_attributes)
- return xsd_type(self.qname)
-
- else:
- xsd_type = type(self.name, (base.base_class,), cls_attributes)
- return xsd_type(self.qname)
diff --git a/src/zeep/xsd/types/builtins.py b/src/zeep/xsd/types/builtins.py
index 49b8c3e..0bbe5d9 100644
--- a/src/zeep/xsd/types/builtins.py
+++ b/src/zeep/xsd/types/builtins.py
@@ -17,6 +17,11 @@ class ParseError(ValueError):
pass
+class BuiltinType(object):
+ def __init__(self, qname=None, is_global=False):
+ super(BuiltinType, self).__init__(qname, is_global=True)
+
+
def check_no_collection(func):
def _wrapper(self, value):
if isinstance(value, (list, dict, set)):
@@ -30,7 +35,7 @@ def check_no_collection(func):
##
# Primitive types
-class String(AnySimpleType):
+class String(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('string')
accepted_types = six.string_types
@@ -44,13 +49,13 @@ class String(AnySimpleType):
return value
-class Boolean(AnySimpleType):
+class Boolean(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('boolean')
accepted_types = (bool,)
@check_no_collection
def xmlvalue(self, value):
- return 'true' if value else 'false'
+ return 'true' if value and value not in ('false', '0') else 'false'
def pythonvalue(self, value):
"""Return True if the 'true' or '1'. 'false' and '0' are legal false
@@ -60,7 +65,7 @@ class Boolean(AnySimpleType):
return value in ('true', '1')
-class Decimal(AnySimpleType):
+class Decimal(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('decimal')
accepted_types = (_Decimal, float) + six.string_types
@@ -72,7 +77,7 @@ class Decimal(AnySimpleType):
return _Decimal(value)
-class Float(AnySimpleType):
+class Float(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('float')
accepted_types = (float, _Decimal) + six.string_types
@@ -83,7 +88,7 @@ class Float(AnySimpleType):
return float(value)
-class Double(AnySimpleType):
+class Double(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('double')
accepted_types = (_Decimal, float) + six.string_types
@@ -95,7 +100,7 @@ class Double(AnySimpleType):
return float(value)
-class Duration(AnySimpleType):
+class Duration(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('duration')
accepted_types = (isodate.duration.Duration,) + six.string_types
@@ -107,7 +112,7 @@ class Duration(AnySimpleType):
return isodate.parse_duration(value)
-class DateTime(AnySimpleType):
+class DateTime(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('dateTime')
accepted_types = (datetime.datetime,) + six.string_types
@@ -131,7 +136,7 @@ class DateTime(AnySimpleType):
return isodate.parse_datetime(value)
-class Time(AnySimpleType):
+class Time(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('time')
accepted_types = (datetime.time,) + six.string_types
@@ -145,7 +150,7 @@ class Time(AnySimpleType):
return isodate.parse_time(value)
-class Date(AnySimpleType):
+class Date(BuiltinType, AnySimpleType):
_default_qname = xsd_ns('date')
accepted_types = (datetime.date,) + six.string_types
@@ -159,7 +164,7 @@ class Date(AnySimpleType):
return isodate.parse_date(value)
-class gYearMonth(AnySimpleType):
+class gYearMonth(BuiltinType, AnySimpleType):
"""gYearMonth represents a specific gregorian month in a specific gregorian
year.
@@ -186,7 +191,7 @@ class gYearMonth(AnySimpleType):
_parse_timezone(group['timezone']))
-class gYear(AnySimpleType):
+class gYear(BuiltinType, AnySimpleType):
"""gYear represents a gregorian calendar year.
Lexical representation: CCYY
@@ -209,7 +214,7 @@ class gYear(AnySimpleType):
return (int(group['year']), _parse_timezone(group['timezone']))
-class gMonthDay(AnySimpleType):
+class gMonthDay(BuiltinType, AnySimpleType):
"""gMonthDay is a gregorian date that recurs, specifically a day of the
year such as the third of May.
@@ -237,7 +242,7 @@ class gMonthDay(AnySimpleType):
_parse_timezone(group['timezone']))
-class gDay(AnySimpleType):
+class gDay(BuiltinType, AnySimpleType):
"""gDay is a gregorian day that recurs, specifically a day of the month
such as the 5th of the month
@@ -261,7 +266,7 @@ class gDay(AnySimpleType):
return (int(group['day']), _parse_timezone(group['timezone']))
-class gMonth(AnySimpleType):
+class gMonth(BuiltinType, AnySimpleType):
"""gMonth is a gregorian month that recurs every year.
Lexical representation: --MM
@@ -284,7 +289,7 @@ class gMonth(AnySimpleType):
return (int(group['month']), _parse_timezone(group['timezone']))
-class HexBinary(AnySimpleType):
+class HexBinary(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('hexBinary')
@@ -296,7 +301,7 @@ class HexBinary(AnySimpleType):
return value
-class Base64Binary(AnySimpleType):
+class Base64Binary(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('base64Binary')
@@ -308,7 +313,7 @@ class Base64Binary(AnySimpleType):
return base64.b64decode(value)
-class AnyURI(AnySimpleType):
+class AnyURI(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('anyURI')
@@ -320,7 +325,7 @@ class AnyURI(AnySimpleType):
return value
-class QName(AnySimpleType):
+class QName(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('QName')
@@ -332,7 +337,7 @@ class QName(AnySimpleType):
return value
-class Notation(AnySimpleType):
+class Notation(BuiltinType, AnySimpleType):
accepted_types = six.string_types
_default_qname = xsd_ns('NOTATION')
@@ -390,6 +395,7 @@ class Entities(Entity):
class Integer(Decimal):
_default_qname = xsd_ns('integer')
+ accepted_types = (int, float) + six.string_types
def xmlvalue(self, value):
return str(value)
@@ -484,58 +490,60 @@ def _unparse_timezone(tzinfo):
return '-%02d:%02d' % (abs(hours), minutes)
+_types = [
+ # Primitive
+ String,
+ Boolean,
+ Decimal,
+ Float,
+ Double,
+ Duration,
+ DateTime,
+ Time,
+ Date,
+ gYearMonth,
+ gYear,
+ gMonthDay,
+ gDay,
+ gMonth,
+ HexBinary,
+ Base64Binary,
+ AnyURI,
+ QName,
+ Notation,
+
+ # Derived
+ NormalizedString,
+ Token,
+ Language,
+ NmToken,
+ NmTokens,
+ Name,
+ NCName,
+ ID,
+ IDREF,
+ IDREFS,
+ Entity,
+ Entities,
+ Integer,
+ NonPositiveInteger, # noqa
+ NegativeInteger,
+ Long,
+ Int,
+ Short,
+ Byte,
+ NonNegativeInteger, # noqa
+ UnsignedByte,
+ UnsignedInt,
+ UnsignedLong,
+ UnsignedShort,
+ PositiveInteger,
+
+ # Other
+ AnyType,
+ AnySimpleType,
+]
+
default_types = {
- cls._default_qname: cls() for cls in [
- # Primitive
- String,
- Boolean,
- Decimal,
- Float,
- Double,
- Duration,
- DateTime,
- Time,
- Date,
- gYearMonth,
- gYear,
- gMonthDay,
- gDay,
- gMonth,
- HexBinary,
- Base64Binary,
- AnyURI,
- QName,
- Notation,
-
- # Derived
- NormalizedString,
- Token,
- Language,
- NmToken,
- NmTokens,
- Name,
- NCName,
- ID,
- IDREF,
- IDREFS,
- Entity,
- Entities,
- Integer,
- NonPositiveInteger, # noqa
- NegativeInteger,
- Long,
- Int,
- Short,
- Byte,
- NonNegativeInteger, # noqa
- UnsignedByte,
- UnsignedInt,
- UnsignedLong,
- UnsignedShort,
- PositiveInteger,
-
- # Other
- AnyType,
- AnySimpleType,
- ]
+ cls._default_qname: cls(is_global=True) for cls in _types
}
diff --git a/src/zeep/xsd/types/collection.py b/src/zeep/xsd/types/collection.py
index f18b14c..5645f7d 100644
--- a/src/zeep/xsd/types/collection.py
+++ b/src/zeep/xsd/types/collection.py
@@ -32,8 +32,8 @@ class ListType(AnySimpleType):
item_type = self.item_type
return [item_type.pythonvalue(v) for v in value.split()]
- def signature(self, depth=()):
- return self.item_type.signature(depth) + '[]'
+ def signature(self, schema=None, standalone=True):
+ return self.item_type.signature(schema) + '[]'
class UnionType(AnySimpleType):
@@ -52,11 +52,11 @@ class UnionType(AnySimpleType):
self.item_class = base_class
return self
- def signature(self, depth=()):
+ def signature(self, schema=None, standalone=True):
return ''
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
- context=None):
+ context=None, schema_type=None):
if self.item_class:
return self.item_class().parse_xmlelement(
xmlelement, schema, allow_none, context)
diff --git a/src/zeep/xsd/types/complex.py b/src/zeep/xsd/types/complex.py
index c1b6925..7e65a70 100644
--- a/src/zeep/xsd/types/complex.py
+++ b/src/zeep/xsd/types/complex.py
@@ -6,14 +6,14 @@ from itertools import chain
from cached_property import threaded_cached_property
from zeep.exceptions import UnexpectedElementError, XMLParseError
-from zeep.xsd.const import xsi_ns, SkipValue, NotSet
+from zeep.xsd.const import NotSet, SkipValue, xsi_ns
from zeep.xsd.elements import (
Any, AnyAttribute, AttributeGroup, Choice, Element, Group, Sequence)
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
+from zeep.xsd.valueobjects import CompoundValue, ArrayValue
logger = logging.getLogger(__name__)
@@ -33,14 +33,24 @@ class ComplexType(AnyType):
self._attributes = attributes or []
self._restriction = restriction
self._extension = extension
+ self._extension_types = tuple()
super(ComplexType, self).__init__(qname=qname, is_global=is_global)
def __call__(self, *args, **kwargs):
+ if self._array_type:
+ return self._array_class(*args, **kwargs)
return self._value_class(*args, **kwargs)
@property
def accepted_types(self):
- return (self._value_class,)
+ return (self._value_class,) + self._extension_types
+
+ @threaded_cached_property
+ def _array_class(self):
+ assert self._array_type
+ return type(
+ self.__class__.__name__, (ArrayValue,),
+ {'_xsd_type': self, '__module__': 'zeep.objects'})
@threaded_cached_property
def _value_class(self):
@@ -94,25 +104,43 @@ class ComplexType(AnyType):
generator = NamePrefixGenerator()
# Handle wsdl:arrayType objects
- attrs = {attr.qname.text: attr for attr in self._attributes if attr.qname}
- array_type = attrs.get('{http://schemas.xmlsoap.org/soap/encoding/}arrayType')
- if array_type:
+ if self._array_type:
name = generator.get_name()
if isinstance(self._element, Group):
- return [(name, Sequence([
- Any(max_occurs='unbounded', restrict=array_type.array_type)
+ result = [(name, Sequence([
+ Any(max_occurs='unbounded', restrict=self._array_type.array_type)
]))]
else:
- return [(name, self._element)]
-
- # _element is one of All, Choice, Group, Sequence
- if self._element:
- result.append((generator.get_name(), self._element))
+ result = [(name, self._element)]
+ else:
+ # _element is one of All, Choice, Group, Sequence
+ if self._element:
+ result.append((generator.get_name(), self._element))
return result
- def parse_xmlelement(self, xmlelement, schema, allow_none=True,
- context=None):
- """Consume matching xmlelements and call parse() on each"""
+ @property
+ def _array_type(self):
+ attrs = {attr.qname.text: attr for attr in self._attributes if attr.qname}
+ array_type = attrs.get('{http://schemas.xmlsoap.org/soap/encoding/}arrayType')
+ return array_type
+
+ def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
+ context=None, schema_type=None):
+ """Consume matching xmlelements and call parse() on each
+
+ :param xmlelement: XML element objects
+ :type xmlelement: lxml.etree._Element
+ :param schema: The parent XML schema
+ :type schema: zeep.xsd.Schema
+ :param allow_none: Allow none
+ :type allow_none: bool
+ :param context: Optional parsing context (for inline schemas)
+ :type context: zeep.xsd.context.XmlParserContext
+ :param schema_type: The original type (not overriden via xsi:type)
+ :type schema_type: zeep.xsd.types.base.Type
+ :rtype: dict or None
+
+ """
# If this is an empty complexType (<xsd:complexType name="x"/>)
if not self.attributes and not self.elements:
return None
@@ -134,6 +162,7 @@ class ComplexType(AnyType):
# Parse elements. These are always indicator elements (all, choice,
# group, sequence)
+ assert len(self.elements_nested) < 2
for name, element in self.elements_nested:
try:
result = element.parse_xmlelements(
@@ -145,7 +174,10 @@ class ComplexType(AnyType):
# Check if all children are consumed (parsed)
if elements:
- raise XMLParseError("Unexpected element %r" % elements[0].tag)
+ if schema.strict:
+ raise XMLParseError("Unexpected element %r" % elements[0].tag)
+ else:
+ init_kwargs['_raw_elements'] = elements
# Parse attributes
if attributes:
@@ -158,12 +190,21 @@ class ComplexType(AnyType):
else:
init_kwargs[name] = attribute.parse(attributes)
- return self(**init_kwargs)
+ value = self._value_class(**init_kwargs)
+ schema_type = schema_type or self
+ if schema_type and getattr(schema_type, '_array_type', None):
+ value = schema_type._array_class.from_value_object(value)
+ return value
def render(self, parent, value, xsd_type=None, render_path=None):
"""Serialize the given value lxml.Element subelements on the parent
element.
+ :type parent: lxml.etree._Element
+ :type value: Union[list, dict, zeep.xsd.valueobjects.CompoundValue]
+ :type xsd_type: zeep.xsd.types.base.Type
+ :param render_path: list
+
"""
if not render_path:
render_path = [self.name]
@@ -171,12 +212,24 @@ class ComplexType(AnyType):
if not self.elements_nested and not self.attributes:
return
+ if isinstance(value, ArrayValue):
+ value = value.as_value_object()
+
# Render attributes
for name, attribute in self.attributes:
attr_value = value[name] if name in value else NotSet
child_path = render_path + [name]
attribute.render(parent, attr_value, child_path)
+ if (
+ len(self.elements_nested) == 1
+ and isinstance(value, self.accepted_types)
+ and not isinstance(value, (list, dict, CompoundValue))
+ ):
+ element = self.elements_nested[0][1]
+ element.type.render(parent, value, None, child_path)
+ return
+
# Render sub elements
for name, element in self.elements_nested:
if isinstance(element, Element) or element.accepts_multiple:
@@ -186,6 +239,7 @@ class ComplexType(AnyType):
element_value = value
child_path = list(render_path)
+ # We want to explicitly skip this sub-element
if element_value is SkipValue:
continue
@@ -201,6 +255,19 @@ class ComplexType(AnyType):
parent.set(xsi_ns('type'), xsd_type.qname)
def parse_kwargs(self, kwargs, name, available_kwargs):
+ """Parse the kwargs for this type and return the accepted data as
+ a dict.
+
+ :param kwargs: The kwargs
+ :type kwargs: dict
+ :param name: The name as which this type is registered in the parent
+ :type name: str
+ :param available_kwargs: The kwargs keys which are still available,
+ modified in place
+ :type available_kwargs: set
+ :rtype: dict
+
+ """
value = None
name = name or self.name
@@ -213,28 +280,27 @@ class ComplexType(AnyType):
return {}
def _create_object(self, value, name):
- """Return the value as a CompoundValue object"""
+ """Return the value as a CompoundValue object
+
+ :type value: str
+ :type value: list, dict, CompoundValue
+
+ """
if value is None:
return None
- if isinstance(value, list):
+ if isinstance(value, list) and not self._array_type:
return [self._create_object(val, name) for val in value]
- if isinstance(value, CompoundValue):
+ if isinstance(value, CompoundValue) or value is SkipValue:
return value
if isinstance(value, dict):
return self(**value)
- # Check if the valueclass only expects one value, in that case
- # we can try to automatically create an object for it.
- if len(self.attributes) + len(self.elements) == 1:
- return self(value)
-
- raise ValueError((
- "Error while create XML for complexType '%s': "
- "Expected instance of type %s, received %r instead."
- ) % (self.qname or name, self._value_class, type(value)))
+ # Try to automatically create an object. This might fail if there
+ # are multiple required arguments.
+ return self(value)
def resolve(self):
"""Resolve all sub elements and types"""
@@ -242,9 +308,6 @@ class ComplexType(AnyType):
return self._resolved
self._resolved = self
- if self._element:
- self._element = self._element.resolve()
-
resolved = []
for attribute in self._attributes:
value = attribute.resolve()
@@ -258,18 +321,17 @@ class ComplexType(AnyType):
if self._extension:
self._extension = self._extension.resolve()
self._resolved = self.extend(self._extension)
- return self._resolved
-
elif self._restriction:
self._restriction = self._restriction.resolve()
self._resolved = self.restrict(self._restriction)
- return self._resolved
- else:
- return self._resolved
+ if self._element:
+ self._element = self._element.resolve()
+
+ return self._resolved
def extend(self, base):
- """Create a new complextype instance which is the current type
+ """Create a new ComplexType instance which is the current type
extending the given base type.
Used for handling xsd:extension tags
@@ -277,6 +339,9 @@ class ComplexType(AnyType):
TODO: Needs a rewrite where the child containers are responsible for
the extend functionality.
+ :type base: zeep.xsd.types.base.Type
+ :rtype base: zeep.xsd.types.base.Type
+
"""
if isinstance(base, ComplexType):
base_attributes = base._attributes_unwrapped
@@ -301,6 +366,9 @@ class ComplexType(AnyType):
# container a placeholder element).
element = []
if self._element and base_element:
+ self._element = self._element.resolve()
+ base_element = base_element.resolve()
+
element = self._element.clone(self._element.name)
if isinstance(base_element, OrderIndicator):
if isinstance(self._element, Choice):
@@ -323,7 +391,10 @@ class ComplexType(AnyType):
new = self.__class__(
element=element,
attributes=attributes,
- qname=self.qname)
+ qname=self.qname,
+ is_global=self.is_global)
+
+ new._extension_types = base.accepted_types
return new
def restrict(self, base):
@@ -332,6 +403,10 @@ class ComplexType(AnyType):
Used for handling xsd:restriction
+ :type base: zeep.xsd.types.base.Type
+ :rtype base: zeep.xsd.types.base.Type
+
+
"""
attributes = list(
chain(base._attributes_unwrapped, self._attributes_unwrapped))
@@ -344,33 +419,30 @@ class ComplexType(AnyType):
new_attributes['##any'] = attr
else:
new_attributes[attr.qname.text] = attr
- attributes = new_attributes.values()
+ attributes = list(new_attributes.values())
+
+ if base._element:
+ base._element.resolve()
new = self.__class__(
element=self._element or base._element,
attributes=attributes,
- qname=self.qname)
+ qname=self.qname,
+ is_global=self.is_global)
return new.resolve()
- def signature(self, depth=()):
- if len(depth) > 0 and self.is_global:
- return self.name
-
+ def signature(self, schema=None, standalone=True):
parts = []
- depth += (self.name,)
for name, element in self.elements_nested:
- # http://schemas.xmlsoap.org/soap/encoding/ contains cyclic type
- if isinstance(element, Element) and element.type == self:
- continue
-
- part = element.signature(depth)
+ part = element.signature(schema, standalone=False)
parts.append(part)
for name, attribute in self.attributes:
- part = '%s: %s' % (name, attribute.signature(depth))
+ part = '%s: %s' % (name, attribute.signature(schema, standalone=False))
parts.append(part)
value = ', '.join(parts)
- if len(depth) > 1:
- value = '{%s}' % value
- return value
+ if standalone:
+ return '%s(%s)' % (self.get_prefixed_name(schema), value)
+ else:
+ return value
diff --git a/src/zeep/xsd/types/simple.py b/src/zeep/xsd/types/simple.py
index 989d292..4c5440a 100644
--- a/src/zeep/xsd/types/simple.py
+++ b/src/zeep/xsd/types/simple.py
@@ -4,7 +4,7 @@ import six
from lxml import etree
from zeep.exceptions import ValidationError
-from zeep.xsd.const import NS_XSD, xsd_ns
+from zeep.xsd.const import xsd_ns
from zeep.xsd.types.any import AnyType
logger = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ class AnySimpleType(AnyType):
return '%s(value)' % (self.__class__.__name__)
def parse_xmlelement(self, xmlelement, schema=None, allow_none=True,
- context=None):
+ context=None, schema_type=None):
if xmlelement.text is None:
return
try:
@@ -70,10 +70,8 @@ class AnySimpleType(AnyType):
def render(self, parent, value, xsd_type=None, render_path=None):
parent.text = self.xmlvalue(value)
- def signature(self, depth=()):
- if self.qname.namespace == NS_XSD:
- return 'xsd:%s' % self.name
- return self.name
+ def signature(self, schema=None, standalone=True):
+ return self.get_prefixed_name(schema)
def validate(self, value, required=False):
if required and value is None:
diff --git a/src/zeep/xsd/types/unresolved.py b/src/zeep/xsd/types/unresolved.py
new file mode 100644
index 0000000..e15a03e
--- /dev/null
+++ b/src/zeep/xsd/types/unresolved.py
@@ -0,0 +1,56 @@
+from zeep.xsd.types.base import Type
+from zeep.xsd.types.collection import UnionType # FIXME
+from zeep.xsd.types.simple import AnySimpleType # FIXME
+
+
+class UnresolvedType(Type):
+ def __init__(self, qname, schema):
+ self.qname = qname
+ assert self.qname.text != 'None'
+ self.schema = schema
+
+ def __repr__(self):
+ return '<%s(qname=%r)>' % (self.__class__.__name__, self.qname.text)
+
+ def render(self, parent, value, xsd_type=None, render_path=None):
+ raise RuntimeError(
+ "Unable to render unresolved type %s. This is probably a bug." % (
+ self.qname))
+
+ def resolve(self):
+ retval = self.schema.get_type(self.qname)
+ return retval.resolve()
+
+
+class UnresolvedCustomType(Type):
+
+ def __init__(self, qname, base_type, schema):
+ assert qname is not None
+ self.qname = qname
+ self.name = str(qname.localname)
+ self.schema = schema
+ self.base_type = base_type
+
+ def __repr__(self):
+ return '<%s(qname=%r, base_type=%r)>' % (
+ self.__class__.__name__, self.qname.text, self.base_type)
+
+ def resolve(self):
+ base = self.base_type
+ base = base.resolve()
+
+ cls_attributes = {
+ '__module__': 'zeep.xsd.dynamic_types',
+ }
+
+ if issubclass(base.__class__, UnionType):
+ xsd_type = type(self.name, (base.__class__,), cls_attributes)
+ return xsd_type(base.item_types)
+
+ elif issubclass(base.__class__, AnySimpleType):
+ xsd_type = type(self.name, (base.__class__,), cls_attributes)
+ return xsd_type(self.qname)
+
+ else:
+ xsd_type = type(self.name, (base.base_class,), cls_attributes)
+ return xsd_type(self.qname)
diff --git a/src/zeep/xsd/utils.py b/src/zeep/xsd/utils.py
index 2b5a3cc..e60c8f6 100644
--- a/src/zeep/xsd/utils.py
+++ b/src/zeep/xsd/utils.py
@@ -1,10 +1,6 @@
-from defusedxml.lxml import fromstring
-from lxml import etree
-
from six.moves import range
-from six.moves.urllib.parse import urlparse
-from zeep.exceptions import XMLSyntaxError
-from zeep.parser import absolute_location
+
+from zeep import ns
class NamePrefixGenerator(object):
@@ -31,34 +27,6 @@ class UniqueNameGenerator(object):
return name
-class ImportResolver(etree.Resolver):
- """Custom lxml resolve to use the transport object"""
- def __init__(self, transport):
- self.transport = transport
-
- def resolve(self, url, pubid, context):
- if urlparse(url).scheme in ('http', 'https'):
- content = self.transport.load(url)
- return self.resolve_string(content, context)
-
-
-def parse_xml(content, transport, base_url=None):
- parser = etree.XMLParser(remove_comments=True, resolve_entities=False)
- parser.resolvers.add(ImportResolver(transport))
- try:
- return fromstring(content, parser=parser, base_url=base_url)
- except etree.XMLSyntaxError as exc:
- raise XMLSyntaxError("Invalid XML content received (%s)" % exc.message)
-
-
-def load_external(url, transport, base_url=None):
- if base_url:
- url = absolute_location(url, base_url)
-
- response = transport.load(url)
- return parse_xml(response, transport, base_url)
-
-
def max_occurs_iter(max_occurs, items=None):
assert max_occurs is not None
generator = range(0, max_occurs if max_occurs != 'unbounded' else 2**31-1)
@@ -69,3 +37,27 @@ def max_occurs_iter(max_occurs, items=None):
else:
for i in generator:
yield i
+
+
+def create_prefixed_name(qname, schema):
+ """Convert a QName to a xsd:name ('ns1:myType').
+
+ :type qname: lxml.etree.QName
+ :type schema: zeep.xsd.schema.Schema
+ :rtype: str
+
+ """
+ if not qname:
+ return
+
+ if schema and qname.namespace:
+ prefix = schema.get_shorthand_for_ns(qname.namespace)
+ if prefix:
+ return '%s:%s' % (prefix, qname.localname)
+ elif qname.namespace in ns.NAMESPACE_TO_PREFIX:
+ prefix = ns.NAMESPACE_TO_PREFIX[qname.namespace]
+ return '%s:%s' % (prefix, qname.localname)
+
+ if qname.namespace:
+ return qname.text
+ return qname.localname
diff --git a/src/zeep/xsd/valueobjects.py b/src/zeep/xsd/valueobjects.py
index c2cf9c5..7ba86d8 100644
--- a/src/zeep/xsd/valueobjects.py
+++ b/src/zeep/xsd/valueobjects.py
@@ -33,11 +33,46 @@ class AnyObject(object):
return self.xsd_obj
+def _unpickle_compound_value(name, values):
+ """Helper function to recreate pickled CompoundValue.
+
+ See CompoundValue.__reduce__
+
+ """
+ cls = type(name, (CompoundValue,), {
+ '_xsd_type': None, '__module__': 'zeep.objects'
+ })
+ obj = cls()
+ obj.__values__ = values
+ return obj
+
+
+class ArrayValue(list):
+ def __init__(self, items):
+ super(ArrayValue, self).__init__(items)
+
+ def as_value_object(self):
+ anon_type = type(
+ self.__class__.__name__, (CompoundValue,),
+ {'_xsd_type': self._xsd_type, '__module__': 'zeep.objects'})
+ return anon_type(list(self))
+
+ @classmethod
+ def from_value_object(cls, obj):
+ items = next(iter(obj.__values__.values()))
+ return cls(items or [])
+
+
class CompoundValue(object):
+ """Represents a data object for a specific xsd:complexType."""
def __init__(self, *args, **kwargs):
values = OrderedDict()
+ # Can be done after unpickle
+ if self._xsd_type is None:
+ return
+
# Set default values
for container_name, container in self._xsd_type.elements_nested:
elm_values = container.default_value
@@ -56,6 +91,9 @@ class CompoundValue(object):
values[key] = value
self.__values__ = values
+ def __reduce__(self):
+ return (_unpickle_compound_value, (self.__class__.__name__, self.__values__,))
+
def __contains__(self, key):
return self.__values__.__contains__(key)
@@ -107,6 +145,9 @@ class CompoundValue(object):
setattr(new, attr, value)
return new
+ def __json__(self):
+ return self.__values__
+
def _process_signature(xsd_type, args, kwargs):
"""Return a dict with the args/kwargs mapped to the field name.
@@ -169,13 +210,19 @@ def _process_signature(xsd_type, args, kwargs):
available_kwargs.remove(attribute_name)
result[attribute_name] = kwargs[attribute_name]
+ # _raw_elements is a special kwarg used for unexpected unparseable xml
+ # elements (e.g. for soap:header or when strict is disabled)
+ if '_raw_elements' in available_kwargs and kwargs['_raw_elements']:
+ result['_raw_elements'] = kwargs['_raw_elements']
+ available_kwargs.remove('_raw_elements')
+
if available_kwargs:
raise TypeError((
"%s() got an unexpected keyword argument %r. " +
- "Signature: (%s)"
+ "Signature: `%s`"
) % (
xsd_type.qname or 'ComplexType',
next(iter(available_kwargs)),
- xsd_type.signature()))
+ xsd_type.signature(standalone=False)))
return result
diff --git a/src/zeep/xsd/visitor.py b/src/zeep/xsd/visitor.py
index 8ed0160..9ba0101 100644
--- a/src/zeep/xsd/visitor.py
+++ b/src/zeep/xsd/visitor.py
@@ -4,14 +4,13 @@ import re
from lxml import etree
-from zeep import exceptions
from zeep.exceptions import XMLParseError
-from zeep.parser import absolute_location
+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.utils import load_external
+from zeep.xsd.types.unresolved import UnresolvedCustomType, UnresolvedType
logger = logging.getLogger(__name__)
@@ -37,12 +36,36 @@ class SchemaVisitor(object):
"""Visitor which processes XSD files and registers global elements and
types in the given schema.
+ :param schema:
+ :type schema: zeep.xsd.schema.Schema
+ :param document:
+ :type document: zeep.xsd.schema.SchemaDocument
+
"""
+
def __init__(self, schema, document):
self.document = document
self.schema = schema
self._includes = set()
+ def register_element(self, qname, instance):
+ self.document.register_element(qname, instance)
+
+ def register_attribute(self, name, instance):
+ self.document.register_attribute(name, instance)
+
+ def register_type(self, qname, instance):
+ self.document.register_type(qname, instance)
+
+ def register_group(self, qname, instance):
+ self.document.register_group(qname, instance)
+
+ def register_attribute_group(self, qname, instance):
+ self.document.register_attribute_group(qname, instance)
+
+ def register_import(self, namespace, document):
+ self.document.register_import(namespace, document)
+
def process(self, node, parent):
visit_func = self.visitors.get(node.tag)
if not visit_func:
@@ -79,7 +102,10 @@ class SchemaVisitor(object):
return cls(node.tag, ref, self.schema, **kwargs)
def visit_schema(self, node):
- """
+ """Visit the xsd:schema element and process all the child elements
+
+ Definition::
+
<schema
attributeFormDefault = (qualified | unqualified): unqualified
blockDefault = (#all | List of (extension | restriction | substitution) : ''
@@ -97,6 +123,9 @@ class SchemaVisitor(object):
annotation*)*)
</schema>
+ :param node: The XML node
+ :type node: lxml.etree._Element
+
"""
assert node is not None
@@ -104,12 +133,14 @@ class SchemaVisitor(object):
self.document._element_form = node.get('elementFormDefault', 'unqualified')
self.document._attribute_form = node.get('attributeFormDefault', 'unqualified')
- parent = node
- for node in node.iterchildren():
- self.process(node, parent=parent)
+ for child in node:
+ self.process(child, parent=node)
def visit_import(self, node, parent):
"""
+
+ Definition::
+
<import
id = ID
namespace = anyURI
@@ -117,6 +148,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace}...>
Content: (annotation?)
</import>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
schema_node = None
namespace = node.get('namespace')
@@ -136,7 +173,7 @@ class SchemaVisitor(object):
document = self.schema._get_schema_document(namespace, location)
if document:
logger.debug("Returning existing schema: %r", location)
- self.document.register_import(namespace, document)
+ self.register_import(namespace, document)
return document
# Hardcode the mapping between the xml namespace and the xsd for now.
@@ -153,7 +190,10 @@ class SchemaVisitor(object):
return
# Load the XML
- schema_node = load_external(location, self.schema._transport)
+ schema_node = load_external(
+ location,
+ self.schema._transport,
+ strict=self.schema.strict)
# Check if the xsd:import namespace matches the targetNamespace. If
# the xsd:import statement didn't specify a namespace then make sure
@@ -168,17 +208,26 @@ class SchemaVisitor(object):
sourceline=node.sourceline)
schema = self.schema.create_new_document(schema_node, location)
- self.document.register_import(namespace, schema)
+ self.register_import(namespace, schema)
return schema
def visit_include(self, node, parent):
"""
- <include
- id = ID
- schemaLocation = anyURI
- {any attributes with non-schema Namespace}...>
- Content: (annotation?)
- </include>
+
+ Definition::
+
+ <include
+ id = ID
+ schemaLocation = anyURI
+ {any attributes with non-schema Namespace}...>
+ Content: (annotation?)
+ </include>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
if not node.get('schemaLocation'):
raise NotImplementedError("schemaLocation is required")
@@ -188,13 +237,49 @@ class SchemaVisitor(object):
return
schema_node = load_external(
- location, self.schema._transport, base_url=self.document._base_url)
+ location, self.schema._transport,
+ base_url=self.document._base_url,
+ strict=self.schema.strict)
self._includes.add(location)
- return self.visit_schema(schema_node)
+ # When the included document has no default namespace defined but the
+ # parent document does have this then we should (atleast for #360)
+ # transfer the default namespace to the included schema. We can't
+ # update the nsmap of elements in lxml so we create a new schema with
+ # the correct nsmap and move all the content there.
+ if not schema_node.nsmap.get(None) and node.nsmap.get(None):
+ nsmap = {None: node.nsmap[None]}
+ nsmap.update(schema_node.nsmap)
+ new = etree.Element(schema_node.tag, nsmap=nsmap)
+ for child in schema_node:
+ new.append(child)
+ for key, value in schema_node.attrib.items():
+ new.set(key, value)
+ schema_node = new
+
+ # Use the element/attribute form defaults from the schema while
+ # processing the nodes.
+ element_form_default = self.document._element_form
+ attribute_form_default = self.document._attribute_form
+ base_url = self.document._base_url
+
+ self.document._element_form = schema_node.get('elementFormDefault', 'unqualified')
+ self.document._attribute_form = schema_node.get('attributeFormDefault', 'unqualified')
+ self.document._base_url = absolute_location(location, self.document._base_url)
+
+ # Iterate directly over the children.
+ for child in schema_node:
+ self.process(child, parent=schema_node)
+
+ self.document._element_form = element_form_default
+ self.document._attribute_form = attribute_form_default
+ self.document._base_url = base_url
def visit_element(self, node, parent):
"""
+
+ Definition::
+
<element
abstract = Boolean : false
block = (#all | List of (extension | restriction | substitution))
@@ -214,6 +299,12 @@ class SchemaVisitor(object):
Content: (annotation?, (
(simpleType | complexType)?, (unique | key | keyref)*))
</element>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
is_global = parent.tag == tags.schema
@@ -272,16 +363,16 @@ class SchemaVisitor(object):
min_occurs=min_occurs, max_occurs=max_occurs, nillable=nillable,
default=default, is_global=is_global)
- self.document._elm_instances.append(element)
-
# Only register global elements
if is_global:
- self.document.register_element(qname, element)
+ self.register_element(qname, element)
return element
def visit_attribute(self, node, parent):
"""Declares an attribute.
+ Definition::
+
<attribute
default = string
fixed = string
@@ -294,6 +385,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace...}>
Content: (annotation?, (simpleType?))
</attribute>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
is_global = parent.tag == tags.schema
@@ -303,9 +400,8 @@ class SchemaVisitor(object):
match = re.match('([^\[]+)', array_type)
if match:
array_type = match.groups()[0]
- qname = as_qname(
- array_type, node.nsmap, self.document._target_namespace)
- array_type = xsd_types.UnresolvedType(qname, self.schema)
+ qname = as_qname(array_type, node.nsmap)
+ array_type = UnresolvedType(qname, self.schema)
# If the elment has a ref attribute then all other attributes cannot
# be present. Short circuit that here.
@@ -316,9 +412,8 @@ class SchemaVisitor(object):
return result
attribute_form = node.get('form', self.document._attribute_form)
- qname = qname_attr(node, 'name', self.document._target_namespace)
if attribute_form == 'qualified' or is_global:
- name = qname
+ name = qname_attr(node, 'name', self.document._target_namespace)
else:
name = etree.QName(node.get('name'))
@@ -338,15 +433,16 @@ class SchemaVisitor(object):
attr = xsd_elements.Attribute(
name, type_=xsd_type, default=default, required=required)
- self.document._elm_instances.append(attr)
# Only register global elements
if is_global:
- self.document.register_attribute(qname, attr)
+ self.register_attribute(name, attr)
return attr
def visit_simple_type(self, node, parent):
"""
+ Definition::
+
<simpleType
final = (#all | (list | union | restriction))
id = ID
@@ -354,6 +450,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace}...>
Content: (annotation?, (restriction | list | union))
</simpleType>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
if parent.tag == tags.schema:
@@ -369,8 +471,7 @@ class SchemaVisitor(object):
child = items[0]
if child.tag == tags.restriction:
base_type = self.visit_restriction_simple_type(child, node)
- xsd_type = xsd_types.UnresolvedCustomType(
- qname, base_type, self.schema)
+ xsd_type = UnresolvedCustomType(qname, base_type, self.schema)
elif child.tag == tags.list:
xsd_type = self.visit_list(child, node)
@@ -382,23 +483,30 @@ class SchemaVisitor(object):
assert xsd_type is not None
if is_global:
- self.document.register_type(qname, xsd_type)
+ self.register_type(qname, xsd_type)
return xsd_type
def visit_complex_type(self, node, parent):
"""
- <complexType
- abstract = Boolean : false
- block = (#all | List of (extension | restriction))
- final = (#all | List of (extension | restriction))
- id = ID
- mixed = Boolean : false
- name = NCName
- {any attributes with non-schema Namespace...}>
- Content: (annotation?, (simpleContent | complexContent |
- ((group | all | choice | sequence)?,
- ((attribute | attributeGroup)*, anyAttribute?))))
- </complexType>
+ Definition::
+
+ <complexType
+ abstract = Boolean : false
+ block = (#all | List of (extension | restriction))
+ final = (#all | List of (extension | restriction))
+ id = ID
+ mixed = Boolean : false
+ name = NCName
+ {any attributes with non-schema Namespace...}>
+ Content: (annotation?, (simpleContent | complexContent |
+ ((group | all | choice | sequence)?,
+ ((attribute | attributeGroup)*, anyAttribute?))))
+ </complexType>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
"""
children = []
@@ -448,22 +556,30 @@ class SchemaVisitor(object):
element=element, attributes=attributes, qname=qname,
is_global=is_global)
else:
- xsd_type = xsd_cls(qname=qname)
+ xsd_type = xsd_cls(qname=qname, is_global=is_global)
if is_global:
- self.document.register_type(qname, xsd_type)
+ self.register_type(qname, xsd_type)
return xsd_type
- def visit_complex_content(self, node, parent, namespace=None):
+ def visit_complex_content(self, node, parent):
"""The complexContent element defines extensions or restrictions on a
complex type that contains mixed content or elements only.
+ Definition::
+
<complexContent
id = ID
mixed = Boolean
{any attributes with non-schema Namespace}...>
Content: (annotation?, (restriction | extension))
</complexContent>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
child = node.getchildren()[-1]
@@ -485,16 +601,24 @@ class SchemaVisitor(object):
'extension': base,
}
- def visit_simple_content(self, node, parent, namespace=None):
+ def visit_simple_content(self, node, parent):
"""Contains extensions or restrictions on a complexType element with
character data or a simpleType element as content and contains no
elements.
+ Definition::
+
<simpleContent
id = ID
{any attributes with non-schema Namespace}...>
Content: (annotation?, (restriction | extension))
</simpleContent>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
child = node.getchildren()[-1]
@@ -505,8 +629,10 @@ class SchemaVisitor(object):
return self.visit_extension_simple_content(child, node)
raise AssertionError("Expected restriction or extension")
- def visit_restriction_simple_type(self, node, parent, namespace=None):
+ def visit_restriction_simple_type(self, node, parent):
"""
+ Definition::
+
<restriction
base = QName
id = ID
@@ -517,6 +643,12 @@ class SchemaVisitor(object):
totalDigits |fractionDigits | length | minLength |
maxLength | enumeration | whiteSpace | pattern)*))
</restriction>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
base_name = qname_attr(node, 'base')
if base_name:
@@ -526,8 +658,10 @@ class SchemaVisitor(object):
if children[0].tag == tags.simpleType:
return self.visit_simple_type(children[0], node)
- def visit_restriction_simple_content(self, node, parent, namespace=None):
+ def visit_restriction_simple_content(self, node, parent):
"""
+ Definition::
+
<restriction
base = QName
id = ID
@@ -539,14 +673,22 @@ class SchemaVisitor(object):
maxLength | enumeration | whiteSpace | pattern)*
)?, ((attribute | attributeGroup)*, anyAttribute?))
</restriction>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
base_name = qname_attr(node, 'base')
base_type = self._get_type(base_name)
return base_type, []
- def visit_restriction_complex_content(self, node, parent, namespace=None):
+ def visit_restriction_complex_content(self, node, parent):
"""
+ Definition::
+
<restriction
base = QName
id = ID
@@ -554,6 +696,12 @@ class SchemaVisitor(object):
Content: (annotation?, (group | all | choice | sequence)?,
((attribute | attributeGroup)*, anyAttribute?))
</restriction>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
base_name = qname_attr(node, 'base')
base_type = self._get_type(base_name)
@@ -572,6 +720,9 @@ class SchemaVisitor(object):
def visit_extension_complex_content(self, node, parent):
"""
+
+ Definition::
+
<extension
base = QName
id = ID
@@ -580,6 +731,12 @@ class SchemaVisitor(object):
(group | all | choice | sequence)?,
((attribute | attributeGroup)*, anyAttribute?)))
</extension>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
base_name = qname_attr(node, 'base')
base_type = self._get_type(base_name)
@@ -599,6 +756,9 @@ class SchemaVisitor(object):
def visit_extension_simple_content(self, node, parent):
"""
+
+ Definition::
+
<extension
base = QName
id = ID
@@ -616,16 +776,27 @@ class SchemaVisitor(object):
def visit_annotation(self, node, parent):
"""Defines an annotation.
+ Definition::
+
<annotation
id = ID
{any attributes with non-schema Namespace}...>
Content: (appinfo | documentation)*
</annotation>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
return
def visit_any(self, node, parent):
"""
+
+ Definition::
+
<any
id = ID
maxOccurs = (nonNegativeInteger | unbounded) : 1
@@ -636,6 +807,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace...}>
Content: (annotation?)
</any>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
min_occurs, max_occurs = _process_occurs_attrs(node)
process_contents = node.get('processContents', 'strict')
@@ -645,6 +822,8 @@ class SchemaVisitor(object):
def visit_sequence(self, node, parent):
"""
+ Definition::
+
<sequence
id = ID
maxOccurs = (nonNegativeInteger | unbounded) : 1
@@ -653,6 +832,12 @@ class SchemaVisitor(object):
Content: (annotation?,
(element | group | choice | sequence | any)*)
</sequence>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
sub_types = [
@@ -677,6 +862,8 @@ class SchemaVisitor(object):
"""Allows the elements in the group to appear (or not appear) in any
order in the containing element.
+ Definition::
+
<all
id = ID
maxOccurs= 1: 1
@@ -684,6 +871,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace...}>
Content: (annotation?, element*)
</all>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
sub_types = [
@@ -703,6 +896,8 @@ class SchemaVisitor(object):
"""Groups a set of element declarations so that they can be
incorporated as a group into complex type definitions.
+ Definition::
+
<group
name= NCName
id = ID
@@ -714,9 +909,16 @@ class SchemaVisitor(object):
Content: (annotation?, (all | choice | sequence))
</group>
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
+ min_occurs, max_occurs = _process_occurs_attrs(node)
- result = self.process_reference(node)
+ result = self.process_reference(
+ node, min_occurs=min_occurs, max_occurs=max_occurs)
if result:
return result
@@ -730,11 +932,13 @@ class SchemaVisitor(object):
elm = xsd_elements.Group(name=qname, child=item)
if parent.tag == tags.schema:
- self.document.register_group(qname, elm)
+ self.register_group(qname, elm)
return elm
def visit_list(self, node, parent):
"""
+ Definition::
+
<list
id = ID
itemType = QName
@@ -745,6 +949,12 @@ class SchemaVisitor(object):
The use of the simpleType element child and the itemType attribute is
mutually exclusive.
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
+
"""
item_type = qname_attr(node, 'itemType')
if item_type:
@@ -757,6 +967,8 @@ class SchemaVisitor(object):
def visit_choice(self, node, parent):
"""
+ Definition::
+
<choice
id = ID
maxOccurs= (nonNegativeInteger | unbounded) : 1
@@ -780,19 +992,27 @@ class SchemaVisitor(object):
def visit_union(self, node, parent):
"""Defines a collection of multiple simpleType definitions.
+ Definition::
+
<union
id = ID
memberTypes = List of QNames
{any attributes with non-schema Namespace}...>
Content: (annotation?, (simpleType*))
</union>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
# TODO
members = node.get('memberTypes')
types = []
if members:
for member in members.split():
- qname = as_qname(member, node.nsmap, self.document._target_namespace)
+ qname = as_qname(member, node.nsmap)
xsd_type = self._get_type(qname)
types.append(xsd_type)
else:
@@ -805,18 +1025,28 @@ class SchemaVisitor(object):
attribute or element values) must be unique within the specified scope.
The value must be unique or nil.
+ Definition::
+
<unique
id = ID
name = NCName
{any attributes with non-schema Namespace}...>
Content: (annotation?, (selector, field+))
</unique>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
# TODO
pass
def visit_attribute_group(self, node, parent):
"""
+ Definition::
+
<attributeGroup
id = ID
name = NCName
@@ -825,6 +1055,12 @@ class SchemaVisitor(object):
Content: (annotation?),
((attribute | attributeGroup)*, anyAttribute?))
</attributeGroup>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
ref = self.process_reference(node)
if ref:
@@ -835,10 +1071,12 @@ class SchemaVisitor(object):
attributes = self._process_attributes(node, children)
attribute_group = xsd_elements.AttributeGroup(qname, attributes)
- self.document.register_attribute_group(qname, attribute_group)
+ self.register_attribute_group(qname, attribute_group)
def visit_any_attribute(self, node, parent):
"""
+ Definition::
+
<anyAttribute
id = ID
namespace = ((##any | ##other) |
@@ -847,6 +1085,12 @@ class SchemaVisitor(object):
{any attributes with non-schema Namespace...}>
Content: (annotation?)
</anyAttribute>
+
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
process_contents = node.get('processContents', 'strict')
return xsd_elements.AnyAttribute(process_contents=process_contents)
@@ -856,6 +1100,8 @@ class SchemaVisitor(object):
non-XML data within an XML document. An XML Schema notation declaration
is a reconstruction of XML 1.0 NOTATION declarations.
+ Definition::
+
<notation
id = ID
name = NCName
@@ -865,17 +1111,18 @@ class SchemaVisitor(object):
Content: (annotation?)
</notation>
+ :param node: The XML node
+ :type node: lxml.etree._Element
+ :param parent: The parent XML node
+ :type parent: lxml.etree._Element
+
"""
pass
def _get_type(self, name):
assert name is not None
name = self._create_qname(name)
- try:
- retval = self.schema.get_type(name)
- except (exceptions.NamespaceError, exceptions.LookupError):
- retval = xsd_types.UnresolvedType(name, self.schema)
- return retval
+ return UnresolvedType(name, self.schema)
def _create_qname(self, name):
if not isinstance(name, etree.QName):
diff --git a/tests/test_asyncio_transport.py b/tests/test_asyncio_transport.py
index 2c65a45..7aca012 100644
--- a/tests/test_asyncio_transport.py
+++ b/tests/test_asyncio_transport.py
@@ -1,6 +1,7 @@
import pytest
from pretend import stub
from lxml import etree
+import aiohttp
from aioresponses import aioresponses
from zeep import cache, asyncio
@@ -39,3 +40,21 @@ async def test_post(event_loop):
headers={})
assert result.content == b'x'
+
+
+ at pytest.mark.requests
+ at pytest.mark.asyncio
+async def test_session_close(event_loop):
+ transport = asyncio.AsyncTransport(loop=event_loop)
+ session = transport.session # copy session object from transport
+ del transport
+ assert session.closed
+
+
+ at pytest.mark.requests
+ at pytest.mark.asyncio
+async def test_session_no_close(event_loop):
+ session = aiohttp.ClientSession(loop=event_loop)
+ transport = asyncio.AsyncTransport(loop=event_loop, session=session)
+ del transport
+ assert not session.closed
diff --git a/tests/test_client_factory.py b/tests/test_client_factory.py
index 92d5b1a..632ae78 100644
--- a/tests/test_client_factory.py
+++ b/tests/test_client_factory.py
@@ -11,6 +11,18 @@ def test_factory_namespace():
assert obj.NameLast == 'van Tellingen'
+def test_factory_no_reference():
+ client = Client('tests/wsdl_files/soap.wsdl')
+ factory = client.type_factory('http://example.com/stockquote.xsd')
+ obj_1 = client.get_type('ns0:ArrayOfAddress')()
+ obj_1.Address.append({
+ 'NameFirst': 'J',
+ 'NameLast': 'Doe',
+ })
+ obj_2 = client.get_type('ns0:ArrayOfAddress')()
+ assert len(obj_2.Address) == 0
+
+
def test_factory_ns_auto_prefix():
client = Client('tests/wsdl_files/soap.wsdl')
factory = client.type_factory('ns0')
diff --git a/tests/test_loader.py b/tests/test_loader.py
new file mode 100644
index 0000000..83ce9a7
--- /dev/null
+++ b/tests/test_loader.py
@@ -0,0 +1,14 @@
+from zeep.loader import parse_xml
+from tests.utils import DummyTransport
+
+def test_huge_text():
+ # libxml2>=2.7.3 has XML_MAX_TEXT_LENGTH 10000000 without XML_PARSE_HUGE
+ tree = parse_xml(u"""
+ <s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
+ <s:Body>
+ <HugeText xmlns="http://hugetext">%s</HugeText>
+ </s:Body>
+ </s:Envelope>
+ """ % (u'\u00e5' * 10000001), DummyTransport(), xml_huge_tree=True)
+
+ assert tree[0][0].text == u'\u00e5' * 10000001
diff --git a/tests/test_soap_multiref.py b/tests/test_soap_multiref.py
new file mode 100644
index 0000000..ae9ab7e
--- /dev/null
+++ b/tests/test_soap_multiref.py
@@ -0,0 +1,134 @@
+import io
+
+import pytest
+import requests_mock
+from lxml import etree
+from pretend import stub
+from six import StringIO
+
+from tests.utils import DummyTransport, assert_nodes_equal
+from zeep import Client, wsdl
+from zeep.transports import Transport
+
+
+ at pytest.mark.requests
+def test_parse_soap_wsdl():
+ 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: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: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/">
+ <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>
+ </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_wsdl.py b/tests/test_wsdl.py
index f42ba4c..2c34ac3 100644
--- a/tests/test_wsdl.py
+++ b/tests/test_wsdl.py
@@ -100,7 +100,8 @@ def test_parse_soap_header_wsdl():
}
})
- assert result == 120.123
+ assert result.body.price == 120.123
+ assert result.header.body is None
request = m.request_history[0]
diff --git a/tests/test_wsdl_arrays.py b/tests/test_wsdl_arrays.py
index 21b96d1..f64b7d2 100644
--- a/tests/test_wsdl_arrays.py
+++ b/tests/test_wsdl_arrays.py
@@ -1,12 +1,14 @@
import io
+import pytest
from lxml import etree
-from tests.utils import DummyTransport, assert_nodes_equal, load_xml
+from tests.utils import DummyTransport, assert_nodes_equal, load_xml, render_node
from zeep import xsd
-def get_transport():
+ at pytest.fixture(scope='function')
+def transport():
transport = DummyTransport()
transport.bind(
'http://schemas.xmlsoap.org/soap/encoding/',
@@ -14,7 +16,7 @@ def get_transport():
return transport
-def test_simple_type():
+def test_simple_type(transport):
schema = xsd.Schema(load_xml("""
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -30,7 +32,7 @@ def test_simple_type():
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
- """), transport=get_transport())
+ """), transport=transport)
ArrayOfString = schema.get_type('ns0:ArrayOfString')
print(ArrayOfString.__dict__)
@@ -52,8 +54,105 @@ def test_simple_type():
assert_nodes_equal(expected, node)
+ data = ArrayOfString.parse_xmlelement(node, schema)
+ assert data == ['item', 'and', 'even', 'more', 'items']
+ assert data.as_value_object()
-def test_complex_type():
+
+def test_simple_type_nested(transport):
+ schema = xsd.Schema(load_xml("""
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+ targetNamespace="http://tests.python-zeep.org/tns"
+ xmlns:tns="http://tests.python-zeep.org/tns">
+ <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+ <xsd:complexType name="container">
+ <xsd:sequence>
+ <xsd:element name="strings" type="tns:ArrayOfString"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ArrayOfString">
+ <xsd:complexContent>
+ <xsd:restriction base="SOAP-ENC:Array">
+ <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[]"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:schema>
+ """), transport=transport)
+
+ Container = schema.get_type('ns0:container')
+ value = Container(strings=['item', 'and', 'even', 'more', 'items'])
+
+ assert value.strings == ['item', 'and', 'even', 'more', 'items']
+
+ node = etree.Element('document')
+ Container.render(node, value)
+
+ expected = """
+ <document>
+ <strings>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">item</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">and</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">even</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">more</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">items</item>
+ </strings>
+ </document>
+ """ # noqa
+
+ assert_nodes_equal(expected, node)
+
+ data = Container.parse_xmlelement(node, schema)
+ assert data.strings == ['item', 'and', 'even', 'more', 'items']
+
+
+def test_simple_type_nested_inline_type(transport):
+ schema = xsd.Schema(load_xml("""
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/"
+ targetNamespace="http://tests.python-zeep.org/tns"
+ xmlns:tns="http://tests.python-zeep.org/tns">
+ <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+ <xsd:complexType name="container">
+ <xsd:sequence>
+ <xsd:element name="strings" type="tns:ArrayOfString"/>
+ </xsd:sequence>
+ </xsd:complexType>
+
+ <xsd:complexType name="ArrayOfString">
+ <xsd:complexContent>
+ <xsd:restriction base="SOAP-ENC:Array">
+ <xsd:attribute ref="SOAP-ENC:arrayType" wsdl:arrayType="xsd:string[]"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:schema>
+ """), transport=transport)
+
+ Container = schema.get_type('ns0:container')
+ node = load_xml("""
+ <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <strings xsi:type="soapenc:Array" xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">item</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">and</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">even</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">more</item>
+ <item xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">items</item>
+ </strings>
+ </document>
+ """) # noqa
+
+ data = Container.parse_xmlelement(node, schema)
+ assert data.strings == ['item', 'and', 'even', 'more', 'items']
+
+
+def test_complex_type(transport):
schema = xsd.Schema(load_xml("""
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -80,7 +179,7 @@ def test_complex_type():
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
- """), transport=get_transport())
+ """), transport=transport)
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
ArrayObject = schema.get_type('ns0:ArrayObject')
@@ -111,9 +210,17 @@ def test_complex_type():
</document>
"""
assert_nodes_equal(expected, node)
+ data = ArrayOfObject.parse_xmlelement(node, schema)
+
+ assert data[0].attr_1 == 'attr-1'
+ assert data[0].attr_2 == 'attr-2'
+ assert data[1].attr_1 == 'attr-3'
+ assert data[1].attr_2 == 'attr-4'
+ assert data[2].attr_1 == 'attr-5'
+ assert data[2].attr_2 == 'attr-6'
-def test_complex_type_without_name():
+def test_complex_type_without_name(transport):
schema = xsd.Schema(load_xml("""
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -137,7 +244,7 @@ def test_complex_type_without_name():
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>
- """), transport=get_transport())
+ """), transport=transport)
ArrayOfObject = schema.get_type('ns0:ArrayOfObject')
ArrayObject = schema.get_type('ns0:ArrayObject')
@@ -170,13 +277,13 @@ def test_complex_type_without_name():
assert_nodes_equal(expected, node)
data = ArrayOfObject.parse_xmlelement(node, schema)
- assert len(data._value_1) == 3
- assert data._value_1[0]['attr_1'] == 'attr-1'
- assert data._value_1[0]['attr_2'] == 'attr-2'
- assert data._value_1[1]['attr_1'] == 'attr-3'
- assert data._value_1[1]['attr_2'] == 'attr-4'
- assert data._value_1[2]['attr_1'] == 'attr-5'
- assert data._value_1[2]['attr_2'] == 'attr-6'
+ assert len(data) == 3
+ assert data[0]['attr_1'] == 'attr-1'
+ assert data[0]['attr_2'] == 'attr-2'
+ assert data[1]['attr_1'] == 'attr-3'
+ assert data[1]['attr_2'] == 'attr-4'
+ assert data[2]['attr_1'] == 'attr-5'
+ assert data[2]['attr_2'] == 'attr-6'
def test_soap_array_parse_remote_ns():
@@ -237,8 +344,8 @@ def test_soap_array_parse_remote_ns():
elm = schema.get_element('ns0:countries')
data = elm.parse(doc, schema)
- assert data._value_1[0].code == 'NL'
- assert data._value_1[0].name == 'The Netherlands'
+ assert data[0].code == 'NL'
+ assert data[0].name == 'The Netherlands'
def test_wsdl_array_type():
@@ -284,9 +391,12 @@ def test_wsdl_array_type():
array = array_elm([item_1, item_2])
node = etree.Element('document')
- assert array_elm.signature() == (
- '_value_1: base[], arrayType: xsd:string, offset: arrayCoordinate, ' +
- 'id: xsd:ID, href: xsd:anyURI, _attr_1: {}')
+ assert array_elm.signature(schema=schema) == 'ns0:array(ns0:array)'
+
+ array_type = schema.get_type('ns0:array')
+ assert array_type.signature(schema=schema) == (
+ 'ns0:array(_value_1: base[], arrayType: xsd:string, ' +
+ 'offset: ns1:arrayCoordinate, id: xsd:ID, href: xsd:anyURI, _attr_1: {})')
array_elm.render(node, array)
expected = """
<document>
@@ -363,7 +473,80 @@ def test_soap_array_parse():
elm = schema.get_element('ns0:FlagDetailsList')
data = elm.parse(doc, schema)
- assert data.FlagDetailsStruct[0].Name == 'flag1'
- assert data.FlagDetailsStruct[0].Value == 'value1'
- assert data.FlagDetailsStruct[1].Name == 'flag2'
- assert data.FlagDetailsStruct[1].Value == 'value2'
+ assert data[0].Name == 'flag1'
+ assert data[0].Value == 'value1'
+ assert data[1].Name == 'flag2'
+ assert data[1].Value == 'value2'
+
+
+def test_xml_soap_enc_string(transport):
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
+ xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <xsd:import namespace="http://schemas.xmlsoap.org/soap/encoding/"/>
+
+ <xsd:element name="value" type="tns:ArrayOfString"/>
+
+ <xsd:complexType name="ArrayOfString">
+ <xsd:complexContent>
+ <xsd:restriction base="soapenc:Array">
+ <xsd:attribute ref="soapenc:arrayType" wsdl:arrayType="soapenc:string[]"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+
+ </xsd:schema>
+ """), transport)
+ shoe_type = schema.get_element('{http://tests.python-zeep.org/}value')
+
+ obj = shoe_type(["foo"])
+ node = render_node(shoe_type, obj)
+ expected = """
+ <document>
+ <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+ <string>foo</string>
+ </ns0:value>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+ obj = shoe_type.parse(node[0], schema)
+ assert obj[0]['_value_1'] == "foo"
+
+ # Via string-types
+ string_type = schema.get_type('{http://schemas.xmlsoap.org/soap/encoding/}string')
+ obj = shoe_type([string_type('foo')])
+ node = render_node(shoe_type, obj)
+ expected = """
+ <document>
+ <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+ <string>foo</string>
+ </ns0:value>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+ obj = shoe_type.parse(node[0], schema)
+ assert obj[0]['_value_1'] == "foo"
+
+ # Via dicts
+ string_type = schema.get_type('{http://schemas.xmlsoap.org/soap/encoding/}string')
+ obj = shoe_type([{'_value_1': 'foo'}])
+ node = render_node(shoe_type, obj)
+ expected = """
+ <document>
+ <ns0:value xmlns:ns0="http://tests.python-zeep.org/">
+ <string>foo</string>
+ </ns0:value>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+ obj = shoe_type.parse(node[0], schema)
+ assert obj[0]['_value_1'] == "foo"
diff --git a/tests/test_wsdl_messages_document.py b/tests/test_wsdl_messages_document.py
index c2a2389..151aef3 100644
--- a/tests/test_wsdl_messages_document.py
+++ b/tests/test_wsdl_messages_document.py
@@ -54,14 +54,14 @@ def test_parse():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'xsd:string'
- assert operation.input.header.signature() == ''
- assert operation.input.envelope.signature() == 'body: xsd:string'
+ assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+ assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
+ assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(body: xsd:string)'
assert operation.input.signature(as_output=False) == 'xsd:string'
- assert operation.output.body.signature() == 'xsd:string'
- assert operation.output.header.signature() == ''
- assert operation.output.envelope.signature() == 'body: xsd:string'
+ assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+ assert operation.output.header.signature(schema=root.types) == 'soap-env:Header()'
+ assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(body: xsd:string)'
assert operation.output.signature(as_output=True) == 'xsd:string'
@@ -111,9 +111,9 @@ def test_empty_input_parse():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == ''
- assert operation.input.header.signature() == ''
- assert operation.input.envelope.signature() == 'body: {}'
+ assert operation.input.body.signature(schema=root.types) == 'soap-env:Body()'
+ assert operation.input.header.signature(schema=root.types) == 'soap-env:Header()'
+ assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(body: {})'
assert operation.input.signature(as_output=False) == ''
@@ -171,15 +171,15 @@ def test_parse_with_header():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'xsd:string'
- assert operation.input.header.signature() == 'auth: RequestHeader()'
- assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeader()}' # noqa
- assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: RequestHeader()}' # noqa
+ assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+ assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
+ assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: xsd:string}, body: xsd:string)' # noqa
+ assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: xsd:string}' # noqa
- assert operation.output.body.signature() == 'xsd:string'
- assert operation.output.header.signature() == 'auth: ResponseHeader()'
- assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeader()}' # noqa
- assert operation.output.signature(as_output=True) == 'body: xsd:string, header: {auth: ResponseHeader()}' # noqa
+ assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+ assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: xsd:string)'
+ assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: xsd:string}, body: xsd:string)' # noqa
+ assert operation.output.signature(as_output=True) == 'header: {auth: xsd:string}, body: xsd:string' # noqa
def test_parse_with_header_type():
@@ -240,15 +240,15 @@ def test_parse_with_header_type():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'xsd:string'
- assert operation.input.header.signature() == 'auth: RequestHeaderType'
- assert operation.input.envelope.signature() == 'body: xsd:string, header: {auth: RequestHeaderType}' # noqa
- assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: RequestHeaderType}' # noqa
+ assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
+ assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:RequestHeaderType)'
+ assert operation.input.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: ns0:RequestHeaderType}, body: xsd:string)' # noqa
+ assert operation.input.signature(as_output=False) == 'xsd:string, _soapheaders={auth: ns0:RequestHeaderType}' # noqa
- assert operation.output.body.signature() == 'xsd:string'
- assert operation.output.header.signature() == 'auth: ResponseHeaderType'
- assert operation.output.envelope.signature() == 'body: xsd:string, header: {auth: ResponseHeaderType}' # noqa
- assert operation.output.signature(as_output=True) == 'body: xsd:string, header: {auth: ResponseHeaderType}' # noqa
+ assert operation.output.body.signature(schema=root.types) == 'ns0:Response(xsd:string)'
+ assert operation.output.header.signature(schema=root.types) == 'soap-env:Header(auth: ns0:ResponseHeaderType)'
+ assert operation.output.envelope.signature(schema=root.types) == 'soap-env:envelope(header: {auth: ns0:ResponseHeaderType}, body: xsd:string)' # noqa
+ assert operation.output.signature(as_output=True) == 'header: {auth: ns0:ResponseHeaderType}, body: xsd:string' # noqa
def test_parse_with_header_other_message():
@@ -292,12 +292,13 @@ def test_parse_with_header_other_message():
""".strip())
root = wsdl.Document(wsdl_content, None)
+ root.types.set_ns_prefix('soap-env', 'http://schemas.xmlsoap.org/soap/envelope/')
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.header.signature() == 'header: RequestHeader()'
- assert operation.input.body.signature() == 'xsd:string'
+ assert operation.input.header.signature(schema=root.types) == 'soap-env:Header(header: xsd:string)'
+ assert operation.input.body.signature(schema=root.types) == 'ns0:Request(xsd:string)'
header = root.types.get_element(
'{http://tests.python-zeep.org/tns}RequestHeader'
@@ -1193,7 +1194,7 @@ def test_deserialize_with_headers():
serialized = operation.process_reply(response_body)
assert operation.output.signature(as_output=True) == (
- 'body: {request_1: Request1(), request_2: Request2()}, header: {header_1: Header1(), header_2: Header2()}') # noqa
+ 'header: {header_1: ns0:Header1, header_2: xsd:string}, body: {request_1: ns0:Request1, request_2: ns0:Request2}')
assert serialized.body.request_1.arg1 == 'ah1'
assert serialized.body.request_2.arg2 == 'ah2'
assert serialized.header.header_1.username == 'mvantellingen'
diff --git a/tests/test_wsdl_messages_http.py b/tests/test_wsdl_messages_http.py
index aa40d25..eb48969 100644
--- a/tests/test_wsdl_messages_http.py
+++ b/tests/test_wsdl_messages_http.py
@@ -52,10 +52,10 @@ def test_urlencoded_serialize():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+ assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
- assert operation.output.body.signature() == 'Body: xsd:string'
+ assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
assert operation.output.signature(as_output=True) == 'xsd:string'
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -112,10 +112,10 @@ def test_urlreplacement_serialize():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+ assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
- assert operation.output.body.signature() == 'Body: xsd:string'
+ assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
assert operation.output.signature(as_output=True) == 'xsd:string'
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -172,10 +172,10 @@ def test_mime_content_serialize_form_urlencoded():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+ assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
- assert operation.output.body.signature() == 'Body: xsd:string'
+ assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
assert operation.output.signature(as_output=True) == 'xsd:string'
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
@@ -229,10 +229,10 @@ def test_mime_content_serialize_text_xml():
binding = root.bindings['{http://tests.python-zeep.org/tns}TestBinding']
operation = binding.get('TestOperation')
- assert operation.input.body.signature() == 'arg1: xsd:string, arg2: xsd:string'
+ assert operation.input.body.signature(schema=root.types) == 'TestOperation(arg1: xsd:string, arg2: xsd:string)'
assert operation.input.signature(as_output=False) == 'arg1: xsd:string, arg2: xsd:string'
- assert operation.output.body.signature() == 'Body: xsd:string'
+ assert operation.output.body.signature(schema=root.types) == 'TestOperation(Body: xsd:string)'
assert operation.output.signature(as_output=True) == 'xsd:string'
serialized = operation.input.serialize(arg1='ah1', arg2='ah2')
diff --git a/tests/test_wsdl_soap.py b/tests/test_wsdl_soap.py
index 960645f..e80c152 100644
--- a/tests/test_wsdl_soap.py
+++ b/tests/test_wsdl_soap.py
@@ -1,12 +1,36 @@
+# -*- coding: utf-8 -*-
+
+import pytest
+
from lxml import etree
from pretend import stub
from tests.utils import load_xml
from zeep import Client
from zeep.exceptions import Fault
+from zeep.exceptions import TransportError
from zeep.wsdl import bindings
+def test_soap11_no_output():
+ client = Client('tests/wsdl_files/soap.wsdl')
+ content = """
+ <soapenv:Envelope
+ xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:stoc="http://example.com/stockquote.xsd">
+ <soapenv:Body></soapenv:Body>
+ </soapenv:Envelope>
+ """.strip()
+ response = stub(
+ status_code=200,
+ headers={},
+ content=content)
+
+ operation = client.service._binding._operations['GetLastTradePriceNoOutput']
+ res = client.service._binding.process_reply(client, operation, response)
+ assert res is None
+
+
def test_soap11_process_error():
response = load_xml("""
<soapenv:Envelope
@@ -26,10 +50,10 @@ def test_soap11_process_error():
</soapenv:Body>
</soapenv:Envelope>
""")
+
binding = bindings.Soap11Binding(
wsdl=None, name=None, port_name=None, transport=None,
default_style=None)
-
try:
binding.process_error(response, None)
assert False
@@ -112,6 +136,79 @@ def test_soap12_process_error():
assert exc.subcodes[1].localname == 'fault-subcode2'
+def test_no_content_type():
+ data = """
+ <?xml version="1.0"?>
+ <soapenv:Envelope
+ xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:stoc="http://example.com/stockquote.xsd">
+ <soapenv:Header/>
+ <soapenv:Body>
+ <stoc:TradePrice>
+ <price>120.123</price>
+ </stoc:TradePrice>
+ </soapenv:Body>
+ </soapenv:Envelope>
+ """.strip()
+
+ client = Client('tests/wsdl_files/soap.wsdl')
+ binding = client.service._binding
+
+ response = stub(
+ status_code=200,
+ content=data,
+ encoding='utf-8',
+ headers={}
+ )
+
+ result = binding.process_reply(
+ client, binding.get('GetLastTradePrice'), response)
+
+ assert result == 120.123
+
+
+def test_wrong_content():
+ data = """
+ The request is answered something unexpected,
+ like an html page or a raw internal stack trace
+ """.strip()
+
+ client = Client('tests/wsdl_files/soap.wsdl')
+ binding = client.service._binding
+
+ response = stub(
+ status_code=200,
+ content=data,
+ encoding='utf-8',
+ headers={}
+ )
+
+ with pytest.raises(TransportError):
+ binding.process_reply(
+ client, binding.get('GetLastTradePrice'), response)
+
+
+def test_wrong_no_unicode_content():
+ data = """
+ The request is answered something unexpected,
+ and the content charset is beyond unicode òñÇÿ
+ """.strip()
+
+ client = Client('tests/wsdl_files/soap.wsdl')
+ binding = client.service._binding
+
+ response = stub(
+ status_code=200,
+ content=data,
+ encoding='utf-8',
+ headers={}
+ )
+
+ with pytest.raises(TransportError):
+ binding.process_reply(
+ client, binding.get('GetLastTradePrice'), response)
+
+
def test_mime_multipart():
data = '\r\n'.join(line.strip() for line in """
--MIME_boundary
@@ -154,6 +251,64 @@ def test_mime_multipart():
response = stub(
status_code=200,
content=data,
+ encoding='utf-8',
+ headers={
+ 'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml at claiming-it.com>"; boundary="MIME_boundary"'
+ }
+ )
+
+ result = binding.process_reply(
+ client, binding.get('GetClaimDetails'), response)
+
+ assert result.root is None
+ assert len(result.attachments) == 2
+
+ assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
+ assert result.attachments[1].content == b'...Raw JPEG image..'
+
+
+def test_mime_multipart_no_encoding():
+ data = '\r\n'.join(line.strip() for line in """
+ --MIME_boundary
+ Content-Type: text/xml
+ Content-Transfer-Encoding: 8bit
+ Content-ID: <claim061400a.xml at claiming-it.com>
+
+ <?xml version='1.0' ?>
+ <SOAP-ENV:Envelope
+ xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
+ <SOAP-ENV:Body>
+ <claim:insurance_claim_auto id="insurance_claim_document_id"
+ xmlns:claim="http://schemas.risky-stuff.com/Auto-Claim">
+ <theSignedForm href="cid:claim061400a.tiff at claiming-it.com"/>
+ <theCrashPhoto href="cid:claim061400a.jpeg at claiming-it.com"/>
+ <!-- ... more claim details go here... -->
+ </claim:insurance_claim_auto>
+ </SOAP-ENV:Body>
+ </SOAP-ENV:Envelope>
+
+ --MIME_boundary
+ Content-Type: image/tiff
+ Content-Transfer-Encoding: base64
+ Content-ID: <claim061400a.tiff at claiming-it.com>
+
+ Li4uQmFzZTY0IGVuY29kZWQgVElGRiBpbWFnZS4uLg==
+
+ --MIME_boundary
+ Content-Type: text/xml
+ Content-ID: <claim061400a.jpeg at claiming-it.com>
+
+ ...Raw JPEG image..
+ --MIME_boundary--
+ """.splitlines()).encode('utf-8')
+
+ client = Client('tests/wsdl_files/claim.wsdl')
+ binding = client.service._binding
+
+ response = stub(
+ status_code=200,
+ content=data,
+ encoding=None,
headers={
'Content-Type': 'multipart/related; type="text/xml"; start="<claim061400a.xml at claiming-it.com>"; boundary="MIME_boundary"'
}
@@ -167,3 +322,38 @@ def test_mime_multipart():
assert result.attachments[0].content == b'...Base64 encoded TIFF image...'
assert result.attachments[1].content == b'...Raw JPEG image..'
+
+
+def test_unexpected_headers():
+ data = """
+ <?xml version="1.0"?>
+ <soapenv:Envelope
+ xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
+ xmlns:stoc="http://example.com/stockquote.xsd">
+ <soapenv:Header>
+ <stoc:IamUnexpected>uhoh</stoc:IamUnexpected>
+ </soapenv:Header>
+ <soapenv:Body>
+ <stoc:TradePrice>
+ <price>120.123</price>
+ </stoc:TradePrice>
+ </soapenv:Body>
+ </soapenv:Envelope>
+ """.strip()
+
+ client = Client('tests/wsdl_files/soap_header.wsdl')
+ binding = client.service._binding
+
+ response = stub(
+ status_code=200,
+ content=data,
+ encoding='utf-8',
+ headers={}
+ )
+
+ result = binding.process_reply(
+ client, binding.get('GetLastTradePrice'), response)
+
+ assert result.body.price == 120.123
+ assert result.header.body is None
+ assert len(result.header._raw_elements) == 1
diff --git a/tests/test_xsd.py b/tests/test_xsd.py
index 573784a..ebc99c6 100644
--- a/tests/test_xsd.py
+++ b/tests/test_xsd.py
@@ -149,39 +149,6 @@ def test_invalid_kwarg_simple_type():
elm(something='is-wrong')
-def test_group_mixed():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'username'),
- xsd.String()),
- xsd.Group(
- etree.QName('http://tests.python-zeep.org/', 'groupie'),
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'password'),
- xsd.String(),
- )
- ])
- )
- ])
- ))
- assert custom_type.signature()
- obj = custom_type(username='foo', password='bar')
-
- expected = """
- <document>
- <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:username>foo</ns0:username>
- <ns0:password>bar</ns0:password>
- </ns0:authentication>
- </document>
- """
- node = etree.Element('document')
- custom_type.render(node, obj)
- assert_nodes_equal(expected, node)
def test_any():
@@ -515,7 +482,7 @@ def test_duplicate_element_names():
))
# sequences
- expected = 'item: xsd:string, item__1: xsd:string, item__2: xsd:string'
+ expected = '{http://tests.python-zeep.org/}container(item: xsd:string, item__1: xsd:string, item__2: xsd:string)'
assert custom_type.signature() == expected
obj = custom_type(item='foo', item__1='bar', item__2='lala')
@@ -548,7 +515,7 @@ def test_element_attribute_name_conflict():
))
# sequences
- expected = 'item: xsd:string, foo: xsd:string, attr__item: xsd:string'
+ expected = '{http://tests.python-zeep.org/}container(item: xsd:string, foo: xsd:string, attr__item: xsd:string)'
assert custom_type.signature() == expected
obj = custom_type(item='foo', foo='x', attr__item='bar')
diff --git a/tests/test_xsd_any.py b/tests/test_xsd_any.py
index 1595b2c..3d62b69 100644
--- a/tests/test_xsd_any.py
+++ b/tests/test_xsd_any.py
@@ -3,7 +3,7 @@ import datetime
import pytest
from lxml import etree
-from tests.utils import assert_nodes_equal, load_xml
+from tests.utils import assert_nodes_equal, load_xml, render_node
from zeep import xsd
@@ -26,6 +26,24 @@ def get_any_schema():
"""))
+
+def test_default_xsd_type():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="container"/>
+ </schema>
+ """))
+ assert schema
+
+ container_cls = schema.get_element('ns0:container')
+ data = container_cls()
+ assert data == ''
+
+
def test_any_simple():
schema = get_any_schema()
@@ -109,6 +127,41 @@ def test_any_value_invalid():
container_elm.render(node, obj)
+def test_any_without_element():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="item">
+ <complexType>
+ <sequence>
+ <any minOccurs="0" maxOccurs="1"/>
+ </sequence>
+ <attribute name="type" type="string"/>
+ <attribute name="title" type="string"/>
+ </complexType>
+ </element>
+ </schema>
+ """))
+ item_elm = schema.get_element('{http://tests.python-zeep.org/}item')
+ item = item_elm(xsd.AnyObject(xsd.String(), 'foobar'), type='attr-1', title='attr-2')
+
+ node = render_node(item_elm, item)
+ expected = """
+ <document>
+ <ns0:item xmlns:ns0="http://tests.python-zeep.org/" type="attr-1" title="attr-2">foobar</ns0:item>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+ item = item_elm.parse(node.getchildren()[0], schema)
+ assert item.type == 'attr-1'
+ assert item.title == 'attr-2'
+ assert item._value_1 is None
+
+
def test_any_with_ref():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
@@ -219,6 +272,36 @@ def test_element_any_type():
item = container_elm.parse(node.getchildren()[0], schema)
assert item.something == 'bar'
+def test_element_any_type_unknown_type():
+ node = etree.fromstring("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <element name="something" />
+ </sequence>
+ </complexType>
+ </element>
+ </schema>
+ """.strip())
+ schema = xsd.Schema(node)
+
+ container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
+
+ node = load_xml("""
+ <document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:something xmlns:q1="http://microsoft.com/wsdl/types/" xsi:type="q1:guid">bar</ns0:something>
+ </ns0:container>
+ </document>
+ """)
+ item = container_elm.parse(node.getchildren()[0], schema)
+ assert item.something == 'bar'
+
def test_element_any_type_elements():
node = etree.fromstring("""
@@ -298,8 +381,8 @@ def test_any_in_nested_sequence():
""")) # noqa
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
- assert container_elm.signature() == (
- 'items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[]')
+ assert container_elm.signature(schema) == (
+ 'ns0:container(items: {_value_1: ANY}, version: xsd:string, _value_1: ANY[])')
something = schema.get_element('{http://tests.python-zeep.org/}something')
foobar = schema.get_element('{http://tests.python-zeep.org/}foobar')
diff --git a/tests/test_xsd_attributes.py b/tests/test_xsd_attributes.py
index f30d76e..535c94a 100644
--- a/tests/test_xsd_attributes.py
+++ b/tests/test_xsd_attributes.py
@@ -26,8 +26,8 @@ def test_anyattribute():
"""))
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
- assert container_elm.signature() == (
- 'foo: xsd:string, _attr_1: {}')
+ assert container_elm.signature(schema) == (
+ 'ns0:container(foo: xsd:string, _attr_1: {})')
obj = container_elm(foo='bar', _attr_1=OrderedDict([
('hiep', 'hoi'), ('hoi', 'hiep')
]))
@@ -73,7 +73,8 @@ def test_attribute_list_type():
"""))
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
- assert container_elm.signature() == ('foo: xsd:string, lijst: xsd:int[]')
+ assert container_elm.signature(schema) == (
+ 'ns0:container(foo: xsd:string, lijst: xsd:int[])')
obj = container_elm(foo='bar', lijst=[1, 2, 3])
expected = """
<document>
@@ -341,7 +342,8 @@ def test_nested_attribute():
"""))
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
- assert container_elm.signature() == 'item: {x: xsd:string, y: xsd:string}'
+ assert container_elm.signature(schema) == (
+ 'ns0:container(item: {x: xsd:string, y: xsd:string})')
obj = container_elm(item={'x': 'foo', 'y': 'bar'})
expected = """
@@ -371,7 +373,7 @@ def test_attribute_union_type():
<restriction base="xsd:string"/>
</simpleType>
<simpleType name="parent">
- <union memberTypes="one two"/>
+ <union memberTypes="tns:one tns:two"/>
</simpleType>
<simpleType name="two">
<restriction base="xsd:string"/>
diff --git a/tests/test_xsd_builtins.py b/tests/test_xsd_builtins.py
index abbab75..17ae68e 100644
--- a/tests/test_xsd_builtins.py
+++ b/tests/test_xsd_builtins.py
@@ -30,6 +30,8 @@ class TestBoolean:
assert instance.xmlvalue(False) == 'false'
assert instance.xmlvalue(1) == 'true'
assert instance.xmlvalue(0) == 'false'
+ assert instance.xmlvalue('false') == 'false'
+ assert instance.xmlvalue('0') == 'false'
def test_pythonvalue(self):
instance = builtins.Boolean()
diff --git a/tests/test_xsd_complex_types.py b/tests/test_xsd_complex_types.py
index c22f185..a6d862e 100644
--- a/tests/test_xsd_complex_types.py
+++ b/tests/test_xsd_complex_types.py
@@ -1,11 +1,11 @@
-import pytest
from lxml import etree
+import pytest
from tests.utils import assert_nodes_equal, load_xml, render_node
from zeep import xsd
-def test_single_node():
+def test_xml_xml_single_node():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -40,7 +40,7 @@ def test_single_node():
assert obj.item == 'bar'
-def test_nested_sequence():
+def test_xml_nested_sequence():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -86,7 +86,37 @@ def test_nested_sequence():
assert obj.item.y == 2
-def test_single_node_array():
+def test_xml_restriction_self():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="container" type="tns:container"/>
+ <complexType name="container">
+ <complexContent>
+ <restriction base="anyType">
+ <sequence>
+ <element minOccurs="0" maxOccurs="1" name="item">
+ <complexType>
+ <sequence>
+ <element name="child" type="tns:container"/>
+ </sequence>
+ </complexType>
+ </element>
+ </sequence>
+ </restriction>
+ </complexContent>
+ </complexType>
+ </schema>
+ """))
+ schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+ container_elm = schema.get_element('tns:container')
+ container_elm.signature(schema)
+
+
+def test_xml_single_node_array():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -124,7 +154,7 @@ def test_single_node_array():
assert obj.item == ['item-1', 'item-2', 'item-3']
-def test_single_node_no_iterable():
+def test_xml_single_node_no_iterable():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -151,7 +181,7 @@ def test_single_node_no_iterable():
render_node(container_elm, obj)
-def test_complex_any_types():
+def test_xml_complex_any_types():
# see https://github.com/mvantellingen/python-zeep/issues/252
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
@@ -231,3 +261,37 @@ def test_complex_any_types():
</document>
""") # noqa
assert_nodes_equal(result, expected)
+
+
+def test_xml_unparsed_elements():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <element minOccurs="0" maxOccurs="1" name="item" type="string" />
+ </sequence>
+ </complexType>
+ </element>
+ </schema>
+ """))
+ schema.strict = False
+ schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+
+ expected = load_xml("""
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item>bar</ns0:item>
+ <ns0:idontbelonghere>bar</ns0:idontbelonghere>
+ </ns0:container>
+ </document>
+ """)
+
+ container_elm = schema.get_element('tns:container')
+ obj = container_elm.parse(expected[0], schema)
+ assert obj.item == 'bar'
+ assert obj._raw_elements
diff --git a/tests/test_xsd_extension.py b/tests/test_xsd_extension.py
index 206b5f0..b80faf8 100644
--- a/tests/test_xsd_extension.py
+++ b/tests/test_xsd_extension.py
@@ -123,7 +123,8 @@ def test_complex_content_with_recursive_elements():
</xsd:schema>
"""))
pet_type = schema.get_element('{http://tests.python-zeep.org/}Pet')
- assert(pet_type.signature() == 'name: xsd:string, common_name: xsd:string, children: Pet')
+ assert(pet_type.signature(schema=schema) == 'ns0:Pet(ns0:Pet)')
+ assert(pet_type.type.signature(schema=schema) == 'ns0:Pet(name: xsd:string, common_name: xsd:string, children: ns0:Pet[])')
obj = pet_type(
name='foo', common_name='bar',
@@ -602,3 +603,100 @@ def test_extension_abstract_complex_type():
</document>
"""
assert_nodes_equal(expected, node)
+
+
+def test_extension_base_anytype():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+
+ <xsd:element name="container">
+ <xsd:complexType>
+ <xsd:complexContent>
+ <xsd:restriction base="xsd:anyType">
+ <xsd:attribute name="attr" type="xsd:unsignedInt" use="required"/>
+ <xsd:anyAttribute namespace="##other" processContents="lax"/>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """))
+ container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
+
+ assert container_elm.signature() == (
+ '{http://tests.python-zeep.org/}container(attr: xsd:unsignedInt, _attr_1: {})')
+
+ obj = container_elm(attr='foo')
+
+ node = render_node(container_elm, obj)
+ expected = """
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr="foo"/>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+
+def test_extension_on_ref():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <xsd:complexType name="type">
+ <xsd:complexContent>
+ <xsd:extension base="tns:base">
+ <xsd:sequence>
+ <xsd:element ref="tns:extra"/>
+ </xsd:sequence>
+ </xsd:extension>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="base">
+ <xsd:sequence>
+ <xsd:element name="item-1" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="extra" type="xsd:string"/>
+ </xsd:schema>
+ """))
+
+ type_cls = schema.get_type('ns0:type')
+ assert type_cls.signature()
+
+
+def test_restrict_on_ref():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <xsd:complexType name="type">
+ <xsd:complexContent>
+ <xsd:restriction base="tns:base">
+ <xsd:sequence>
+ <xsd:element ref="tns:extra"/>
+ </xsd:sequence>
+ </xsd:restriction>
+ </xsd:complexContent>
+ </xsd:complexType>
+ <xsd:complexType name="base">
+ <xsd:sequence>
+ <xsd:element name="item-1" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="extra" type="xsd:string"/>
+ </xsd:schema>
+ """))
+
+ type_cls = schema.get_type('ns0:type')
+ assert type_cls.signature()
diff --git a/tests/test_xsd_indicators_all.py b/tests/test_xsd_indicators_all.py
new file mode 100644
index 0000000..73b9939
--- /dev/null
+++ b/tests/test_xsd_indicators_all.py
@@ -0,0 +1,65 @@
+from lxml import etree
+
+from tests.utils import assert_nodes_equal, render_node, load_xml
+from zeep import xsd
+
+
+def test_build_occurs_1():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.All([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ])
+ ))
+
+ obj = custom_type(item_1='foo', item_2='bar')
+ result = render_node(custom_type, obj)
+
+ expected = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ </document>
+ """)
+
+ assert_nodes_equal(result, expected)
+
+ obj = custom_type.parse(result[0], None)
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+
+
+def test_build_pare_other_order():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.All([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ])
+ ))
+
+ xml = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ </ns0:authentication>
+ </document>
+ """)
+
+ obj = custom_type.parse(xml[0], None)
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
diff --git a/tests/test_xsd_choice.py b/tests/test_xsd_indicators_choice.py
similarity index 73%
rename from tests/test_xsd_choice.py
rename to tests/test_xsd_indicators_choice.py
index cff9296..00b00fb 100644
--- a/tests/test_xsd_choice.py
+++ b/tests/test_xsd_indicators_choice.py
@@ -1,9 +1,10 @@
+from collections import deque
import pytest
from lxml import etree
from tests.utils import assert_nodes_equal, load_xml, render_node
from zeep import xsd
-from zeep.exceptions import XMLParseError
+from zeep.exceptions import XMLParseError, ValidationError
from zeep.helpers import serialize_object
@@ -45,7 +46,7 @@ def test_choice_element():
element.render(node, value)
assert_nodes_equal(expected, node)
- value = element.parse(node.getchildren()[0], schema)
+ value = element.parse(node[0], schema)
assert value.item_1 == 'foo'
assert value.item_2 is None
assert value.item_3 is None
@@ -89,11 +90,89 @@ def test_choice_element_second_elm():
element.render(node, value)
assert_nodes_equal(expected, node)
- value = element.parse(node.getchildren()[0], schema)
+ value = element.parse(node[0], schema)
assert value.item_1 is None
assert value.item_2 == 'foo'
assert value.item_3 is None
+def test_choice_element_second_elm_positional():
+ node = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:complexType name="type_1">
+ <xsd:sequence>
+ <xsd:element name="child_1" type="xsd:string"/>
+ <xsd:element name="child_2" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:complexType name="type_2">
+ <xsd:sequence>
+ <xsd:element name="child_1" type="xsd:string"/>
+ <xsd:element name="child_2" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ <xsd:element name="container">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="item_1" type="tns:type_1" />
+ <xsd:element name="item_2" type="tns:type_2" />
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="containerArray">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:choice>
+ <xsd:element name="item_1" type="tns:type_1" />
+ <xsd:element name="item_2" type="tns:type_2" />
+ </xsd:choice>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """.strip())
+ schema = xsd.Schema(node)
+
+ child = schema.get_type('ns0:type_2')(child_1='ha', child_2='ho')
+
+ element = schema.get_element('ns0:container')
+ with pytest.raises(TypeError):
+ value = element(child)
+ value = element(item_2=child)
+
+ element = schema.get_element('ns0:containerArray')
+ with pytest.raises(TypeError):
+ value = element(child)
+ value = element(item_2=child)
+
+ element = schema.get_element('ns0:container')
+ value = element(item_2=child)
+ assert value.item_1 is None
+ assert value.item_2 == child
+
+ expected = """
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_2>
+ <ns0:child_1>ha</ns0:child_1>
+ <ns0:child_2>ho</ns0:child_2>
+ </ns0:item_2>
+ </ns0:container>
+ </document>
+ """
+ node = etree.Element('document')
+ element.render(node, value)
+ assert_nodes_equal(expected, node)
+
+ value = element.parse(node[0], schema)
+ assert value.item_1 is None
+ assert value.item_2.child_1 == 'ha'
+ assert value.item_2.child_2 == 'ho'
+
def test_choice_element_multiple():
node = etree.fromstring("""
@@ -137,7 +216,7 @@ def test_choice_element_multiple():
element.render(node, value)
assert_nodes_equal(expected, node)
- value = element.parse(node.getchildren()[0], schema)
+ value = element.parse(node[0], schema)
assert value._value_1 == [
{'item_1': 'foo'}, {'item_2': 'bar'}, {'item_1': 'three'},
]
@@ -179,6 +258,8 @@ def test_choice_element_optional():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
+ assert value.item_4 == 'foo'
def test_choice_element_with_any():
@@ -219,7 +300,7 @@ def test_choice_element_with_any():
element.render(node, value)
assert_nodes_equal(expected, node)
- result = element.parse(node.getchildren()[0], schema)
+ result = element.parse(node[0], schema)
assert result.name == 'foo'
assert result.something is True
assert result.item_1 == 'foo'
@@ -266,7 +347,7 @@ def test_choice_element_with_any_max_occurs():
"""
node = render_node(element, value)
assert_nodes_equal(node, expected)
- result = element.parse(node.getchildren()[0], schema)
+ result = element.parse(node[0], schema)
assert result.item_2 == 'item-2'
assert result._value_1 == ['any-content']
@@ -319,8 +400,8 @@ def test_choice_in_sequence():
schema = xsd.Schema(node)
container_elm = schema.get_element('ns0:container')
- assert container_elm.type.signature() == (
- 'something: xsd:string, ({item_1: xsd:string} | {item_2: xsd:string} | {item_3: xsd:string})') # noqa
+ assert container_elm.type.signature(schema=schema) == (
+ 'ns0:container(something: xsd:string, ({item_1: xsd:string} | {item_2: xsd:string} | {item_3: xsd:string}))')
value = container_elm(something='foobar', item_1='item-1')
expected = """
@@ -334,6 +415,7 @@ def test_choice_in_sequence():
node = etree.Element('document')
container_elm.render(node, value)
assert_nodes_equal(expected, node)
+ value = container_elm.parse(node[0], schema)
def test_choice_with_sequence():
@@ -362,8 +444,8 @@ def test_choice_with_sequence():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
value = element(item_1='foo', item_2='bar')
expected = """
@@ -377,6 +459,7 @@ def test_choice_with_sequence():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_once():
@@ -404,8 +487,8 @@ def test_choice_with_sequence_once():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- 'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string})')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}))')
value = element(item_0='nul', item_1='foo', item_2='bar')
expected = """
@@ -420,6 +503,94 @@ def test_choice_with_sequence_once():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
+
+
+def test_choice_with_sequence_unbounded():
+ node = load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:element name="container">
+ <xsd:complexType xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:choice>
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="item_0" type="xsd:string"/>
+ <xsd:element name="item_1" type="xsd:string"/>
+ <xsd:element name="item_2" type="tns:obj"/>
+ </xsd:sequence>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:complexType name="obj">
+ <xsd:sequence maxOccurs="unbounded">
+ <xsd:element name="item_2_1" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:schema>
+ """)
+ schema = xsd.Schema(node)
+ element = schema.get_element('ns0:container')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({[item_0: xsd:string, item_1: xsd:string, item_2: ns0:obj]}))')
+ value = element(_value_1=[
+ {'item_0': 'nul', 'item_1': 'foo', 'item_2': {'_value_1': [{'item_2_1': 'bar'}]}},
+ ])
+
+ expected = """
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_0>nul</ns0:item_0>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>
+ <ns0:item_2_1>bar</ns0:item_2_1>
+ </ns0:item_2>
+ </ns0:container>
+ </document>
+ """
+ node = etree.Element('document')
+ element.render(node, value)
+ assert_nodes_equal(expected, node)
+
+ value = element.parse(node[0], schema)
+ assert value._value_1[0]['item_0'] == 'nul'
+ assert value._value_1[0]['item_1'] == 'foo'
+ assert value._value_1[0]['item_2']._value_1[0]['item_2_1'] == 'bar'
+
+ assert not hasattr(value._value_1[0]['item_2'], 'item_2_1')
+
+
+def test_choice_with_sequence_missing_elements():
+ node = load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:element name="container">
+ <xsd:complexType xmlns:xsd="http://www.w3.org/2001/XMLSchema">
+ <xsd:choice maxOccurs="2">
+ <xsd:sequence>
+ <xsd:element name="item_1" type="xsd:string"/>
+ <xsd:element name="item_2" type="xsd:string"/>
+ </xsd:sequence>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """)
+ schema = xsd.Schema(node)
+ element = schema.get_element('ns0:container')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string, item_2: xsd:string})[])')
+
+ value = element(_value_1={'item_1': 'foo'})
+ with pytest.raises(ValidationError):
+ render_node(element, value)
def test_choice_with_sequence_once_extra_data():
@@ -448,8 +619,8 @@ def test_choice_with_sequence_once_extra_data():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- 'item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}), item_3: xsd:string')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(item_0: xsd:string, ({item_1: xsd:string, item_2: xsd:string}), item_3: xsd:string)')
value = element(item_0='nul', item_1='foo', item_2='bar', item_3='item-3')
expected = """
@@ -465,6 +636,7 @@ def test_choice_with_sequence_once_extra_data():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_second():
@@ -493,8 +665,8 @@ def test_choice_with_sequence_second():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
value = element(item_3='foo', item_4='bar')
expected = """
@@ -508,6 +680,7 @@ def test_choice_with_sequence_second():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_invalid():
@@ -536,8 +709,8 @@ def test_choice_with_sequence_invalid():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string}))')
with pytest.raises(TypeError):
element(item_1='foo', item_4='bar')
@@ -594,6 +767,7 @@ def test_choice_with_sequence_change():
node = etree.Element('document')
element.render(node, elm)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_change_named():
@@ -638,6 +812,7 @@ def test_choice_with_sequence_change_named():
node = etree.Element('document')
element.render(node, elm)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_multiple():
@@ -666,8 +841,8 @@ def test_choice_with_sequence_multiple():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- '({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[]')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string, item_2: xsd:string} | {item_3: xsd:string, item_4: xsd:string})[])')
value = element(_value_1=[
dict(item_1='foo', item_2='bar'),
dict(item_3='foo', item_4='bar'),
@@ -686,6 +861,7 @@ def test_choice_with_sequence_multiple():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_choice_with_sequence_and_element():
@@ -713,8 +889,8 @@ def test_choice_with_sequence_and_element():
""")
schema = xsd.Schema(node)
element = schema.get_element('ns0:container')
- assert element.type.signature() == (
- '({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})})')
+ assert element.type.signature(schema=schema) == (
+ 'ns0:container(({item_1: xsd:string} | {({item_2: xsd:string} | {item_3: xsd:string})}))')
value = element(item_2='foo')
@@ -728,6 +904,7 @@ def test_choice_with_sequence_and_element():
node = etree.Element('document')
element.render(node, value)
assert_nodes_equal(expected, node)
+ value = element.parse(node[0], schema)
def test_element_ref_in_choice():
@@ -1043,3 +1220,138 @@ def test_choice_extend():
assert value['item-1-2'] == 'bar'
assert value['_value_1'][0] == {'item-2-1': 'xafoo'}
assert value['_value_1'][1] == {'item-2-2': 'xabar'}
+
+
+def test_nested_choice():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <choice>
+ <choice minOccurs="2" maxOccurs="unbounded">
+ <element ref="tns:a" />
+ </choice>
+ <element ref="tns:b" />
+ </choice>
+ </sequence>
+ </complexType>
+ </element>
+ <element name="a" type="string" />
+ <element name="b" type="string" />
+ </schema>
+ """))
+
+ schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
+ container_type = schema.get_element('tns:container')
+
+ item = container_type(_value_1=[{'a': 'item-1'}, {'a': 'item-2'}])
+ assert item._value_1[0] == {'a': 'item-1'}
+ assert item._value_1[1] == {'a': 'item-2'}
+
+ expected = load_xml("""
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:a>item-1</ns0:a>
+ <ns0:a>item-2</ns0:a>
+ </ns0:container>
+ </document>
+ """)
+ node = render_node(container_type, item)
+ assert_nodes_equal(node, expected)
+
+ result = container_type.parse(expected[0], schema)
+ assert result._value_1[0] == {'a': 'item-1'}
+ assert result._value_1[1] == {'a': 'item-2'}
+
+ expected = load_xml("""
+ <container xmlns="http://tests.python-zeep.org/">
+ <b>1</b>
+ </container>
+ """)
+
+ result = container_type.parse(expected, schema)
+ assert result.b == '1'
+
+
+def test_unit_choice_parse_xmlelements_max_1():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:element name="container">
+ <xsd:complexType>
+ <xsd:choice>
+ <xsd:element name="item_1" type="xsd:string" />
+ <xsd:element name="item_2" type="xsd:string" />
+ <xsd:element name="item_3" type="xsd:string" />
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """))
+ element = schema.get_element('ns0:container')
+
+ def create_elm(name, text):
+ elm = etree.Element(name)
+ elm.text = text
+ return elm
+
+ data = deque([
+ create_elm('item_1', 'item-1'),
+ create_elm('item_2', 'item-2'),
+ create_elm('item_1', 'item-3'),
+ ])
+
+ result = element.type._element.parse_xmlelements(data, schema)
+ assert result == {'item_1': 'item-1'}
+ assert len(data) == 2
+
+
+def test_unit_choice_parse_xmlelements_max_2():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:element name="container">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="2">
+ <xsd:element name="item_1" type="xsd:string" />
+ <xsd:element name="item_2" type="xsd:string" />
+ <xsd:element name="item_3" type="xsd:string" />
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """))
+ element = schema.get_element('ns0:container')
+
+ def create_elm(name, text):
+ elm = etree.Element(name)
+ elm.text = text
+ return elm
+
+ data = deque([
+ create_elm('item_1', 'item-1'),
+ create_elm('item_2', 'item-2'),
+ create_elm('item_1', 'item-3'),
+ ])
+
+ result = element.type._element.parse_xmlelements(data, schema, name='items')
+ assert result == {
+ 'items': [
+ {'item_1': 'item-1'},
+ {'item_2': 'item-2'},
+ ]
+ }
+ assert len(data) == 1
diff --git a/tests/test_xsd_indicators_group.py b/tests/test_xsd_indicators_group.py
new file mode 100644
index 0000000..68d5924
--- /dev/null
+++ b/tests/test_xsd_indicators_group.py
@@ -0,0 +1,417 @@
+import pytest
+from lxml import etree
+
+from tests.utils import assert_nodes_equal, render_node, load_xml
+from zeep import xsd
+
+
+def test_build_objects():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'username'),
+ xsd.String()),
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'groupie'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'password'),
+ xsd.String(),
+ )
+ ])
+ )
+ ])
+ ))
+ assert custom_type.signature()
+ obj = custom_type(username='foo', password='bar')
+
+ expected = """
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:username>foo</ns0:username>
+ <ns0:password>bar</ns0:password>
+ </ns0:authentication>
+ </document>
+ """
+ node = etree.Element('document')
+ custom_type.render(node, obj)
+ assert_nodes_equal(expected, node)
+
+ obj = custom_type.parse(node[0], None)
+ assert obj.username == 'foo'
+ assert obj.password == 'bar'
+
+
+def test_build_group_min_occurs_1():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ min_occurs=1)
+ ))
+
+ obj = custom_type(item_1='foo', item_2='bar')
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+
+ result = render_node(custom_type, obj)
+ expected = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ </document>
+ """)
+
+ assert_nodes_equal(result, expected)
+
+ obj = custom_type.parse(result[0], None)
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+ assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_min_occurs_1_parse_args():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ min_occurs=1)
+ ))
+
+ obj = custom_type('foo', 'bar')
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+
+
+def test_build_group_min_occurs_2():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ min_occurs=2, max_occurs=2)
+ ))
+
+ obj = custom_type(_value_1=[
+ {'item_1': 'foo', 'item_2': 'bar'},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ])
+ assert obj._value_1 == [
+ {'item_1': 'foo', 'item_2': 'bar'},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ]
+
+ result = render_node(custom_type, obj)
+ expected = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ </document>
+ """)
+
+ assert_nodes_equal(result, expected)
+
+ obj = custom_type.parse(result[0], None)
+ assert obj._value_1 == [
+ {'item_1': 'foo', 'item_2': 'bar'},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ]
+ assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_min_occurs_2_sequence_min_occurs_2():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ], min_occurs=2, max_occurs=2),
+ min_occurs=2, max_occurs=2)
+ ))
+ expected = etree.fromstring("""
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:container>
+ """)
+ obj = custom_type.parse(expected, None)
+ assert obj._value_1 == [
+ {'_value_1': [
+ {'item_1': 'foo', 'item_2': 'bar'},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ]},
+ {'_value_1': [
+ {'item_1': 'foo', 'item_2': 'bar'},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ]},
+ ]
+ assert not hasattr(obj, 'foobar')
+
+
+def test_build_group_occurs_1_invalid_kwarg():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ min_occurs=1, max_occurs=1)
+ ))
+
+ with pytest.raises(TypeError):
+ custom_type(item_1='foo', item_2='bar', error=True)
+
+
+def test_build_group_min_occurs_2_invalid_kwarg():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Group(
+ etree.QName('http://tests.python-zeep.org/', 'foobar'),
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ min_occurs=2, max_occurs=2)
+ ))
+
+ with pytest.raises(TypeError):
+ custom_type(_value_1=[
+ {'item_1': 'foo', 'item_2': 'bar', 'error': True},
+ {'item_1': 'foo', 'item_2': 'bar'},
+ ])
+
+
+def test_xml_group_via_ref():
+ 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="qualified">
+
+ <xs:element name="Address">
+ <xs:complexType>
+ <xs:group ref="tns:Name" />
+ </xs:complexType>
+ </xs:element>
+
+ <xs:group name="Name">
+ <xs:sequence>
+ <xs:element name="first_name" type="xs:string" />
+ <xs:element name="last_name" type="xs:string" />
+ </xs:sequence>
+ </xs:group>
+
+ </xs:schema>
+ """))
+ address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+ obj = address_type(first_name='foo', last_name='bar')
+
+ node = etree.Element('document')
+ address_type.render(node, obj)
+ expected = """
+ <document>
+ <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:first_name>foo</ns0:first_name>
+ <ns0:last_name>bar</ns0:last_name>
+ </ns0:Address>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+
+def test_xml_group_via_ref_max_occurs_unbounded():
+ 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="qualified">
+
+ <xs:element name="Address">
+ <xs:complexType>
+ <xs:group ref="tns:Name" minOccurs="0" maxOccurs="unbounded"/>
+ </xs:complexType>
+ </xs:element>
+
+ <xs:group name="Name">
+ <xs:sequence>
+ <xs:element name="first_name" type="xs:string" />
+ <xs:element name="last_name" type="xs:string" />
+ </xs:sequence>
+ </xs:group>
+
+ </xs:schema>
+ """))
+ address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+ obj = address_type(
+ _value_1=[
+ {'first_name': 'foo-1', 'last_name': 'bar-1'},
+ {'first_name': 'foo-2', 'last_name': 'bar-2'},
+ ])
+
+ node = etree.Element('document')
+ address_type.render(node, obj)
+ expected = """
+ <document>
+ <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:first_name>foo-1</ns0:first_name>
+ <ns0:last_name>bar-1</ns0:last_name>
+ <ns0:first_name>foo-2</ns0:first_name>
+ <ns0:last_name>bar-2</ns0:last_name>
+ </ns0:Address>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+ obj = address_type.parse(node[0], None)
+ assert obj._value_1[0]['first_name'] == 'foo-1'
+ assert obj._value_1[0]['last_name'] == 'bar-1'
+ assert obj._value_1[1]['first_name'] == 'foo-2'
+ assert obj._value_1[1]['last_name'] == 'bar-2'
+
+
+def test_xml_multiple_groups_in_sequence():
+ 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:element name="Address" type="tns:AddressType" />
+
+ <xs:complexType name="AddressType">
+ <xs:sequence>
+ <xs:group ref="tns:NameGroup"/>
+ <xs:group ref="tns:AddressGroup"/>
+ </xs:sequence>
+ </xs:complexType>
+
+ <xs:group name="NameGroup">
+ <xs:sequence>
+ <xs:element name="first_name" type="xs:string" />
+ <xs:element name="last_name" type="xs:string" />
+ </xs:sequence>
+ </xs:group>
+
+ <xs:group name="AddressGroup">
+ <xs:annotation>
+ <xs:documentation>blub</xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="city" type="xs:string" />
+ <xs:element name="country" type="xs:string" />
+ </xs:sequence>
+ </xs:group>
+ </xs:schema>
+ """))
+ address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
+
+ obj = address_type(
+ first_name='foo', last_name='bar',
+ city='Utrecht', country='The Netherlands')
+
+ node = etree.Element('document')
+ address_type.render(node, obj)
+ expected = """
+ <document>
+ <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
+ <first_name>foo</first_name>
+ <last_name>bar</last_name>
+ <city>Utrecht</city>
+ <country>The Netherlands</country>
+ </ns0:Address>
+ </document>
+ """
+ assert_nodes_equal(expected, node)
+
+
+def test_xml_group_methods():
+ 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:annotation>
+ <xs:documentation>blub</xs:documentation>
+ </xs:annotation>
+ <xs:sequence>
+ <xs:element name="city" type="xs:string" />
+ <xs:element name="country" type="xs:string" />
+ </xs:sequence>
+ </xs:group>
+
+ </xs:schema>
+ """))
+ Group = schema.get_group('{http://tests.python-zeep.org/}Group')
+ assert Group.signature(schema) == (
+ 'ns0:Group(city: xsd:string, country: xsd:string)')
+ assert str(Group) == (
+ '{http://tests.python-zeep.org/}Group(city: xsd:string, country: xsd:string)')
+
+ assert len(list(Group)) == 2
diff --git a/tests/test_xsd_indicators_sequence.py b/tests/test_xsd_indicators_sequence.py
new file mode 100644
index 0000000..63b7c7b
--- /dev/null
+++ b/tests/test_xsd_indicators_sequence.py
@@ -0,0 +1,477 @@
+import pytest
+from lxml import etree
+
+from tests.utils import load_xml, render_node, assert_nodes_equal
+from zeep import xsd
+
+
+def test_build_occurs_1():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ])
+ ))
+ obj = custom_type(item_1='foo', item_2='bar')
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+
+ result = render_node(custom_type, obj)
+ expected = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ </document>
+ """)
+ assert_nodes_equal(result, expected)
+ obj = custom_type.parse(expected[0], None)
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+
+
+def test_build_occurs_1_skip_value():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ])
+ ))
+ obj = custom_type(item_1=xsd.SkipValue, item_2='bar')
+ assert obj.item_1 == xsd.SkipValue
+ assert obj.item_2 == 'bar'
+
+ result = render_node(custom_type, obj)
+ expected = load_xml("""
+ <document>
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ </document>
+ """)
+ assert_nodes_equal(result, expected)
+
+
+def test_build_min_occurs_2_max_occurs_2():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ], min_occurs=2, max_occurs=2)
+ ))
+
+ assert custom_type.signature()
+
+
+ elm = custom_type(_value_1=[
+ {'item_1': 'foo-1', 'item_2': 'bar-1'},
+ {'item_1': 'foo-2', 'item_2': 'bar-2'},
+ ])
+
+ assert elm._value_1 == [
+ {'item_1': 'foo-1', 'item_2': 'bar-1'},
+ {'item_1': 'foo-2', 'item_2': 'bar-2'},
+ ]
+
+ expected = load_xml("""
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:container>
+ """)
+ obj = custom_type.parse(expected, None)
+ assert obj._value_1 == [
+ {
+ 'item_1': 'foo',
+ 'item_2': 'bar',
+ },
+ {
+ 'item_1': 'foo',
+ 'item_2': 'bar',
+ },
+ ]
+
+
+def test_build_min_occurs_2_max_occurs_2_error():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ], min_occurs=2, max_occurs=2)
+ ))
+
+ with pytest.raises(TypeError):
+ custom_type(_value_1={
+ 'item_1': 'foo-1', 'item_2': 'bar-1', 'error': True
+ })
+
+
+def test_build_sequence_and_attributes():
+ custom_element = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ]),
+ [
+ xsd.Attribute(
+ etree.QName('http://tests.python-zeep.org/', 'attr_1'),
+ xsd.String()),
+ xsd.Attribute('attr_2', xsd.String()),
+ ]
+ ))
+ expected = load_xml("""
+ <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/" ns0:attr_1="x" attr_2="y">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:authentication>
+ """)
+ obj = custom_element.parse(expected, None)
+ assert obj.item_1 == 'foo'
+ assert obj.item_2 == 'bar'
+ assert obj.attr_1 == 'x'
+ assert obj.attr_2 == 'y'
+
+
+def test_build_sequence_with_optional_elements():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'container'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2_1'),
+ xsd.String(),
+ nillable=True)
+ ])
+ )
+ ),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_3'),
+ xsd.String(),
+ max_occurs=2),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_4'),
+ xsd.String(),
+ min_occurs=0),
+ ])
+ ))
+ expected = etree.fromstring("""
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>1</ns0:item_1>
+ <ns0:item_2/>
+ <ns0:item_3>3</ns0:item_3>
+ </ns0:container>
+ """)
+ obj = custom_type.parse(expected, None)
+ assert obj.item_1 == '1'
+ assert obj.item_2 is None
+ assert obj.item_3 == ['3']
+ assert obj.item_4 is None
+
+
+def test_build_max_occurs_unbounded():
+ custom_type = xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'authentication'),
+ xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_1'),
+ xsd.String()),
+ xsd.Element(
+ etree.QName('http://tests.python-zeep.org/', 'item_2'),
+ xsd.String()),
+ ], max_occurs='unbounded')
+ ))
+ expected = etree.fromstring("""
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
+ <ns0:item_1>foo</ns0:item_1>
+ <ns0:item_2>bar</ns0:item_2>
+ </ns0:container>
+ """)
+ obj = custom_type.parse(expected, None)
+ assert obj._value_1 == [
+ {
+ 'item_1': 'foo',
+ 'item_2': 'bar',
+ }
+ ]
+
+
+def test_xml_sequence_with_choice():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/tst"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/tst">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <choice>
+ <element name="item_1" type="xsd:string" />
+ <element name="item_2" type="xsd:string" />
+ </choice>
+ <element name="item_3" type="xsd:string" />
+ </sequence>
+ </complexType>
+ </element>
+ </schema>
+ """))
+
+ xml = load_xml("""
+ <tst:container
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tst="http://tests.python-zeep.org/tst">
+ <tst:item_1>blabla</tst:item_1>
+ <tst:item_3>haha</tst:item_3>
+ </tst:container>
+ """)
+
+ elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+ result = elm.parse(xml, schema)
+ assert result.item_1 == 'blabla'
+ assert result.item_3 == 'haha'
+
+
+def test_xml_sequence_with_choice_max_occurs_2():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/tst"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/tst">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <choice maxOccurs="2">
+ <element name="item_1" type="xsd:string" />
+ <element name="item_2" type="xsd:string" />
+ </choice>
+ <element name="item_3" type="xsd:string" />
+ </sequence>
+ <attribute name="item_1" type="xsd:string" use="optional" />
+ <attribute name="item_2" type="xsd:string" use="optional" />
+ </complexType>
+ </element>
+ </schema>
+ """))
+
+ xml = load_xml("""
+ <tst:container
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tst="http://tests.python-zeep.org/tst">
+ <tst:item_1>item-1-1</tst:item_1>
+ <tst:item_1>item-1-2</tst:item_1>
+ <tst:item_3>item-3</tst:item_3>
+ </tst:container>
+ """)
+
+ elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+ result = elm.parse(xml, schema)
+ assert result._value_1 == [
+ {'item_1': 'item-1-1'},
+ {'item_1': 'item-1-2'},
+ ]
+
+ assert result.item_3 == 'item-3'
+
+
+def test_xml_sequence_with_choice_max_occurs_3():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/tst"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/tst">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <choice maxOccurs="3">
+ <sequence>
+ <element name="item_1" type="xsd:string" />
+ <element name="item_2" type="xsd:string" />
+ </sequence>
+ <element name="item_3" type="xsd:string" />
+ </choice>
+ <element name="item_4" type="xsd:string" />
+ </sequence>
+ </complexType>
+ </element>
+ </schema>
+ """))
+
+ xml = load_xml("""
+ <tst:container
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tst="http://tests.python-zeep.org/tst">
+ <tst:item_1>text-1</tst:item_1>
+ <tst:item_2>text-2</tst:item_2>
+ <tst:item_1>text-1</tst:item_1>
+ <tst:item_2>text-2</tst:item_2>
+ <tst:item_3>text-3</tst:item_3>
+ <tst:item_4>text-4</tst:item_4>
+ </tst:container>
+ """)
+
+ elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
+ result = elm.parse(xml, schema)
+ assert result._value_1 == [
+ {'item_1': 'text-1', 'item_2': 'text-2'},
+ {'item_1': 'text-1', 'item_2': 'text-2'},
+ {'item_3': 'text-3'},
+ ]
+ assert result.item_4 == 'text-4'
+
+
+def test_xml_sequence_with_nil_element():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <element name="container">
+ <complexType>
+ <sequence>
+ <element name="item" type="xsd:string" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+ </element>
+ </schema>
+ """))
+
+ xml = load_xml("""
+ <tns:container
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tns="http://tests.python-zeep.org/">
+ <tns:item>text-1</tns:item>
+ <tns:item>text-2</tns:item>
+ <tns:item/>
+ <tns:item>text-4</tns:item>
+ <tns:item>text-5</tns:item>
+ </tns:container>
+ """)
+
+ elm = schema.get_element('{http://tests.python-zeep.org/}container')
+ result = elm.parse(xml, schema)
+ assert result.item == [
+ 'text-1',
+ 'text-2',
+ None,
+ 'text-4',
+ 'text-5',
+ ]
+
+
+def test_xml_sequence_unbounded():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <complexType name="ValueListType">
+ <sequence maxOccurs="unbounded" minOccurs="0">
+ <element ref="tns:Value"/>
+ </sequence>
+ </complexType>
+ <element name="ValueList" type="tns:ValueListType"/>
+ <element name="Value" type="tns:LongName"/>
+ <simpleType name="LongName">
+ <restriction base="string">
+ <maxLength value="256"/>
+ </restriction>
+ </simpleType>
+ </schema>
+ """))
+
+ elm_type = schema.get_type('{http://tests.python-zeep.org/}ValueListType')
+
+ with pytest.raises(TypeError):
+ elm_type(Value='bla')
+ elm_type(_value_1={'Value': 'bla'})
+
+
+def test_xml_sequence_recover_from_missing_element():
+ schema = xsd.Schema(load_xml("""
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <complexType name="container">
+ <sequence>
+ <element name="item_1" type="xsd:string"/>
+ <element name="item_2" type="xsd:string"/>
+ <element name="item_3" type="xsd:string"/>
+ <element name="item_4" type="xsd:string"/>
+ </sequence>
+ </complexType>
+ </schema>
+ """), strict=False)
+
+ xml = load_xml("""
+ <tns:container
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:tns="http://tests.python-zeep.org/">
+ <tns:item_1>text-1</tns:item_1>
+ <tns:item_3>text-3</tns:item_3>
+ <tns:item_4>text-4</tns:item_4>
+ </tns:container>
+ """)
+ elm_type = schema.get_type('{http://tests.python-zeep.org/}container')
+ result = elm_type.parse_xmlelement(xml, schema)
+ assert result.item_1 == 'text-1'
+ assert result.item_2 is None
+ assert result.item_3 == 'text-3'
+ assert result.item_4 == 'text-4'
diff --git a/tests/test_xsd_integration.py b/tests/test_xsd_integration.py
index b262b48..04d796f 100644
--- a/tests/test_xsd_integration.py
+++ b/tests/test_xsd_integration.py
@@ -3,11 +3,11 @@ import copy
import pytest
from lxml import etree
-from tests.utils import assert_nodes_equal, load_xml
+from tests.utils import assert_nodes_equal, load_xml, render_node
from zeep import xsd
-def test_complex_type_nested_wrong_type():
+def test_xml_complex_type_nested_wrong_type():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -36,7 +36,7 @@ def test_complex_type_nested_wrong_type():
container_elm(item={'bar': 1})
-def test_element_with_annotation():
+def test_xml_element_with_annotation():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -61,7 +61,7 @@ def test_element_with_annotation():
address_type(foo='bar')
-def test_complex_type_parsexml():
+def test_xml_complex_type_parsexml():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -90,8 +90,8 @@ def test_complex_type_parsexml():
assert obj.foo == 'bar'
-def test_array():
- node = etree.fromstring("""
+def test_xml_array():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -105,9 +105,7 @@ def test_array():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
schema.set_ns_prefix('tns', 'http://tests.python-zeep.org/')
address_type = schema.get_element('tns:Address')
@@ -130,8 +128,8 @@ def test_array():
assert_nodes_equal(expected, node)
-def test_complex_type_unbounded_one():
- node = etree.fromstring("""
+def test_xml_complex_type_unbounded_one():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -145,9 +143,7 @@ def test_complex_type_unbounded_one():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type(foo=['foo'])
@@ -164,8 +160,8 @@ def test_complex_type_unbounded_one():
assert_nodes_equal(expected, node)
-def test_complex_type_unbounded_named():
- node = etree.fromstring("""
+def test_xml_complex_type_unbounded_named():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -178,9 +174,8 @@ def test_complex_type_unbounded_named():
</sequence>
</complexType>
</schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type()
assert obj.foo == []
@@ -201,8 +196,8 @@ def test_complex_type_unbounded_named():
assert_nodes_equal(expected, node)
-def test_complex_type_array_to_other_complex_object():
- node = etree.fromstring("""
+def test_xml_complex_type_array_to_other_complex_object():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="Address">
@@ -217,9 +212,8 @@ def test_complex_type_array_to_other_complex_object():
</xs:complexType>
<xs:element name="ArrayOfAddress" type="ArrayOfAddress"/>
</xs:schema>
- """.strip()) # noqa
+ """))
- schema = xsd.Schema(node)
address_array = schema.get_element('ArrayOfAddress')
obj = address_array()
assert obj.Address == []
@@ -227,21 +221,26 @@ def test_complex_type_array_to_other_complex_object():
obj.Address.append(schema.get_type('Address')(foo='foo'))
obj.Address.append(schema.get_type('Address')(foo='bar'))
- node = etree.fromstring("""
+ expected = """
<?xml version="1.0"?>
- <ArrayOfAddress>
- <Address>
- <foo>foo</foo>
- </Address>
- <Address>
- <foo>bar</foo>
- </Address>
- </ArrayOfAddress>
- """.strip())
+ <document>
+ <ArrayOfAddress>
+ <Address>
+ <foo>foo</foo>
+ </Address>
+ <Address>
+ <foo>bar</foo>
+ </Address>
+ </ArrayOfAddress>
+ </document>
+ """
+
+ result = render_node(address_array, obj)
+ assert_nodes_equal(expected, result)
-def test_complex_type_init_kwargs():
- node = etree.fromstring("""
+def test_xml_complex_type_init_kwargs():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -256,9 +255,8 @@ def test_complex_type_init_kwargs():
</complexType>
</element>
</schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type(
NameFirst='John', NameLast='Doe', Email='j.doe at example.com')
@@ -267,8 +265,8 @@ def test_complex_type_init_kwargs():
assert obj.Email == 'j.doe at example.com'
-def test_complex_type_init_args():
- node = etree.fromstring("""
+def test_xml_complex_type_init_args():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -283,9 +281,7 @@ def test_complex_type_init_args():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type('John', 'Doe', 'j.doe at example.com')
assert obj.NameFirst == 'John'
@@ -293,107 +289,9 @@ def test_complex_type_init_args():
assert obj.Email == 'j.doe at example.com'
-def test_group():
- node = etree.fromstring("""
- <?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="qualified">
-
- <xs:element name="Address">
- <xs:complexType>
- <xs:group ref="tns:Name" />
- </xs:complexType>
- </xs:element>
-
- <xs:group name="Name">
- <xs:sequence>
- <xs:element name="first_name" type="xs:string" />
- <xs:element name="last_name" type="xs:string" />
- </xs:sequence>
- </xs:group>
-
- </xs:schema>
- """.strip())
- schema = xsd.Schema(node)
- address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
-
- obj = address_type(first_name='foo', last_name='bar')
-
- node = etree.Element('document')
- address_type.render(node, obj)
- expected = """
- <document>
- <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:first_name>foo</ns0:first_name>
- <ns0:last_name>bar</ns0:last_name>
- </ns0:Address>
- </document>
- """
- assert_nodes_equal(expected, node)
-
-
-def test_group_for_type():
- node = etree.fromstring("""
- <?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:element name="Address" type="tns:AddressType" />
-
- <xs:complexType name="AddressType">
- <xs:sequence>
- <xs:group ref="tns:NameGroup"/>
- <xs:group ref="tns:AddressGroup"/>
- </xs:sequence>
- </xs:complexType>
-
- <xs:group name="NameGroup">
- <xs:sequence>
- <xs:element name="first_name" type="xs:string" />
- <xs:element name="last_name" type="xs:string" />
- </xs:sequence>
- </xs:group>
-
- <xs:group name="AddressGroup">
- <xs:annotation>
- <xs:documentation>blub</xs:documentation>
- </xs:annotation>
- <xs:sequence>
- <xs:element name="city" type="xs:string" />
- <xs:element name="country" type="xs:string" />
- </xs:sequence>
- </xs:group>
- </xs:schema>
- """.strip())
- schema = xsd.Schema(node)
- address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
-
- obj = address_type(
- first_name='foo', last_name='bar',
- city='Utrecht', country='The Netherlands')
-
- node = etree.Element('document')
- address_type.render(node, obj)
- expected = """
- <document>
- <ns0:Address xmlns:ns0="http://tests.python-zeep.org/">
- <first_name>foo</first_name>
- <last_name>bar</last_name>
- <city>Utrecht</city>
- <country>The Netherlands</country>
- </ns0:Address>
- </document>
- """
- assert_nodes_equal(expected, node)
-
-
-def test_element_ref_missing_namespace():
+def test_xml_element_ref_missing_namespace():
# For buggy soap servers (#170)
- node = etree.fromstring("""
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -407,9 +305,7 @@ def test_element_ref_missing_namespace():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
custom_type = schema.get_element('{http://tests.python-zeep.org/}bar')
input_xml = load_xml("""
@@ -421,8 +317,8 @@ def test_element_ref_missing_namespace():
assert item.foo == 'bar'
-def test_element_ref():
- node = etree.fromstring("""
+def test_xml_element_ref():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -437,9 +333,7 @@ def test_element_ref():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
assert isinstance(foo_type.type, xsd.String)
@@ -460,8 +354,8 @@ def test_element_ref():
assert_nodes_equal(expected, node)
-def test_element_ref_occurs():
- node = etree.fromstring("""
+def test_xml_element_ref_occurs():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -477,9 +371,7 @@ def test_element_ref_occurs():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
foo_type = schema.get_element('{http://tests.python-zeep.org/}foo')
assert isinstance(foo_type.type, xsd.String)
@@ -500,8 +392,8 @@ def test_element_ref_occurs():
assert_nodes_equal(expected, node)
-def test_unqualified():
- node = etree.fromstring("""
+def test_xml_unqualified():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -517,9 +409,8 @@ def test_unqualified():
</complexType>
</element>
</schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type(foo='bar')
@@ -536,8 +427,8 @@ def test_unqualified():
assert_nodes_equal(expected, node)
-def test_defaults():
- node = etree.fromstring("""
+def test_xml_defaults():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -547,25 +438,26 @@ def test_defaults():
<xsd:element name="container">
<xsd:complexType>
<xsd:sequence>
- <xsd:element name="foo" type="xsd:string" default="hoi"/>
+ <xsd:element name="item_1" type="xsd:string" default="hoi"/>
+ <xsd:element name="item_2" type="xsd:string" default="hoi" minOccurs="0"/>
</xsd:sequence>
- <xsd:attribute name="bar" type="xsd:string" default="hoi"/>
+ <xsd:attribute name="attr_1" type="xsd:string" default="hoi"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
container_type = schema.get_element(
'{http://tests.python-zeep.org/}container')
obj = container_type()
- assert obj.foo == "hoi"
- assert obj.bar == "hoi"
+ assert obj.item_1 == "hoi"
+ assert obj.item_2 is None
+ assert obj.attr_1 == "hoi"
expected = """
<document>
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/" bar="hoi">
- <ns0:foo>hoi</ns0:foo>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr_1="hoi">
+ <ns0:item_1>hoi</ns0:item_1>
</ns0:container>
</document>
"""
@@ -573,9 +465,23 @@ def test_defaults():
container_type.render(node, obj)
assert_nodes_equal(expected, node)
+ obj.item_2 = 'ok'
-def test_defaults_parse():
- node = etree.fromstring("""
+ expected = """
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/" attr_1="hoi">
+ <ns0:item_1>hoi</ns0:item_1>
+ <ns0:item_2>ok</ns0:item_2>
+ </ns0:container>
+ </document>
+ """
+ node = etree.Element('document')
+ container_type.render(node, obj)
+ assert_nodes_equal(expected, node)
+
+
+def test_xml_defaults_parse_boolean():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -585,29 +491,65 @@ def test_defaults_parse():
<xsd:element name="container">
<xsd:complexType>
<xsd:sequence>
- <xsd:element name="foo" type="xsd:string" default="hoi" minOccurs="0"/>
+ <xsd:element name="foo" type="xsd:boolean" default="false"/>
</xsd:sequence>
- <xsd:attribute name="bar" type="xsd:string" default="hoi"/>
+ <xsd:attribute name="bar" type="xsd:boolean" default="0"/>
</xsd:complexType>
</xsd:element>
</xsd:schema>
- """.strip())
+ """))
+
+ container_type = schema.get_element(
+ '{http://tests.python-zeep.org/}container')
+ obj = container_type()
+ assert obj.foo == "false"
+ assert obj.bar == "0"
+
+ expected = """
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/" bar="false">
+ <ns0:foo>false</ns0:foo>
+ </ns0:container>
+ </document>
+ """
+ node = etree.Element('document')
+ container_type.render(node, obj)
+ assert_nodes_equal(expected, node)
+
+
+def test_xml_defaults_parse():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <xsd:element name="container">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="item_1" type="xsd:string" default="hoi" minOccurs="0"/>
+ </xsd:sequence>
+ <xsd:attribute name="attr_1" type="xsd:string" default="hoi"/>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ """))
- schema = xsd.Schema(node)
container_elm = schema.get_element(
'{http://tests.python-zeep.org/}container')
node = load_xml("""
<ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:foo>hoi</ns0:foo>
+ <ns0:item_1>hoi</ns0:item_1>
</ns0:container>
""")
item = container_elm.parse(node, schema)
- assert item.bar == 'hoi'
+ assert item.attr_1 == 'hoi'
-def test_init_with_dicts():
- node = etree.fromstring("""
+def test_xml_init_with_dicts():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -638,9 +580,8 @@ def test_init_with_dicts():
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
@@ -662,9 +603,8 @@ def test_init_with_dicts():
assert_nodes_equal(expected, node)
-
-def test_sequence_in_sequence():
- node = load_xml("""
+def test_xml_sequence_in_sequence():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema
xmlns="http://www.w3.org/2001/XMLSchema"
@@ -684,8 +624,7 @@ def test_sequence_in_sequence():
</element>
<element name="foobar" type="xsd:string"/>
</schema>
- """)
- schema = xsd.Schema(node)
+ """))
element = schema.get_element('ns0:container')
value = element(item_1="foo", item_2="bar")
@@ -703,7 +642,7 @@ def test_sequence_in_sequence():
assert_nodes_equal(expected, node)
-def test_sequence_in_sequence_many():
+def test_xml_sequence_in_sequence_many():
node = load_xml("""
<?xml version="1.0"?>
<schema
@@ -753,8 +692,8 @@ def test_sequence_in_sequence_many():
assert_nodes_equal(expected, node)
-def test_complex_type_empty():
- node = etree.fromstring("""
+def test_xml_complex_type_empty():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -769,9 +708,7 @@ def test_complex_type_empty():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
obj = container_elm(something={})
@@ -790,7 +727,7 @@ def test_complex_type_empty():
assert item.something is None
-def test_schema_as_payload():
+def test_xml_schema_as_payload():
schema = xsd.Schema(load_xml("""
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -834,7 +771,7 @@ def test_schema_as_payload():
assert value._value_1['item-2'] == 'value-2'
-def test_nill():
+def test_xml_nill():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
@@ -868,8 +805,8 @@ def test_nill():
assert_nodes_equal(expected, node)
-def test_empty_xmlns():
- node = load_xml("""
+def test_xml_empty_xmlns():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
@@ -885,9 +822,7 @@ def test_empty_xmlns():
</complexType>
</element>
</schema>
- """.strip())
-
- schema = xsd.Schema(node)
+ """))
container_elm = schema.get_element('{http://tests.python-zeep.org/}container')
node = load_xml("""
@@ -905,8 +840,8 @@ def test_empty_xmlns():
assert item._value_1 == 'foo'
-def test_keep_objects_intact():
- node = etree.fromstring("""
+def test_xml_keep_objects_intact():
+ schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xsd:schema
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
@@ -937,9 +872,8 @@ def test_keep_objects_intact():
</xsd:sequence>
</xsd:complexType>
</xsd:schema>
- """.strip())
+ """))
- schema = xsd.Schema(node)
address_type = schema.get_element('{http://tests.python-zeep.org/}Address')
obj = address_type(name='foo', container={'service': [{'name': 'foo'}]})
diff --git a/tests/test_xsd_parse.py b/tests/test_xsd_parse.py
index aa0e76c..511d019 100644
--- a/tests/test_xsd_parse.py
+++ b/tests/test_xsd_parse.py
@@ -7,132 +7,6 @@ from zeep import xsd
from zeep.xsd.schema import Schema
-def test_sequence_parse_basic():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ])
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj.item_1 == 'foo'
- assert obj.item_2 == 'bar'
-
-
-def test_sequence_parse_max_occurs_infinite_loop():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ], max_occurs='unbounded')
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj._value_1 == [
- {
- 'item_1': 'foo',
- 'item_2': 'bar',
- }
- ]
-
-def test_sequence_parse_basic_with_attrs():
- custom_element = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ]),
- [
- xsd.Attribute(
- etree.QName('http://tests.python-zeep.org/', 'attr_1'),
- xsd.String()),
- xsd.Attribute('attr_2', xsd.String()),
- ]
- ))
- expected = etree.fromstring("""
- <ns0:authentication xmlns:ns0="http://tests.python-zeep.org/" ns0:attr_1="x" attr_2="y">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:authentication>
- """)
- obj = custom_element.parse(expected, None)
- assert obj.item_1 == 'foo'
- assert obj.item_2 == 'bar'
- assert obj.attr_1 == 'x'
- assert obj.attr_2 == 'y'
-
-
-def test_sequence_parse_with_optional():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'container'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2_1'),
- xsd.String(),
- nillable=True)
- ])
- )
- ),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_3'),
- xsd.String(),
- max_occurs=2),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_4'),
- xsd.String(),
- min_occurs=0),
- ])
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>1</ns0:item_1>
- <ns0:item_2/>
- <ns0:item_3>3</ns0:item_3>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj.item_1 == '1'
- assert obj.item_2 is None
- assert obj.item_3 == ['3']
- assert obj.item_4 is None
-
-
def test_sequence_parse_regression():
schema_doc = load_xml(b"""
<?xml version="1.0" encoding="utf-8"?>
@@ -239,7 +113,7 @@ def test_sequence_parse_anytype_obj():
'{http://www.w3.org/2001/XMLSchema}Schema',
targetNamespace='http://tests.python-zeep.org/'))
- root = next(schema.documents)
+ root = schema.root_document
root.register_type('{http://tests.python-zeep.org/}something', value_type)
custom_type = xsd.Element(
@@ -264,147 +138,6 @@ def test_sequence_parse_anytype_obj():
assert obj.item_1.value == 100
-def test_sequence_parse_choice():
- schema_doc = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <schema
- xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:tns="http://tests.python-zeep.org/tst"
- elementFormDefault="qualified"
- targetNamespace="http://tests.python-zeep.org/tst">
- <element name="container">
- <complexType>
- <sequence>
- <choice>
- <element name="item_1" type="xsd:string" />
- <element name="item_2" type="xsd:string" />
- </choice>
- <element name="item_3" type="xsd:string" />
- </sequence>
- </complexType>
- </element>
- </schema>
- """)
-
- xml = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <tst:container
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:tst="http://tests.python-zeep.org/tst">
- <tst:item_1>blabla</tst:item_1>
- <tst:item_3>haha</tst:item_3>
- </tst:container>
- """)
-
- schema = xsd.Schema(schema_doc)
- elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
- result = elm.parse(xml, schema)
- assert result.item_1 == 'blabla'
- assert result.item_3 == 'haha'
-
-
-def test_sequence_parse_choice_max_occurs():
- schema_doc = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <schema
- xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:tns="http://tests.python-zeep.org/tst"
- elementFormDefault="qualified"
- targetNamespace="http://tests.python-zeep.org/tst">
- <element name="container">
- <complexType>
- <sequence>
- <choice maxOccurs="2">
- <element name="item_1" type="xsd:string" />
- <element name="item_2" type="xsd:string" />
- </choice>
- <element name="item_3" type="xsd:string" />
- </sequence>
- <attribute name="item_1" type="xsd:string" use="optional" />
- <attribute name="item_2" type="xsd:string" use="optional" />
- </complexType>
- </element>
- </schema>
- """)
-
- xml = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <tst:container
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:tst="http://tests.python-zeep.org/tst">
- <tst:item_1>item-1-1</tst:item_1>
- <tst:item_1>item-1-2</tst:item_1>
- <tst:item_3>item-3</tst:item_3>
- </tst:container>
- """)
-
- schema = xsd.Schema(schema_doc)
- elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
- result = elm.parse(xml, schema)
- assert result._value_1 == [
- {'item_1': 'item-1-1'},
- {'item_1': 'item-1-2'},
- ]
-
- assert result.item_3 == 'item-3'
-
-
-def test_sequence_parse_choice_sequence_max_occurs():
- schema_doc = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <schema
- xmlns="http://www.w3.org/2001/XMLSchema"
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:tns="http://tests.python-zeep.org/tst"
- elementFormDefault="qualified"
- targetNamespace="http://tests.python-zeep.org/tst">
- <element name="container">
- <complexType>
- <sequence>
- <choice maxOccurs="3">
- <sequence>
- <element name="item_1" type="xsd:string" />
- <element name="item_2" type="xsd:string" />
- </sequence>
- <element name="item_3" type="xsd:string" />
- </choice>
- <element name="item_4" type="xsd:string" />
- </sequence>
- </complexType>
- </element>
- </schema>
- """)
-
- xml = load_xml(b"""
- <?xml version="1.0" encoding="utf-8"?>
- <tst:container
- xmlns:xsd="http://www.w3.org/2001/XMLSchema"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xmlns:tst="http://tests.python-zeep.org/tst">
- <tst:item_1>text-1</tst:item_1>
- <tst:item_2>text-2</tst:item_2>
- <tst:item_1>text-1</tst:item_1>
- <tst:item_2>text-2</tst:item_2>
- <tst:item_3>text-3</tst:item_3>
- <tst:item_4>text-4</tst:item_4>
- </tst:container>
- """)
-
- schema = xsd.Schema(schema_doc)
- elm = schema.get_element('{http://tests.python-zeep.org/tst}container')
- result = elm.parse(xml, schema)
- assert result._value_1 == [
- {'item_1': 'text-1', 'item_2': 'text-2'},
- {'item_1': 'text-1', 'item_2': 'text-2'},
- {'item_3': 'text-3'},
- ]
- assert result.item_4 == 'text-4'
-
-
def test_sequence_parse_anytype_regression_17():
schema_doc = load_xml(b"""
<?xml version="1.0" encoding="utf-8"?>
@@ -465,177 +198,6 @@ def test_sequence_parse_anytype_regression_17():
assert result.getCustomFieldReturn.value.content == 'Test Solution'
-def test_sequence_min_occurs_2():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ], min_occurs=2, max_occurs=2)
- ))
-
- # INIT
- elm = custom_type(_value_1=[
- {'item_1': 'foo-1', 'item_2': 'bar-1'},
- {'item_1': 'foo-2', 'item_2': 'bar-2'},
- ])
-
- assert elm._value_1 == [
- {'item_1': 'foo-1', 'item_2': 'bar-1'},
- {'item_1': 'foo-2', 'item_2': 'bar-2'},
- ]
-
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj._value_1 == [
- {
- 'item_1': 'foo',
- 'item_2': 'bar',
- },
- {
- 'item_1': 'foo',
- 'item_2': 'bar',
- },
- ]
-
-
-def test_all_basic():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.All([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ])
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj.item_1 == 'foo'
- assert obj.item_2 == 'bar'
-
-
-def test_group_optional():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Group(
- etree.QName('http://tests.python-zeep.org/', 'foobar'),
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ]),
- min_occurs=1)
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj.item_1 == 'foo'
- assert obj.item_2 == 'bar'
- assert not hasattr(obj, 'foobar')
-
-
-def test_group_min_occurs_2():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Group(
- etree.QName('http://tests.python-zeep.org/', 'foobar'),
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ]),
- min_occurs=2, max_occurs=2)
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj._value_1 == [
- {'item_1': 'foo', 'item_2': 'bar'},
- {'item_1': 'foo', 'item_2': 'bar'},
- ]
- assert not hasattr(obj, 'foobar')
-
-
-def test_group_min_occurs_2_sequence_min_occurs_2():
- custom_type = xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'authentication'),
- xsd.ComplexType(
- xsd.Group(
- etree.QName('http://tests.python-zeep.org/', 'foobar'),
- xsd.Sequence([
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_1'),
- xsd.String()),
- xsd.Element(
- etree.QName('http://tests.python-zeep.org/', 'item_2'),
- xsd.String()),
- ], min_occurs=2, max_occurs=2),
- min_occurs=2, max_occurs=2)
- ))
- expected = etree.fromstring("""
- <ns0:container xmlns:ns0="http://tests.python-zeep.org/">
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- <ns0:item_1>foo</ns0:item_1>
- <ns0:item_2>bar</ns0:item_2>
- </ns0:container>
- """)
- obj = custom_type.parse(expected, None)
- assert obj._value_1 == [
- {'_value_1': [
- {'item_1': 'foo', 'item_2': 'bar'},
- {'item_1': 'foo', 'item_2': 'bar'},
- ]},
- {'_value_1': [
- {'item_1': 'foo', 'item_2': 'bar'},
- {'item_1': 'foo', 'item_2': 'bar'},
- ]},
- ]
- assert not hasattr(obj, 'foobar')
-
def test_nested_complex_type():
custom_type = xsd.Element(
@@ -723,7 +285,7 @@ def test_nested_complex_type_optional():
""")
obj = custom_type.parse(expected, None)
assert obj.item_1 == 'foo'
- assert obj.item_2 == []
+ assert obj.item_2 == [None]
expected = etree.fromstring("""
<ns0:container xmlns:ns0="http://tests.python-zeep.org/">
@@ -885,3 +447,19 @@ def test_parse_invalid_values():
assert result.item_2 == datetime.date(2016, 10, 20)
assert result.attr_1 is None
assert result.attr_2 == datetime.date(2013, 10, 20)
+
+
+def test_xsd_missing_localname():
+ schema = xsd.Schema(load_xml(b"""
+ <?xml version="1.0" encoding="utf-8"?>
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ elementFormDefault="qualified"
+ targetNamespace="http://tests.python-zeep.org/">
+ <element name="container" type="xsd:"/>
+ </schema>
+ """))
+
+ schema.get_element('{http://tests.python-zeep.org/}container')
diff --git a/tests/test_xsd_schemas.py b/tests/test_xsd_schemas.py
index a629e46..44a7a96 100644
--- a/tests/test_xsd_schemas.py
+++ b/tests/test_xsd_schemas.py
@@ -4,6 +4,8 @@ from lxml import etree
from tests.utils import DummyTransport, load_xml
from zeep import exceptions, xsd
from zeep.xsd import Schema
+from zeep.xsd.types.unresolved import UnresolvedType
+from tests.utils import assert_nodes_equal, load_xml, render_node
def test_default_types():
@@ -98,7 +100,7 @@ def test_invalid_localname_handling():
def test_schema_repr_none():
schema = xsd.Schema()
- assert repr(schema) == "<Schema(location='<none>')>"
+ assert repr(schema) == "<Schema()>"
def test_schema_repr_val():
@@ -111,7 +113,7 @@ def test_schema_repr_val():
elementFormDefault="qualified">
</xs:schema>
"""))
- assert repr(schema) == "<Schema(location=None)>"
+ assert repr(schema) == "<Schema(location=None, tns='http://tests.python-zeep.org/')>"
def test_schema_doc_repr_val():
@@ -433,8 +435,8 @@ def test_duplicate_target_namespace():
elm_b = schema.get_element('{http://tests.python-zeep.org/duplicate}elm-in-b')
elm_c = schema.get_element('{http://tests.python-zeep.org/duplicate}elm-in-c')
- assert not isinstance(elm_b.type, xsd.UnresolvedType)
- assert not isinstance(elm_c.type, xsd.UnresolvedType)
+ assert not isinstance(elm_b.type, UnresolvedType)
+ assert not isinstance(elm_c.type, UnresolvedType)
def test_multiple_no_namespace():
@@ -644,6 +646,136 @@ def test_include_recursion():
schema.get_element('{http://tests.python-zeep.org/b}bar')
+def test_include_relative():
+ node_a = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xs:schema
+ xmlns="http://tests.python-zeep.org/tns"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://tests.python-zeep.org/a"
+ elementFormDefault="qualified">
+
+ <xs:include schemaLocation="http://tests.python-zeep.org/subdir/b.xsd"/>
+
+ </xs:schema>
+ """.strip())
+
+ node_b = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:include schemaLocation="c.xsd"/>
+ <xs:element name="bar" type="xs:string"/>
+ </xs:schema>
+ """.strip())
+
+ node_c = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified">
+ <xs:element name="foo" type="xs:string"/>
+ </xs:schema>
+ """.strip())
+
+ transport = DummyTransport()
+ transport.bind('http://tests.python-zeep.org/subdir/b.xsd', node_b)
+ transport.bind('http://tests.python-zeep.org/subdir/c.xsd', node_c)
+
+ schema = xsd.Schema(node_a, transport=transport)
+ schema.get_element('{http://tests.python-zeep.org/a}foo')
+ schema.get_element('{http://tests.python-zeep.org/a}bar')
+
+
+def test_include_no_default_namespace():
+ node_a = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xs:schema
+ xmlns="http://tests.python-zeep.org/tns"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://tests.python-zeep.org/tns"
+ elementFormDefault="qualified">
+
+ <xs:include
+ schemaLocation="http://tests.python-zeep.org/b.xsd"/>
+
+ <xs:element name="container" type="foo"/>
+ </xs:schema>
+ """.strip())
+
+ # include without default namespace, other xsd prefix
+ node_b = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/b">
+
+ <xsd:simpleType name="my-string">
+ <xsd:restriction base="xsd:boolean"/>
+ </xsd:simpleType>
+
+ <xsd:complexType name="foo">
+ <xsd:sequence>
+ <xsd:element name="item" type="my-string"/>
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:schema>
+ """.strip())
+
+ transport = DummyTransport()
+ transport.bind('http://tests.python-zeep.org/b.xsd', node_b)
+ schema = xsd.Schema(node_a, transport=transport)
+ item = schema.get_element('{http://tests.python-zeep.org/tns}container')
+ assert item
+
+
+def test_include_different_form_defaults():
+ node_a = etree.fromstring("""
+ <?xml version="1.0"?>
+ <xs:schema
+ xmlns="http://tests.python-zeep.org/"
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://tests.python-zeep.org/">
+
+ <xs:include
+ schemaLocation="http://tests.python-zeep.org/b.xsd"/>
+ </xs:schema>
+ """.strip())
+
+ # include without default namespace, other xsd prefix
+ node_b = load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ elementFormDefault="qualified"
+ attributeFormDefault="qualified"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/b">
+
+ <xsd:element name="container" type="foo"/>
+
+ <xsd:complexType name="foo">
+ <xsd:sequence>
+ <xsd:element name="item" type="xsd:string"/>
+ </xsd:sequence>
+ <xsd:attribute name="attr" type="xsd:string"/>
+ </xsd:complexType>
+ </xsd:schema>
+ """)
+
+ transport = DummyTransport()
+ transport.bind('http://tests.python-zeep.org/b.xsd', node_b)
+
+ schema = xsd.Schema(node_a, transport=transport)
+ item = schema.get_element('{http://tests.python-zeep.org/}container')
+ obj = item(item='foo', attr='bar')
+ node = render_node(item, obj)
+
+ expected = load_xml("""
+ <document>
+ <ns0:container xmlns:ns0="http://tests.python-zeep.org/" ns0:attr="bar">
+ <ns0:item>foo</ns0:item>
+ </ns0:container>
+ </document>
+ """)
+ assert_nodes_equal(expected, node)
+
def test_merge():
node_a = etree.fromstring("""
<?xml version="1.0"?>
@@ -674,3 +806,37 @@ def test_merge():
schema_a.get_element('{http://tests.python-zeep.org/a}foo')
schema_a.get_element('{http://tests.python-zeep.org/b}foo')
+
+
+def test_xml_namespace():
+ xmlns = load_xml("""
+ <?xml version="1.0"?>
+ <xs:schema
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
+ xmlns:xml="http://www.w3.org/XML/1998/namespace"
+ targetNamespace="http://www.w3.org/XML/1998/namespace"
+ elementFormDefault="qualified">
+ <xs:attribute name="lang" type="xs:string"/>
+ </xs:schema>
+ """)
+
+ transport = DummyTransport()
+ transport.bind('http://www.w3.org/2001/xml.xsd', xmlns)
+
+ 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="qualified">
+ <xs:import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+ <xs:element name="container">
+ <xs:complexType>
+ <xs:sequence/>
+ <xs:attribute ref="xml:lang"/>
+ </xs:complexType>
+ </xs:element>
+ </xs:schema>
+ """), transport=transport)
diff --git a/tests/test_xsd_signatures.py b/tests/test_xsd_signatures.py
index 0040d6a..1bcf48e 100644
--- a/tests/test_xsd_signatures.py
+++ b/tests/test_xsd_signatures.py
@@ -1,6 +1,7 @@
from lxml import etree
from zeep import xsd
+from tests.utils import load_xml
def test_signature_complex_type_choice():
@@ -16,7 +17,7 @@ def test_signature_complex_type_choice():
xsd.String()),
])
))
- assert custom_type.signature() == '({item_1: xsd:string} | {item_2: xsd:string})'
+ assert custom_type.signature() == '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2: xsd:string}))'
def test_signature_complex_type_choice_sequence():
@@ -38,7 +39,7 @@ def test_signature_complex_type_choice_sequence():
])
))
assert custom_type.signature() == (
- '({item_1: xsd:string} | {item_2_1: xsd:string, item_2_2: xsd:string})')
+ '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2_1: xsd:string, item_2_2: xsd:string}))')
def test_signature_nested_sequences():
@@ -80,7 +81,7 @@ def test_signature_nested_sequences():
))
assert custom_type.signature() == (
- 'item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})' # noqa
+ '{http://tests.python-zeep.org/}authentication(item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string}))'
)
@@ -123,7 +124,7 @@ def test_signature_nested_sequences_multiple():
))
assert custom_type.signature() == (
- 'item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, _value_1: ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})[]' # noqa
+ '{http://tests.python-zeep.org/}authentication(item_1: xsd:string, item_2: xsd:string, item_3: xsd:string, item_4: xsd:string, ({item_5: xsd:string} | {item_6: xsd:string} | {item_5: xsd:string, item_6: xsd:string})[])'
)
@@ -138,7 +139,7 @@ def test_signature_complex_type_any():
xsd.Any()
])
))
- assert custom_type.signature() == '({item_1: xsd:string} | {_value_1: ANY})'
+ assert custom_type.signature() == '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {_value_1: ANY}))'
custom_type(item_1='foo')
@@ -161,7 +162,7 @@ def test_signature_complex_type_sequence_with_any():
])
))
assert custom_type.signature() == (
- '({item_1: xsd:string} | {item_2: {_value_1: ANY}})')
+ '{http://tests.python-zeep.org/}authentication(({item_1: xsd:string} | {item_2: {_value_1: ANY}}))')
def test_signature_complex_type_sequence_with_anys():
@@ -184,4 +185,30 @@ def test_signature_complex_type_sequence_with_anys():
])
))
assert custom_type.signature() == (
- '({item_1: xsd:string} | {item_2: {_value_1: ANY, _value_2: ANY}})')
+ '{http://tests.python-zeep.org/}authentication(' +
+ '({item_1: xsd:string} | {item_2: {_value_1: ANY, _value_2: ANY}})' +
+ ')')
+
+def test_schema_recursive_ref():
+ schema = xsd.Schema(load_xml("""
+ <?xml version="1.0"?>
+ <xsd:schema
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ xmlns:tns="http://tests.python-zeep.org/"
+ targetNamespace="http://tests.python-zeep.org/"
+ elementFormDefault="qualified">
+
+ <xsd:element name="Container">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element ref="tns:Container" />
+ </xsd:sequence>
+ </xsd:complexType>
+ </xsd:element>
+
+ </xsd:schema>
+ """))
+
+ elm = schema.get_element('ns0:Container')
+ elm.signature(schema)
+
diff --git a/tests/test_xsd_union.py b/tests/test_xsd_union.py
index 1993ff5..679ba14 100644
--- a/tests/test_xsd_union.py
+++ b/tests/test_xsd_union.py
@@ -6,6 +6,7 @@ def test_union_same_types():
schema = xsd.Schema(load_xml("""
<?xml version="1.0"?>
<xsd:schema
+ xmlns="http://tests.python-zeep.org/"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:tns="http://tests.python-zeep.org/"
targetNamespace="http://tests.python-zeep.org/"
@@ -21,7 +22,7 @@ def test_union_same_types():
</xsd:simpleType>
<xsd:simpleType name="Date">
- <xsd:union memberTypes="MMYY MMYYYY"/>
+ <xsd:union memberTypes="tns:MMYY MMYYYY"/>
</xsd:simpleType>
<xsd:element name="item" type="tns:Date"/>
</xsd:schema>
@@ -49,7 +50,7 @@ def test_union_mixed():
elementFormDefault="qualified">
<xsd:element name="item" type="tns:Date"/>
<xsd:simpleType name="Date">
- <xsd:union memberTypes="xsd:date xsd:gYear xsd:gYearMonth MMYY MMYYYY"/>
+ <xsd:union memberTypes="xsd:date xsd:gYear xsd:gYearMonth tns:MMYY tns:MMYYYY"/>
</xsd:simpleType>
<xsd:simpleType name="MMYY">
<xsd:restriction base="xsd:string">
diff --git a/tests/test_xsd_valueobjects.py b/tests/test_xsd_valueobjects.py
index f9c6eaa..5874f0f 100644
--- a/tests/test_xsd_valueobjects.py
+++ b/tests/test_xsd_valueobjects.py
@@ -1,5 +1,9 @@
+import json
+import pickle
+
import pytest
import six
+from lxml.etree import QName
from zeep import xsd
from zeep.xsd import valueobjects
@@ -214,9 +218,10 @@ def test_choice_mixed():
xsd.Element('item_2', xsd.String()),
]),
xsd.Element('item_2', xsd.String())
- ])
+ ]),
+ qname=QName('http://tests.python-zeep.org', 'container')
)
- expected = '({item_1: xsd:string} | {item_2: xsd:string}), item_2__1: xsd:string'
+ expected = '{http://tests.python-zeep.org}container(({item_1: xsd:string} | {item_2: xsd:string}), item_2__1: xsd:string)'
assert xsd_type.signature() == expected
args = tuple([])
@@ -401,3 +406,35 @@ def test_choice_sequences_init_dict():
{'item_1': 'value-1', 'item_2': 'value-2'}
]
}
+
+
+def test_pickle():
+ xsd_type = xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element('item_1', xsd.String()),
+ xsd.Element('item_2', xsd.String())
+ ]))
+
+ obj = xsd_type(item_1='x', item_2='y')
+
+ data = pickle.dumps(obj)
+ obj_rt = pickle.loads(data)
+
+ assert obj.item_1 == 'x'
+ assert obj.item_2 == 'y'
+ assert obj_rt.item_1 == 'x'
+ assert obj_rt.item_2 == 'y'
+
+
+def test_json():
+ xsd_type = xsd.ComplexType(
+ xsd.Sequence([
+ xsd.Element('item_1', xsd.String()),
+ xsd.Element('item_2', xsd.String())
+ ]))
+
+ obj = xsd_type(item_1='x', item_2='y')
+ assert obj.__json__() == {
+ 'item_1': 'x',
+ 'item_2': 'y',
+ }
diff --git a/tests/test_xsd_visitor.py b/tests/test_xsd_visitor.py
index 66d561b..d5e343e 100644
--- a/tests/test_xsd_visitor.py
+++ b/tests/test_xsd_visitor.py
@@ -23,7 +23,7 @@ def test_schema_empty():
</schema>
""")
schema = parse_schema_node(node)
- root = next(schema.documents)
+ root = schema._get_schema_documents('http://tests.python-zeep.org/')[0]
assert root._element_form == 'qualified'
assert root._attribute_form == 'unqualified'
diff --git a/tests/wsdl_files/soap.wsdl b/tests/wsdl_files/soap.wsdl
index ab28e67..5783270 100644
--- a/tests/wsdl_files/soap.wsdl
+++ b/tests/wsdl_files/soap.wsdl
@@ -1,13 +1,13 @@
<?xml version="1.0"?>
-<definitions
- xmlns:tns="http://example.com/stockquote.wsdl"
- xmlns:xsd1="http://example.com/stockquote.xsd"
- xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+<definitions
+ xmlns:tns="http://example.com/stockquote.wsdl"
+ xmlns:xsd1="http://example.com/stockquote.xsd"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns="http://schemas.xmlsoap.org/wsdl/"
- name="StockQuote"
+ name="StockQuote"
targetNamespace="http://example.com/stockquote.wsdl">
<types>
- <schema xmlns="http://www.w3.org/2001/XMLSchema"
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://example.com/stockquote.xsd"
xmlns:tns="http://example.com/stockquote.xsd" >
<complexType name="Address">
@@ -17,6 +17,12 @@
<element minOccurs="0" maxOccurs="1" name="Email" type="string"/>
</sequence>
</complexType>
+ <complexType name="ArrayOfAddress">
+ <sequence>
+ <element name="Address" type="tns:Address" minOccurs="0" maxOccurs="unbounded"/>
+ </sequence>
+ </complexType>
+
<element name="Fault1">
<complexType>
<sequence>
@@ -87,6 +93,9 @@
<fault message="tns:FaultMessageMsg1" name="fault1"/>
<fault message="tns:FaultMessageMsg2" name="fault2"/>
</operation>
+ <operation name="GetLastTradePriceNoOutput">
+ <input message="tns:GetLastTradePriceInput"/>
+ </operation>
</portType>
<binding name="StockQuoteBinding" type="tns:StockQuotePortType">
<soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
@@ -105,6 +114,12 @@
<soap:fault name="fault2" use="literal"/>
</fault>
</operation>
+ <operation name="GetLastTradePriceNoOutput">
+ <soap:operation soapAction="http://example.com/GetLastTradePrice"/>
+ <input>
+ <soap:body use="literal"/>
+ </input>
+ </operation>
</binding>
<service name="StockQuoteService">
<documentation>My first service</documentation>
diff --git a/tests/wsdl_files/soap_header.wsdl b/tests/wsdl_files/soap_header.wsdl
index 04e1269..aa1e169 100644
--- a/tests/wsdl_files/soap_header.wsdl
+++ b/tests/wsdl_files/soap_header.wsdl
@@ -1,7 +1,16 @@
<?xml version="1.0"?>
-<definitions xmlns:tns="http://example.com/stockquote.wsdl" xmlns:xsd1="http://example.com/stockquote.xsd" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns="http://schemas.xmlsoap.org/wsdl/" name="StockQuote" targetNamespace="http://example.com/stockquote.wsdl">
+<definitions
+ xmlns:tns="http://example.com/stockquote.wsdl"
+ xmlns:xsd1="http://example.com/stockquote.xsd"
+ xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
+ xmlns="http://schemas.xmlsoap.org/wsdl/"
+ xmlns:xsd="http://www.w3.org/2001/XMLSchema"
+ name="StockQuote"
+ targetNamespace="http://example.com/stockquote.wsdl">
<types>
- <schema xmlns="http://www.w3.org/2001/XMLSchema" targetNamespace="http://example.com/stockquote.xsd">
+ <schema
+ xmlns="http://www.w3.org/2001/XMLSchema"
+ targetNamespace="http://example.com/stockquote.xsd">
<complexType name="Address">
<sequence>
<element minOccurs="0" maxOccurs="1" name="NameFirst" type="string"/>
@@ -48,6 +57,10 @@
<message name="GetLastTradePriceOutput">
<part name="body" element="xsd1:TradePrice"/>
</message>
+ <message name="OptionalResponseHeader">
+ <part name="body" type="xsd:string"/>
+ </message>
+
<portType name="StockQuotePortType">
<operation name="GetLastTradePrice">
<input message="tns:GetLastTradePriceInput"/>
@@ -65,6 +78,7 @@
</input>
<output>
<soap:body use="literal"/>
+ <soap:header message="tns:OptionalResponseHeader" part="body" use="literal"/>
</output>
</operation>
</binding>
--
python-zeep
More information about the tryton-debian-vcs
mailing list